feat(WEB): 견적서 V2 컴포넌트 개선 및 미리보기 모달 패턴 적용

- LocationDetailPanel: 6개 탭 구현 (본체, 가이드레일, 케이스, 하단마감재, 모터&제어기, 부자재)
- 각 탭별 다른 테이블 컬럼 구조 적용
- QuoteSummaryPanel: 개소별/상세별 합계 패널 개선
- QuotePreviewModal: EstimateDocumentModal 패턴 적용 (헤더+버튼 영역 분리)
- Input value → defaultValue 변경으로 React 경고 해결
- 팩스/카카오톡 버튼 제거

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2026-01-12 15:26:17 +09:00
parent e56b7d53a4
commit d036ce4f42
40 changed files with 5292 additions and 141 deletions

View File

@@ -1,6 +1,7 @@
# 권한 관리 시스템 현황 분석
> 작성일: 2026-01-07
> 최종 수정일: 2026-01-12
> 목적: SAM 프로젝트 권한 시스템 현황 파악 및 향후 구현 계획 정리
---
@@ -10,13 +11,13 @@
| 구분 | 상태 | 설명 |
|------|------|------|
| 권한 설정 UI | ✅ 완성 | `/settings/permissions/[id]`에서 역할별 권한 설정 가능 |
| 백엔드 권한 API | ✅ 존재 | 권한 매트릭스 조회/설정 API 구현됨 |
| 백엔드 권한 API | ✅ 완성 | 권한 매트릭스 조회/설정 API 구현됨 |
| 백엔드 API 권한 체크 | ⚠️ 구조만 있음 | 미들웨어 존재하나 라우트에 미적용 |
| 프론트 권한 체크 | ❌ 미구현 | 권한 매트릭스 조회 및 UI 제어 로직 없음 |
---
## 2. 권한 타입 (7가지)
## 2. 권한 타입 (5가지)
| 권한 | 영문 | 적용 대상 |
|------|------|----------|
@@ -25,8 +26,8 @@
| 수정 | `update` | 수정 버튼 |
| 삭제 | `delete` | 삭제 버튼 |
| 승인 | `approve` | 승인/반려 버튼 |
| 내보내기 | `export` | Excel 다운로드 등 |
| 관리 | `manage` | 관리자 전용 기능 |
> ⚠️ **참고**: `export`, `manage` 권한은 백엔드에 미구현 상태
---
@@ -51,32 +52,91 @@
### 3.2 권한 매트릭스 조회 API
**사용자별 권한 조회**:
**사용자별 권한 조회** (프론트엔드에서 사용):
```
GET /api/v1/permissions/users/{userId}/menu-matrix
```
**응답 구조**:
**실제 응답 구조**:
```json
{
"permission_types": ["view", "create", "update", "delete", "approve", "export", "manage"],
"permissions": {
"1": { "view": true, "create": true, "update": false, ... },
"2": { "view": true, "create": false, ... }
"success": true,
"message": "유저 메뉴 권한 매트릭스 조회 성공",
"data": {
"actions": ["view", "create", "update", "delete", "approve"],
"tree": [
{
"menu_id": 1,
"parent_id": null,
"name": "대시보드",
"url": "/dashboard",
"type": "system",
"children": [
{
"menu_id": 2,
"parent_id": 1,
"name": "CEO 대시보드",
"url": "/dashboard/ceo",
"children": [],
"actions": { ... }
}
],
"actions": {
"view": {
"permission_id": 123,
"permission_code": "menu:1.view",
"guard_name": "api",
"state": "allow",
"is_allowed": 1
},
"create": {
"permission_id": 124,
"permission_code": "menu:1.create",
"guard_name": "api",
"state": "deny",
"is_allowed": 0
},
"update": null,
"delete": null,
"approve": null
}
}
]
}
}
```
### 3.3 기타 권한 API
**권한 상태 값**:
| state | is_allowed | 의미 |
|-------|------------|------|
| `allow` | 1 | 권한 허용됨 |
| `deny` | 0 | 권한 명시적 거부 |
| `none` | 0 | 권한 미설정 (기본 거부) |
| 엔드포인트 | 설명 |
|-----------|------|
| `GET /api/v1/permissions/departments/{dept_id}/menu-matrix` | 부서별 권한 매트릭스 |
| `GET /api/v1/permissions/roles/{role_id}/menu-matrix` | 역할별 권한 매트릭스 |
| `GET /api/v1/roles/{id}/permissions/matrix` | 역할 권한 매트릭스 (설정 UI용) |
| `POST /api/v1/roles/{id}/permissions/toggle` | 개별 권한 토글 |
| `POST /api/v1/roles/{id}/permissions/allow-all` | 전체 허용 |
| `POST /api/v1/roles/{id}/permissions/deny-all` | 전체 거부 |
**actions가 null인 경우**: 해당 메뉴에 해당 권한이 정의되지 않음
### 3.3 권한 매트릭스 API 목록
| 엔드포인트 | 메서드 | 설명 |
|-----------|--------|------|
| `/api/v1/permissions/users/{user_id}/menu-matrix` | GET | 사용자별 권한 매트릭스 |
| `/api/v1/permissions/roles/{role_id}/menu-matrix` | GET | 역할별 권한 매트릭스 |
| `/api/v1/permissions/departments/{dept_id}/menu-matrix` | GET | 부서별 권한 매트릭스 |
### 3.4 역할 권한 관리 API
| 엔드포인트 | 메서드 | 설명 |
|-----------|--------|------|
| `/api/v1/role-permissions/menus` | GET | 권한 설정용 메뉴 트리 |
| `/api/v1/roles/{id}/permissions` | GET | 역할 권한 목록 |
| `/api/v1/roles/{id}/permissions` | POST | 역할 권한 부여 |
| `/api/v1/roles/{id}/permissions` | DELETE | 역할 권한 회수 |
| `/api/v1/roles/{id}/permissions/sync` | PUT | 역할 권한 동기화 |
| `/api/v1/roles/{id}/permissions/matrix` | GET | 역할 권한 매트릭스 (설정 UI용) |
| `/api/v1/roles/{id}/permissions/toggle` | POST | 개별 권한 토글 |
| `/api/v1/roles/{id}/permissions/allow-all` | POST | 전체 허용 |
| `/api/v1/roles/{id}/permissions/deny-all` | POST | 전체 거부 |
| `/api/v1/roles/{id}/permissions/reset` | POST | 기본값 초기화 (view만 허용) |
---
@@ -124,13 +184,15 @@ HTTP 메서드에 따라 액션 자동 매핑:
- 로그인 시 `menus`, `roles` 데이터 저장 (localStorage)
- 사이드바 메뉴 표시 (백엔드에서 필터링된 메뉴)
- 메뉴 폴링 (30초 주기)
- 역할별 권한 설정 UI (`/settings/permissions/[id]`)
### 5.2 미구현 사항
- 권한 매트릭스 API 호출
- 권한 데이터 저장
- 권한 데이터 저장 (permissionStore)
- `usePermission`
- 페이지/버튼별 권한 체크
- 환경 변수 플래그
---
@@ -143,7 +205,7 @@ HTTP 메서드에 따라 액션 자동 매핑:
/api/v1/permissions/users/{userId}/menu-matrix 호출
권한 매트릭스 저장 (Zustand/localStorage)
권한 매트릭스 저장 (Zustand permissionStore)
usePermission 훅으로 권한 체크
@@ -152,12 +214,13 @@ usePermission 훅으로 권한 체크
**usePermission 훅 예시**:
```typescript
// 사용법
const { canView, canCreate, canUpdate, canDelete } = usePermission('판매관리');
// 사용법 (메뉴명 또는 URL로 조회)
const { canView, canCreate, canUpdate, canDelete, canApprove } = usePermission('/sales/orders');
// 적용
{canCreate && <Button>등록</Button>}
{canDelete && <Button>삭제</Button>}
{canApprove && <Button>승인</Button>}
```
**환경 변수 플래그**:
@@ -209,6 +272,7 @@ Route::post('/orders', [OrderController::class, 'store'])
| `src/components/settings/PermissionManagement/` | 권한 관리 컴포넌트 |
| `src/layouts/AuthenticatedLayout.tsx` | 메뉴 표시 레이아웃 |
| `src/middleware.ts` | 인증 체크 (권한 체크 없음) |
| `src/store/menuStore.ts` | 메뉴 상태 관리 |
### 백엔드 (sam-api)
@@ -218,7 +282,9 @@ Route::post('/orders', [OrderController::class, 'store'])
| `app/Http/Controllers/Api/V1/RolePermissionController.php` | 역할 권한 API |
| `app/Http/Middleware/CheckPermission.php` | 권한 체크 미들웨어 |
| `app/Http/Middleware/PermMapper.php` | HTTP → 액션 매핑 |
| `app/Services/PermissionService.php` | 권한 매트릭스 서비스 |
| `app/Services/Authz/AccessService.php` | 권한 판정 서비스 |
| `app/Services/Authz/RolePermissionService.php` | 역할 권한 서비스 |
---

View File

@@ -0,0 +1,153 @@
# 프론트엔드 권한 시스템 구현 체크리스트
> 작성일: 2026-01-12
> 참고 문서: [ANALYSIS-2026-01-07] permission-system-status.md
---
## 구현 목표
로그인한 사용자의 권한에 따라 UI 요소(버튼, 메뉴 등)를 동적으로 표시/숨김 처리
---
## Phase 1: 기반 구조 구축
### 1.1 타입 정의
- [ ] `src/types/permission.ts` 생성
- [ ] `PermissionAction` 타입 (view, create, update, delete, approve)
- [ ] `PermissionState` 타입 (allow, deny, none)
- [ ] `MenuPermission` 인터페이스 (API 응답 구조)
- [ ] `PermissionMatrix` 인터페이스 (트리 → 플랫 변환용)
### 1.2 환경 변수 설정
- [ ] `.env.local``NEXT_PUBLIC_ENABLE_AUTHORIZATION=false` 추가
- [ ] `.env.example`에 동일 항목 추가 (문서화)
---
## Phase 2: 상태 관리
### 2.1 Permission Store 생성
- [ ] `src/store/permissionStore.ts` 생성
- [ ] 상태 정의
- [ ] `permissions`: URL 기반 권한 맵 (`Record<string, PermissionActions>`)
- [ ] `isLoaded`: 권한 로딩 완료 여부
- [ ] `isEnabled`: 환경 변수 기반 활성화 여부
- [ ] 액션 정의
- [ ] `setPermissions(tree)`: API 응답 트리를 플랫 맵으로 변환 저장
- [ ] `clearPermissions()`: 로그아웃 시 초기화
- [ ] `hasPermission(url, action)`: 권한 체크 함수
- [ ] persist 미들웨어 적용 (localStorage)
### 2.2 유틸리티 함수
- [ ] `src/lib/permission-utils.ts` 생성
- [ ] `flattenPermissionTree(tree)`: 트리 구조를 URL 기반 플랫 맵으로 변환
- [ ] `normalizeUrl(url)`: URL 정규화 (locale 제거 등)
---
## Phase 3: API 연동
### 3.1 Server Action 생성
- [ ] `src/lib/api/permissions/actions.ts` 생성
- [ ] `getUserPermissions(userId)`: 권한 매트릭스 API 호출
### 3.2 로그인 플로우 연동
- [ ] 로그인 성공 후 권한 API 호출 로직 추가
- [ ] `AuthenticatedLayout.tsx` 또는 로그인 처리 부분에서 호출
- [ ] 권한 로딩 중 상태 처리 (로딩 UI 또는 스켈레톤)
---
## Phase 4: usePermission 훅 구현
### 4.1 훅 생성
- [ ] `src/hooks/usePermission.ts` 생성
- [ ] 입력: 메뉴 URL 또는 메뉴명
- [ ] 출력:
```typescript
{
canView: boolean;
canCreate: boolean;
canUpdate: boolean;
canDelete: boolean;
canApprove: boolean;
isLoading: boolean;
}
```
- [ ] 환경 변수 비활성화 시 모두 `true` 반환
### 4.2 편의 컴포넌트 (선택사항)
- [ ] `src/components/common/PermissionGuard.tsx` 생성
```typescript
<PermissionGuard menu="/sales/orders" action="create">
<Button>등록</Button>
</PermissionGuard>
```
---
## Phase 5: 적용 및 테스트
### 5.1 샘플 페이지 적용
- [ ] 테스트용 페이지 1개 선정 (예: 판매관리)
- [ ] 등록/수정/삭제 버튼에 권한 체크 적용
- [ ] 동작 확인
### 5.2 전체 적용 (점진적)
- [ ] 주요 페이지 목록 작성
- [ ] 각 페이지별 권한 적용 진행
---
## Phase 6: 예외 처리 및 UX
### 6.1 에러 처리
- [ ] 권한 API 실패 시 fallback 처리 (모두 허용 or 모두 거부)
- [ ] 네트워크 오류 시 재시도 로직
### 6.2 UX 개선
- [ ] 권한 없는 버튼: 숨김 vs 비활성화(disabled) 정책 결정
- [ ] 권한 없는 페이지 접근 시 처리 (리다이렉트 or 안내 메시지)
---
## 파일 생성 목록 요약
| 파일 경로 | 설명 |
|----------|------|
| `src/types/permission.ts` | 권한 관련 타입 정의 |
| `src/store/permissionStore.ts` | 권한 상태 관리 (Zustand) |
| `src/lib/permission-utils.ts` | 권한 유틸리티 함수 |
| `src/lib/api/permissions/actions.ts` | 권한 API Server Action |
| `src/hooks/usePermission.ts` | 권한 체크 훅 |
| `src/components/common/PermissionGuard.tsx` | 권한 가드 컴포넌트 (선택) |
---
## 의존성
- 추가 패키지 설치 불필요 (기존 Zustand 활용)
---
## 주의사항
1. **환경 변수 기본값**: 개발 중에는 `NEXT_PUBLIC_ENABLE_AUTHORIZATION=false`로 비활성화
2. **플랫 맵 변환**: API 응답이 트리 구조이므로 URL 기반 플랫 맵으로 변환 필요
3. **URL 정규화**: locale prefix (`/ko`, `/en`) 제거하여 비교
4. **로그아웃 시 초기화**: permissionStore 클리어 필수
---
## 예상 작업 순서
```
Phase 1 (타입/환경변수) → Phase 2 (스토어) → Phase 3 (API 연동)
→ Phase 4 (훅) → Phase 5 (적용) → Phase 6 (예외처리)
```
---
*체크리스트 완료 후 이 문서를 archive로 이동*

View File

@@ -0,0 +1,133 @@
# [IMPL-2026-01-12] 견적 V2 테스트 페이지 구현
## 개요
- **목적**: 견적 등록/상세/수정 페이지의 새로운 UI (자동 견적 산출 V2) 테스트
- **원칙**: 기존 견적관리 페이지는 절대 수정하지 않음 (API 연결됨)
- **범위**: 테스트 페이지 3개 + 새 컴포넌트 생성
---
## 스크린샷 기반 UI 구성
### 레이아웃 구조
```
┌─────────────────────────────────────────────────────────────┐
│ [발주 개소 목록 (3)] │ [1층 / FSS-01 상세정보] │
│ ┌──────────────────────┐ │ 제품명: KSS01 │
│ │ 층 │ 부호 │사이즈│제품│수량│ 오픈사이즈: 5000 × 3000 │
│ │ 1층│FSS-01│5000×3000│KSS01│1│ 제작사이즈/중량/면적/수량 │
│ │ 3층│FST-30│7500×3300│KSS02│1│ ───────────────────── │
│ │ 5층│FSS-50│6000×2800│KSS01│2│ 필수설정: 가이드레일/전원/제어기│
│ └──────────────────────┘ │ ───────────────────── │
│ [품목 추가 폼] │ [탭: 본체│철골품-가이드레일│...]│
│ 층|부호|가로|세로|제품명|수량 │ [품목 테이블] │
│ 가이드레일|전원|제어기 [+][↑] │ │
├─────────────────────────────────────────────────────────────┤
│ 💰 견적 금액 요약 │
│ ┌─────────────────┐ ┌──────────────────────────────┐ │
│ │ 개소별 합계 │ │ 상세별 합계 (선택 개소) │ │
│ │ 1층/FSS-01 1,645,200│ │ 본체(스크린/슬랫) 1,061,676 │ │
│ │ 3층/FST-30 2,589,198│ │ 철골품-가이드레일 116,556 │ │
│ │ 5층/FSS-50 3,442,428│ │ ... │ │
│ └─────────────────┘ └──────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 총 개소 수: 3 │ 예상 견적금액: 11,119,254 │ 견적상태: 작성중│
├─────────────────────────────────────────────────────────────┤
│ 예상 전체 견적금액 [견적서산출] [임시저장] [최종저장] │
│ 11,119,254원 │
└─────────────────────────────────────────────────────────────┘
```
### 기능 요약
| 영역 | 기능 |
|------|------|
| 발주 개소 목록 | 테이블로 개소 표시, 클릭 시 우측 상세 변경 |
| 품목 추가 폼 | 층/부호/사이즈/제품/수량 + 설정 입력 후 [+] 추가 |
| 엑셀 업로드 | [↑] 버튼으로 엑셀 일괄 업로드 |
| 상세 정보 | 선택 개소의 제품정보, 필수설정, 품목탭 |
| 견적 금액 요약 | 개소별 합계 + 상세별 합계 |
| 푸터 | 총 개소 수, 예상 견적금액, 견적 상태 |
| 버튼 | 견적서 산출, 임시저장, 최종저장 (미리보기 제외) |
---
## 파일 구조
### 테스트 페이지 (새로 생성)
```
src/app/[locale]/(protected)/sales/quote-management/
├── test-new/page.tsx ← 테스트 등록 페이지
├── test/[id]/page.tsx ← 테스트 상세 페이지
└── test/[id]/edit/page.tsx ← 테스트 수정 페이지
```
### 컴포넌트 (새로 생성)
```
src/components/quotes/
├── QuoteRegistrationV2.tsx ← 메인 컴포넌트 (새 UI)
├── LocationListPanel.tsx ← 왼쪽: 발주 개소 목록 + 추가 폼
├── LocationDetailPanel.tsx ← 오른쪽: 선택 개소 상세
├── QuoteSummaryPanel.tsx ← 견적 금액 요약
├── QuoteFooterBar.tsx ← 하단 푸터 바
└── ExcelUploadButton.tsx ← 엑셀 업로드/다운로드
```
---
## 작업 체크리스트
### Phase 1: 기본 구조 설정
- [ ] 테스트 등록 페이지 생성 (test-new/page.tsx)
- [ ] 테스트 상세 페이지 생성 (test/[id]/page.tsx)
- [ ] 테스트 수정 페이지 생성 (test/[id]/edit/page.tsx)
- [ ] /dev/test-urls에 테스트 URL 추가
### Phase 2: 핵심 컴포넌트 구현
- [ ] QuoteRegistrationV2.tsx 메인 컴포넌트 생성
- [ ] LocationListPanel.tsx 발주 개소 목록 구현
- [ ] LocationDetailPanel.tsx 상세 정보 구현
- [ ] QuoteSummaryPanel.tsx 금액 요약 구현
- [ ] QuoteFooterBar.tsx 푸터 바 구현
### Phase 3: 상세 기능 구현
- [ ] 개소 선택 시 우측 상세 변경 기능
- [ ] 품목 추가 폼 기능
- [ ] 탭 전환 기능 (본체, 철골품 등)
- [ ] 품목 테이블 표시
### Phase 4: 엑셀 기능
- [ ] ExcelUploadButton.tsx 컴포넌트 생성
- [ ] 엑셀 양식 다운로드 기능
- [ ] 엑셀 업로드 및 파싱 기능
### Phase 5: 버튼 및 저장 기능
- [ ] 견적서 산출 버튼 기능
- [ ] 임시저장 버튼 기능
- [ ] 최종저장 버튼 기능
---
## 참고 사항
### 기존 파일 (수정 금지)
- `src/app/[locale]/(protected)/sales/quote-management/page.tsx` (목록)
- `src/app/[locale]/(protected)/sales/quote-management/new/page.tsx` (등록)
- `src/app/[locale]/(protected)/sales/quote-management/[id]/page.tsx` (상세)
- `src/app/[locale]/(protected)/sales/quote-management/[id]/edit/page.tsx` (수정)
- `src/components/quotes/QuoteRegistration.tsx` (기존 컴포넌트)
### 재사용 가능 파일
- `src/components/quotes/actions.ts` (API 호출)
- `src/components/quotes/QuoteDocument.tsx` (견적서 문서)
- `src/components/quotes/types.ts` (타입 정의)
### 디자인 원칙
- 내용/기능: 스크린샷 충실히 구현
- 스타일/레이아웃: 기존 프로젝트 패턴 따르기
- 색상: 주황색 헤더, 노란색 배경 등 스크린샷 참고
---
## 진행 상태
- 시작일: 2026-01-12
- 현재 상태: 계획 수립 완료

View File

@@ -58,10 +58,23 @@ http://localhost:3000/ko/hr/attendance # 🧪 모바일 출퇴근 (테스트)
| 견적관리 | `/ko/sales/quote-management` | ✅ |
| 단가관리 | `/ko/sales/pricing-management` | ✅ |
### 견적 V2 테스트 (새 UI)
| 페이지 | URL | 상태 |
|--------|-----|------|
| **견적 등록 (V2)** | `/ko/sales/quote-management/test-new` | 🧪 테스트 |
| **견적 상세 (V2)** | `/ko/sales/quote-management/test/1` | 🧪 테스트 |
| **견적 수정 (V2)** | `/ko/sales/quote-management/test/1/edit` | 🧪 테스트 |
```
http://localhost:3000/ko/sales/client-management-sales-admin
http://localhost:3000/ko/sales/quote-management
http://localhost:3000/ko/sales/pricing-management
# 견적 V2 테스트 (새 UI)
http://localhost:3000/ko/sales/quote-management/test-new # 🧪 견적 등록 V2
http://localhost:3000/ko/sales/quote-management/test/1 # 🧪 견적 상세 V2
http://localhost:3000/ko/sales/quote-management/test/1/edit # 🧪 견적 수정 V2
```
---

View File

@@ -1,5 +1,5 @@
# Juil Enterprise Test URLs
Last Updated: 2026-01-05
Last Updated: 2026-01-12
### 대시보드
| 페이지 | URL | 상태 |
@@ -7,10 +7,11 @@ Last Updated: 2026-01-05
| **메인 대시보드** | `/ko/construction/dashboard` | ✅ 완료 |
## 프로젝트 관리 (Project)
### 메인
### 프로젝트관리 (Management)
| 페이지 | URL | 상태 |
|---|---|---|
| **프로젝트 관리 메인** | `/ko/construction/project` | 🚧 구조잡기 |
| **프로젝트 관리** | `/ko/construction/project/management` | ✅ 완료 |
### 입찰관리 (Bidding)
| 페이지 | URL | 상태 |
@@ -42,25 +43,4 @@ Last Updated: 2026-01-05
| **노임관리** | `/ko/construction/order/base-info/labor` | 🆕 NEW |
## 공사 관리 (Construction)
### 인수인계 / 실측 / 발주 / 시공
| 페이지 | URL | 상태 |
|---|---|---|
| **공사 관리 메인** | `/ko/construction/construction` | 🚧 구조잡기 |
## 현장 작업 (Field)
### 할당 / 인력 / 근태 / 보고
| 페이지 | URL | 상태 |
|---|---|---|
| **현장 작업 메인** | `/ko/construction/field` | 🚧 구조잡기 |
## 기성/정산 (Finance)
### 기성 / 변경계약 / 정산
| 페이지 | URL | 상태 |
|---|---|---|
| **재무 관리 메인** | `/ko/construction/finance` | 🚧 구조잡기 |
## 시스템 (System)
### 공통
| 페이지 | URL | 상태 |
|---|---|---|
| **개발용 메뉴 목록** | `/ko/dev/juil-test-urls` | ✅ 완료 |