From e111f7b362a36d97e9c6afbea54ad0aca4460346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Tue, 3 Feb 2026 10:17:02 +0900 Subject: [PATCH] =?UTF-8?q?feat(WEB):=20=EA=B6=8C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PermissionContext, usePermission 훅, PermissionGuard 컴포넌트 신규 추가 - AccessDenied 접근 거부 페이지 추가 - permissions lib (체커, 매퍼, 타입) 구현 - BadDebtDetail, BoardDetail, LaborDetail, PricingDetail 등 상세 페이지 권한 적용 - ProcessDetail, StepDetail, ItemDetail, PermissionDetail 권한 연동 - RootProvider에 PermissionProvider 통합 - protected layout 권한 체크 추가 - Claude 프로젝트 설정 파일 추가 Co-Authored-By: Claude Opus 4.5 --- .claude/hooks/typecheck-after-edit.sh | 25 + CLAUDE.md | 277 ++++ ...01-20] permission-system-implementation.md | 1131 +++-------------- ...-2026-02-03] claude-config-optimization.md | 163 +++ src/app/[locale]/(protected)/layout.tsx | 5 +- .../BadDebtCollection/BadDebtDetail.tsx | 20 +- .../board/BoardManagement/BoardDetail.tsx | 24 +- .../BoardManagement/BoardDetailClientV2.tsx | 6 +- .../labor-management/LaborDetailClient.tsx | 30 +- .../PricingDetailClient.tsx | 30 +- src/components/common/AccessDenied.tsx | 67 + src/components/common/PermissionGuard.tsx | 50 + src/components/items/ItemDetailClient.tsx | 18 +- .../process-management/ProcessDetail.tsx | 12 +- .../process-management/StepDetail.tsx | 12 +- .../PermissionDetailClient.tsx | 11 + src/contexts/PermissionContext.tsx | 143 +++ src/contexts/RootProvider.tsx | 12 +- src/hooks/usePermission.ts | 65 + src/lib/permissions/actions.ts | 30 + src/lib/permissions/types.ts | 19 + src/lib/permissions/utils.ts | 111 ++ 22 files changed, 1267 insertions(+), 994 deletions(-) create mode 100755 .claude/hooks/typecheck-after-edit.sh create mode 100644 CLAUDE.md create mode 100644 claudedocs/[PLAN-2026-02-03] claude-config-optimization.md create mode 100644 src/components/common/AccessDenied.tsx create mode 100644 src/components/common/PermissionGuard.tsx create mode 100644 src/contexts/PermissionContext.tsx create mode 100644 src/hooks/usePermission.ts create mode 100644 src/lib/permissions/actions.ts create mode 100644 src/lib/permissions/types.ts create mode 100644 src/lib/permissions/utils.ts diff --git a/.claude/hooks/typecheck-after-edit.sh b/.claude/hooks/typecheck-after-edit.sh new file mode 100755 index 00000000..ac58c59f --- /dev/null +++ b/.claude/hooks/typecheck-after-edit.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# PostToolUse Hook: Write/Edit 후 TypeScript 타입체크 +# exit 0 = 성공, exit 2 = 에러 (Claude에 피드백) + +INPUT=$(cat) +FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') + +# TypeScript 파일만 체크 +if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.tsx ]]; then + exit 0 +fi + +# 프로젝트 디렉토리로 이동 +cd "$CLAUDE_PROJECT_DIR" 2>/dev/null || exit 0 + +# tsc 실행 (에러만 출력, 최대 20줄) +RESULT=$(npx tsc --noEmit 2>&1 | head -20) + +if [ -n "$RESULT" ] && echo "$RESULT" | grep -q "error TS"; then + echo "TypeScript errors after editing $FILE_PATH:" >&2 + echo "$RESULT" >&2 + exit 2 +fi + +exit 0 \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..ab938687 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,277 @@ +# SAM ERP 프로젝트 규칙 + +SAM 프로젝트(Next.js 프론트엔드) 전용 규칙. 범용 규칙은 `~/.claude/RULES.md` 참조. + +--- + +## 프로젝트 개요 + +```yaml +sam_project: + frontend: sam_project/sam-next/sma-next-project/sam-react-prod # Next.js (현재) + backend_api: sam_project/sam-api/sam-api # PHP Laravel + design: sam_project/sam-design/sam-design # React 디자인 시스템 + 특성: 인증 필수 폐쇄형 ERP 시스템 (SEO 불필요) +``` + +--- + +## Client Component 사용 원칙 +**Priority**: 🔴 + +### 배경 +- 폐쇄형 사이트 → SEO 불필요, 오히려 노출되면 안 됨 +- Server Component에서는 쿠키 수정(토큰 갱신) 불가 + +### 규칙 +- **Server Component 사용 금지**: `export default async function Page()` 패턴 금지 +- **Client Component 사용**: 모든 페이지는 `'use client'` 선언 필수 +- **데이터 로딩**: useEffect에서 Server Action 호출 + +```typescript +// ✅ 올바른 패턴 +'use client'; +import { useEffect, useState } from 'react'; +import { getData } from '@/components/.../actions'; + +export default function Page() { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + getData() + .then(result => setData(result.data)) + .finally(() => setIsLoading(false)); + }, []); + + if (isLoading) return
로딩 중...
; + return ; +} +``` + +```typescript +// ❌ 잘못된 패턴 +export default async function Page() { + const result = await getData(); + return ; +} +``` + +--- + +## HttpOnly Cookie API Communication +**Priority**: 🔴 + +- HttpOnly 쿠키는 JavaScript로 읽을 수 없음 +- **모든 인증 API 호출은 Next.js API route 프록시 필수** + +```typescript +// ✅ Next.js API Proxy +// /src/app/api/proxy/[...path]/route.ts +export async function GET(request: NextRequest, { params }: { params: { path: string[] } }) { + const token = request.cookies.get('access_token')?.value; + const response = await fetch(`${BACKEND_URL}/${params.path.join('/')}`, { + headers: { 'Authorization': `Bearer ${token}` }, + }); + return response; +} + +// 프론트엔드에서는 프록시 호출 +const response = await fetch('/api/proxy/item-master/init'); +``` + +--- + +## 기획서/스크린샷 기반 UI 구현 프로세스 +**Priority**: 🔴 + +### 기획서 Description 영역 처리 +기획서 스크린샷의 Description 영역(보통 오른쪽 검은 배경)은 **설명용**이며 UI에 구현하지 않음. +빨간 원 번호, 설명 텍스트, 메타 정보 → 절대 UI에 추가 금지. + +### 필수 5단계 프로세스 + +**1단계: Description 정독 및 요소 추출** +- 각 번호(①②③...) 항목별 정확히 파악 +- 필터 조건, 테이블 헤더, 버튼/액션, 특수 기능 추출 + +**2단계: 구성 계획 작성 및 사용자 확인** +🔴 구현 전 반드시 계획 제시 후 사용자 확인 필수. 확인 없이 구현 진행 절대 금지. + +```markdown +## [페이지명] 구성 계획 +### 필터 조건 +| 필터명 | 타입 | 옵션 | 기본값 | +### 테이블 컬럼 +| 순서 | 컬럼명 | 설명 | +### 특수 기능 +- [기능1]: [설명] +``` + +**3단계: 기존 패턴 검색** +``` +1순위: 동일 기능 컴포넌트 (예: "*Dashboard*.tsx") +2순위: 유사 도메인 컴포넌트 +3순위: 공통 UI 컴포넌트 (src/components/ui/) +``` + +**4단계: 구현** - 기획서 요소만, 임의 추가 절대 금지 + +**5단계: 검증 체크리스트** +```markdown +| 기획서 요소 | 구현 여부 | 비고 | +``` + +--- + +## Component Pattern Reuse +**Priority**: 🔴 + +- 새 컴포넌트 만들기 전 프로젝트 내 유사 컴포넌트 검색 필수 +- 스크린샷만으로 추측 금지, 프로젝트 표준 우선 + +| 요소 | 확인 사항 | +|------|----------| +| 모달/다이얼로그 | 너비, 배경색, 헤더 구조, 버튼 배치 | +| 문서/프린트 | 용지 스타일, 헤더/푸터, 결재라인 | +| 폼 | 레이아웃, 필드 배치, 버튼 위치 | +| 테이블/리스트 | 컬럼 구조, 체크박스, 페이지네이션 | + +--- + +## Common Table Standards +**Priority**: 🔴 + +### 필수 컬럼 구조 +- **체크박스** → **번호(1부터)** → **데이터 컬럼** → **작업 컬럼** +- 작업 버튼: 체크박스 선택 시만 표시 +- 번호: `globalIndex` 사용 또는 `(currentPage - 1) * pageSize + index + 1` + +--- + +## Document Table Merging (rowSpan/colSpan) +**Priority**: 🔴 + +### 핵심: 구조 분석 → 코딩 (절대 순서 바꾸지 않음) + +**1단계: 플랫 인덱스 맵** - 논리적 No가 아닌 실제 렌더링 행 수 기준 +``` +flatIdx 0: No.1 겉모양 +flatIdx 1: No.2 치수-두께 ← No.2 시작 (methodSpan: 3) +flatIdx 2: No.2 치수-너비 +flatIdx 3: No.2 치수-길이 ← No.2 끝 +``` + +**2단계: 병합 범위 표기** - span은 병합 그룹의 첫 행에만 + +**3단계: Coverage Map 패턴** +```typescript +function buildCoverageMap(items, spanKey) { + const map = {}; const covered = new Set(); + items.forEach((item, idx) => { + const span = item[spanKey]; + if (span && span > 1) { + map[idx] = span; + for (let i = idx + 1; i < idx + span; i++) covered.add(i); + } + }); + return { map, covered }; +} +// map에 있으면 → +// covered에 있으면 → skip +// 둘 다 아니면 → 일반 +``` + +--- + +## Page Layout Standards +**Priority**: 🟡 + +- **AuthenticatedLayout**: `
`에 패딩 없음 +- **PageLayout**: `p-3 md:p-6` 패딩 담당 +- **page.tsx**: 패딩 wrapper 금지 (이중 패딩 방지) + +--- + +## Design Popup Policy +**Priority**: 🟡 + +- `alert()`, `confirm()`, `prompt()` 사용 금지 +- Radix UI Dialog/AlertDialog 또는 `toast from 'sonner'` 사용 + +--- + +## Radix UI Select Controlled Mode Bug +**Priority**: 🟡 + +빈 값('')으로 마운트 후 value 변경이 반영 안 되는 버그: +```tsx +// ✅ key prop으로 강제 리마운트 +