# 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 디자인 시스템 hotfix: sam_project/sam-hotfix/sam-hotfix # E2E 테스트 결과/핫픽스 관리 특성: 인증 필수 폐쇄형 ERP 시스템 (SEO 불필요) ``` --- ## Git Workflow **Priority**: 🔴 ### 브랜치 구조 | 브랜치 | 역할 | 커밋 상태 | |--------|------|-----------| | `develop` | 평소 작업 브랜치 (자유롭게) | 지저분해도 OK | | `stage` | QA/테스트 환경 | 기능별 squash 정리 | | `main` | 배포용 (기본 브랜치) | 검증된 것만 | | `feature/*` | 큰 기능/실험적 작업 시 | 선택적 사용 | ### "git 올려줘" 단축 명령어 `git 올려줘` 입력 시 **develop에 push**: 1. `git status` → 2. `git diff --stat` → 3. `git add -A` → 4. `git commit` (자동 메시지) → 5. `git push origin develop` - `snapshot.txt`, `.DS_Store` 파일은 항상 제외 - develop에서 자유롭게 커밋 (커밋 메시지 정리 불필요) ### main에 올리기 (기능별 squash merge) — 필수 규칙 사용자가 "main에 올려줘" 또는 특정 기능을 main에 올리라고 지시할 때만 실행. **절대 자동으로 main에 push하지 않음.** **🔴 반드시 기능별로 나눠서 올릴 것. 통째로 squash 금지.** ```bash # 실행 순서 git checkout main git pull origin main # 1. develop 커밋 이력 분석 → 기능별 그룹 분류 git log --oneline main..develop # 2. 기능별로 cherry-pick + squash commit (기능 수만큼 반복) git cherry-pick --no-commit <기능A커밋1> <기능A커밋2> ... git commit -m "feat: [기능A 설명]" git cherry-pick --no-commit <기능B커밋1> <기능B커밋2> ... git commit -m "feat: [기능B 설명]" # 3. push 후 develop으로 복귀 git push origin main git checkout develop ``` **기능 분류 기준**: - 같은 도메인/모듈 수정은 하나로 묶기 (예: CEO 대시보드 관련 커밋들) - CI/CD, 문서 등 인프라 변경은 별도 커밋 (예: chore: Jenkinsfile 정비) - 커밋 메시지 타입: feat(기능), fix(버그), refactor(리팩토링), chore(설정/문서) **핵심: main에는 기능 단위 커밋만 → 문제 시 `git revert`로 해당 기능만 롤백 가능** ### feature 브랜치 사용 기준 | 상황 | 방법 | |------|------| | 일반 작업 | develop에서 바로 | | 1주일+ 걸리는 큰 기능 | feature/* 따서 작업 | | 실험적 시도 | feature/* 따서 작업 | | 백엔드와 동시 수정 건 | 각자 feature/* 권장 | ### 금지 사항 - ❌ main에 직접 커밋/push - ❌ `git push --force` (main/develop) - ❌ 사용자 지시 없이 main에 merge --- ## 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**: 🔴 - 새 컴포넌트 만들기 전 프로젝트 내 유사 컴포넌트 검색 필수 - 스크린샷만으로 추측 금지, 프로젝트 표준 우선 | 요소 | 확인 사항 | |------|----------| | 모달/다이얼로그 | 너비, 배경색, 헤더 구조, 버튼 배치 | | 문서/프린트 | 용지 스타일, 헤더/푸터, 결재라인 | | 폼 | 레이아웃, 필드 배치, 버튼 위치 | | 테이블/리스트 | 컬럼 구조, 체크박스, 페이지네이션 | ### 컴포넌트 레지스트리 활용 (dev/component-registry) 실시간 스캔 기반 컴포넌트 목록 + 관계도 페이지가 존재함. 새로고침 시 최신 상태 반영. **새 컴포넌트 생성 전 필수 확인**: 1. **목록 뷰**: 동일/유사 컴포넌트가 이미 있는지 검색 2. **관계도 뷰**: 유사 컴포넌트의 구성요소(imports)를 확인하여 동일한 공통 컴포넌트 조합 패턴 따르기 **기존 컴포넌트 수정 시 필수 확인**: - 관계도의 **사용처(usedBy)** 확인 → 수정 시 영향받는 범위 파악 - usedBy가 많은 공통 컴포넌트일수록 수정 시 주의 --- ## 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으로 강제 리마운트 handleChange('companyName', e.target.value)} placeholder="회사명" disabled={!isEditMode} /> ``` ### FormField 지원 타입 | type | 설명 | 대체 컴포넌트 | |------|------|---------------| | `text` (기본값) | 일반 텍스트 입력 | Label + Input | | `number` | 숫자 입력 | Label + Input[type=number] | | `email` | 이메일 입력 | Label + Input[type=email] | | `tel` | 전화번호 (자동 포맷) | Label + PhoneInput | | `businessNumber` | 사업자등록번호 (자동 포맷) | Label + BusinessNumberInput | | `textarea` | 여러 줄 텍스트 | Label + Textarea | ### FormField로 대체하지 않는 경우 - **특수 컴포넌트 필드**: Select, DatePicker, ImageUpload, FileInput, AccountNumberInput 등 - **복합 레이아웃 필드**: 주소 검색(버튼+입력), 다중 입력 조합 - **커스텀 인터랙션**: 편집/읽기 모드가 다른 컴포넌트(예: 결제일 Select↔Input 전환) --- ## User Environment **Priority**: 🟢 - 스크린샷: 항상 바탕화면 `/Users/byeongcheolryu/Desktop/` - 파일명 패턴: `스크린샷 YYYY-MM-DD 오전/오후 HH.MM.SS.png`