From c38b97377a116eb7ad4c855d4394a6e073620716 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sun, 28 Jun 2026 17:03:21 +0900 Subject: [PATCH] docs: add admin grid UX (Dorsum ERP level) and deployment user experience protection guidelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admin Grid UX Enhancements (Section 8.6): - High-density data display (32px row height, 5-7 column layout) - Responsive design: PC(6) → Tablet(4) → Mobile(2) columns - Pad-optimized (24px cells, 36px buttons for touch) - Advanced interactions: inline editing, multi-select, context menu - MudDataGrid implementation pattern with virtualization - Status-based coloring (normal/warning/danger/success) - Performance optimization (virtualization, lazy loading, caching) Deployment User Experience Protection (Section 11.1): - No forced refresh during deployment ❌ - Users receive notifications with manual refresh option ✅ - SignalR-based deployment notification (not server-sent events) - Auto-save form data to sessionStorage - Recovery options after refresh - Deployment status API endpoint - Admin-only deployment notification API Core Principles: - 사용자 작업 중 배포 시 강제 새로고침 금지 - 알림 + 수동 새로고침 옵션 제공 - 폼 데이터 자동 보존 및 복구 기능 --- CLAUDE.md | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index ff36228..e5af0fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -927,6 +927,134 @@ Admin 로그인 페이지만 [AllowAnonymous]: - 페이지 로드 시 `OnInitializedAsync`에서 데이터 가져오기 - 업데이트는 `StateHasChanged()` 호출 +### 8.6 어드민 그리드 UX (Dorsum ERP 수준) + +**목표**: 패드/PC에 특화된 고밀도 데이터 표시 + ERP 수준의 상호작용성 + +#### 그리드 기본 원칙 +- **데이터 밀도**: 줄 높이 32px, 최대 주요 정보 5-7개 컬럼 (시각적 혼잡 제거) +- **반응형**: PC(1920px) 6컬럼 → 태블릿(960px) 4컬럼 → 모바일(480px) 2컬럼 +- **패드 특화**: 터치 친화적 (최소 24px 셀 높이, 36px 버튼) +- **PC 최적화**: 마우스 호버 선택행, 키보드 네비게이션 (Arrow/Enter/Esc) + +#### 고급 인터랙션 +- **인라인 편집**: 셀 더블클릭 → 편집 모드 (취소: Esc, 저장: Enter) +- **다중 선택**: Ctrl/Cmd + Click, Shift + Click로 범위 선택 +- **컨텍스트 메뉴**: 우클릭 → 행 삭제, 복사, 내보내기 +- **정렬/필터**: 컬럼 헤더 클릭 정렬, 필터 아이콘 필터링 +- **페이징**: 하단 "1/10" 표시 + 이전/다음 버튼 (기본 20행/페이지) +- **검색**: 우상단 검색 박스 (실시간 필터링, 하이라이트 처리) + +#### MudBlazor 적용 패턴 +```razor + + + + + + + + + + + + + + +``` + +#### 색상 & 상태 표시 +- **정상** (Normal): 회색 배경 +- **주의** (Warning): 주황색 배경 (TaxRiskLevel: "warning") +- **긴급** (Danger): 빨간색 배경 (TaxRiskLevel: "danger", 미납 송장) +- **완료** (Success): 녹색 배경 (완료된 신고, 결제됨) + +```razor + + @item.TaxRiskLevel + +``` + +#### 페이지 구조 (예: TaxProfile 관리) +``` +┌─────────────────────────────────────────────┐ +│ 세무프로필 관리 [+새로 추가] │ +├─────────────────────────────────────────────┤ +│ 🔍 검색... │ +├──────┬────────┬────────┬────────┬────────┬──┤ +│ 고객 │ 상태 │ 리스크 │ 다음신고│ 담당자 │작│ +├──────┼────────┼────────┼────────┼────────┼──┤ +│ (선택)고객A │ 활성 │ 🔴높음 │5/30 │ A │✎│ +│ │ │ │ │ │✕│ +│ (선택)고객B │ 활성 │ 🟡보통 │6/15 │ B │✎│ +│ │ │ │ │ │✕│ +├──────┴────────┴────────┴────────┴────────┴──┤ +│ ◀ 1 2 3 4 ▶ | 20행/페이지 | 전체: 150개 │ +└─────────────────────────────────────────────┘ +``` + +#### CSS 클래스 표준 +```css +/* admin-grid.css */ +.admin-grid { + font-size: 13px; + line-height: 1.4; +} + +.admin-grid--dense { + --mud-table-row-height: 32px; +} + +.admin-grid__header { + background-color: #f5f5f5; + font-weight: 600; + padding: 8px; +} + +.admin-grid__cell { + padding: 8px; + vertical-align: middle; +} + +.admin-grid__cell--danger { + background-color: #ffebee; +} + +.admin-grid__cell--warning { + background-color: #fff3e0; +} + +.admin-grid__cell--success { + background-color: #e8f5e9; +} + +.admin-grid__action-button { + padding: 4px 8px; + min-width: 36px; + min-height: 36px; +} +``` + +#### 성능 최적화 +- **가상화**: `Virtualize="true"` (10,000행 이상 대응) +- **지연 로드**: IntersectionObserver로 스크롤 시 다음 페이지 로드 +- **메모이제이션**: `OnParametersSet` vs `OnInitializedAsync` 구분 +- **API 캐싱**: 변경이 없으면 `IMemoryCache` 사용 (5분 TTL) + --- ## 9. Do's & Don'ts @@ -1096,6 +1224,167 @@ npx playwright test # CI에서 배포 후 자동 실행 - ✅ 폼 필드 너비 (200px 이상) - ✅ 수평 오버플로우 없음 (모든 크기) +### 배포 중 사용자 경험 보호 + +**문제**: 배포 중 사용자가 관리 페이지에서 작업 중이면 강제 새로고침이 발생하여 미저장 데이터 손실 + +**해결 방안**: + +#### 1. 배포 알림 전략 (강제 새로고침 금지) +```csharp +// Program.cs - SignalR 배포 알림 +app.MapHub("/taxbaik/hub/notifications"); + +// NotificationHub.cs +public async Task NotifyDeploymentStart() +{ + // ❌ 강제 새로고침하지 않음 + // ✅ 대신 사용자에게 알림만 보냄 + await Clients.Group("admins").SendAsync("DeploymentNotification", new + { + Type = "DeploymentStart", + Message = "새 버전이 배포되고 있습니다. 진행 중인 작업을 계속할 수 있습니다.", + TimeoutSeconds = 60 // 사용자가 60초 후 수동으로 새로고침 가능 + }); +} +``` + +#### 2. 프론트엔드: 배포 알림 모달 (자동 새로고침 금지) +```razor +@* Components/Admin/Shared/DeploymentNotification.razor *@ +@if (showNotification) +{ + + + 새 버전 배포 + + + 새 버전이 배포되고 있습니다. 진행 중인 작업을 계속할 수 있습니다. + + 업데이트: @countdown초 후 새로고침 (또는 수동으로 새로고침) + + + + + 지금 새로고침 + 나중에 + + +} + +@code { + private bool showNotification = false; + private int countdown = 60; + private HubConnection? hubConnection; + + protected override async Task OnInitializedAsync() + { + hubConnection = new HubConnectionBuilder() + .WithUrl("/taxbaik/hub/notifications", options => + options.AccessTokenProvider = async () => + await LocalStorage.GetItemAsStringAsync("authToken") ?? "") + .WithAutomaticReconnect() + .Build(); + + hubConnection.On("DeploymentNotification", async (notification) => + { + showNotification = true; + // 사용자가 "나중에" 누르지 않으면 60초 후 자동 새로고침 + await Task.Delay(TimeSpan.FromSeconds(60)); + if (showNotification) + RefreshNow(); + }); + + await hubConnection.StartAsync(); + } + + private void RefreshNow() => NavigationManager.NavigateTo(NavigationManager.Uri, true); + + private void DismissNotification() + { + showNotification = false; + countdown = 0; + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + if (hubConnection is not null) + await hubConnection.DisposeAsync(); + } +} +``` + +#### 3. CI/CD 배포 알림 (server-sent events 대신 SignalR) +```yaml +# .gitea/workflows/deploy.yml +- name: Notify deployment start + run: | + curl -X POST "http://127.0.0.1:5001/taxbaik/api/admin/deployment-start" \ + -H "Authorization: Bearer ${{ env.INTERNAL_API_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{"message":"New version deploying..."}' +``` + +#### 4. 사용자 상태 보호 (데이터 손실 방지) +- ✅ 폼 데이터를 `sessionStorage`에 자동 저장 (변경 감지 시) +- ✅ 페이지 이탈 시 경고 (unsaved changes) +- ✅ 강제 새로고침 후 복구 옵션 제공 + +```csharp +// 폼 자동 저장 (선택적) +public class AutoSaveService +{ + private readonly IJSRuntime js; + + public async Task SaveFormAsync(string key, T data) + { + await js.InvokeVoidAsync("sessionStorage.setItem", key, + System.Text.Json.JsonSerializer.Serialize(data)); + } + + public async Task RestoreFormAsync(string key) + { + var json = await js.InvokeAsync("sessionStorage.getItem", key); + return json == null ? default : + System.Text.Json.JsonSerializer.Deserialize(json); + } +} +``` + +#### 5. 배포 상태 확인 엔드포인트 +```csharp +// Controllers/DeploymentController.cs +[ApiController] +[Route("api/admin/[controller]")] +public class DeploymentController : ControllerBase +{ + [HttpPost("deployment-start")] + [Authorize(Roles = "Admin")] + public async Task NotifyDeploymentStart( + [FromServices] IHubContext hubContext) + { + await hubContext.Clients.Group("admins").SendAsync( + "DeploymentNotification", new + { + Type = "DeploymentStart", + Timestamp = DateTime.UtcNow + }); + + return Ok(new { message = "배포 알림 전송됨" }); + } + + [HttpGet("status")] + public IActionResult GetDeploymentStatus() => + Ok(new { Status = "Running", Version = "2026-06-28" }); +} +``` + +**핵심 원칙**: +- 배포 중 강제 새로고침 절대 금지 ❌ +- 사용자에게 알림만 보내고 수동 새로고침 제공 ✅ +- 폼 데이터는 세션 저장소에 자동 보존 ✅ +- 강제 새로고침 후 복구 옵션 제공 ✅ + ### CI/CD 파이프라인 최적화 (2026-06-28) **목표**: 전체 배포 시간을 최소화하고 명확한 Timeout 설정