feat(WEB): 권한 관리 시스템 구현 및 상세 페이지 권한 통합
- 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 <noreply@anthropic.com>
2026-02-03 10:17:02 +09:00
|
|
|
# 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 디자인 시스템
|
2026-02-04 22:40:18 +09:00
|
|
|
hotfix: sam_project/sam-hotfix/sam-hotfix # E2E 테스트 결과/핫픽스 관리
|
feat(WEB): 권한 관리 시스템 구현 및 상세 페이지 권한 통합
- 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 <noreply@anthropic.com>
2026-02-03 10:17:02 +09:00
|
|
|
특성: 인증 필수 폐쇄형 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 <div>로딩 중...</div>;
|
|
|
|
|
return <Component initialData={data} />;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
// ❌ 잘못된 패턴
|
|
|
|
|
export default async function Page() {
|
|
|
|
|
const result = await getData();
|
|
|
|
|
return <Component initialData={result.data} />;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 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에 있으면 → <td rowSpan={span}>
|
|
|
|
|
// covered에 있으면 → skip
|
|
|
|
|
// 둘 다 아니면 → 일반 <td>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Page Layout Standards
|
|
|
|
|
**Priority**: 🟡
|
|
|
|
|
|
|
|
|
|
- **AuthenticatedLayout**: `<main>`에 패딩 없음
|
|
|
|
|
- **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으로 강제 리마운트
|
|
|
|
|
<Select key={`${fieldKey}-${stringValue}`} value={stringValue} onValueChange={onChange}>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Build Policy
|
|
|
|
|
**Priority**: 🔴
|
|
|
|
|
|
|
|
|
|
- Claude가 직접 `npm run build` 실행 금지
|
|
|
|
|
- 빌드 필요 시 사용자에게 "빌드 확인해주세요" 요청
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## React → Next.js Migration Rules
|
|
|
|
|
**Priority**: 🔴
|
|
|
|
|
|
|
|
|
|
### localStorage Access
|
|
|
|
|
```typescript
|
|
|
|
|
// ✅ Next.js Pattern
|
|
|
|
|
const [data, setData] = useState(() => {
|
|
|
|
|
if (typeof window === 'undefined') return defaultValue;
|
|
|
|
|
const saved = localStorage.getItem('key');
|
|
|
|
|
return saved ? JSON.parse(saved) : defaultValue;
|
|
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### App Router Rules
|
|
|
|
|
- Client Components: 'use client' for interactivity, state, browser APIs
|
|
|
|
|
- Dynamic Import: `next/dynamic` with `ssr: false` for client-only components
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Large File Migration Workflow
|
|
|
|
|
**Priority**: 🟡
|
|
|
|
|
|
|
|
|
|
**섹션당 6단계**: 구조 파악 → 기능 구현 → 기능 검증 → 스타일 파악 → 스타일 구현 → 스타일 검증
|
|
|
|
|
|
|
|
|
|
분할 전략: <1000줄 전체 | 1000-3000줄 3-4섹션 | >3000줄 1000줄 단위
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Backend API Analysis Policy
|
|
|
|
|
**Priority**: 🟡
|
|
|
|
|
|
|
|
|
|
- Backend API 코드는 **분석만**, 직접 수정 안 함
|
|
|
|
|
- 수정 필요 시 백엔드 요청 문서로 정리:
|
|
|
|
|
```markdown
|
|
|
|
|
## 백엔드 API 수정 요청
|
|
|
|
|
### 파일 위치: `/path/to/file.php` - 메서드명 (Line XX-XX)
|
|
|
|
|
### 현재 문제: [설명]
|
|
|
|
|
### 수정 요청: [내용]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Test URL Documentation Rules
|
|
|
|
|
**Priority**: 🟡
|
|
|
|
|
|
|
|
|
|
- 메인 페이지만 등록, 세부 페이지(상세/수정/등록) 제외
|
|
|
|
|
- 간결한 목록 유지
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## User Environment
|
|
|
|
|
**Priority**: 🟢
|
|
|
|
|
|
|
|
|
|
- 스크린샷: 항상 바탕화면 `/Users/byeongcheolryu/Desktop/`
|
|
|
|
|
- 파일명 패턴: `스크린샷 YYYY-MM-DD 오전/오후 HH.MM.SS.png`
|