Merge branch 'kwshin-node' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# Cursor Agent & Skills 체계 매핑
|
||||
|
||||
## 최우선 제약: 리포트 기능 외 수정 금지
|
||||
|
||||
**모든 Agent와 Skill은 리포트 관련 파일만 수정한다.**
|
||||
|
||||
### 허용 범위
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── components/report/** # 리포트 컴포넌트
|
||||
├── app/(main)/admin/screenMng/reportList/** # 리포트 라우트
|
||||
├── contexts/ReportDesignerContext.tsx # 디자이너 상태
|
||||
├── hooks/useReportList.ts # 리포트 훅
|
||||
├── lib/api/reportApi.ts # 리포트 API
|
||||
├── types/report.ts # 리포트 타입
|
||||
└── lib/registry/components/v2-report-viewer/** # 리포트 뷰어 V2
|
||||
|
||||
backend-node/src/
|
||||
├── routes/reportRoutes.ts
|
||||
├── controllers/reportController.ts
|
||||
├── services/reportService.ts
|
||||
└── types/report.ts
|
||||
```
|
||||
|
||||
### 범위 밖 파일에서 문제 발견 시
|
||||
|
||||
수정하지 말고 **보고만** 한다. 사용자 확인 후 진행.
|
||||
|
||||
---
|
||||
|
||||
## 아키텍처
|
||||
|
||||
```
|
||||
.cursor/
|
||||
├── rules/ # 항상 적용 규칙 (8개, 자동 로드)
|
||||
├── agents/ # 전문가 역할 (4개, 자동 위임)
|
||||
├── skills/ # 워크플로우/지식 (12개, 필요 시 로드)
|
||||
└── mcp.json
|
||||
```
|
||||
|
||||
## Layer 1: Rules (항상 적용)
|
||||
|
||||
| 파일 | 용도 |
|
||||
|------|------|
|
||||
| `api-client-usage.mdc` | fetch 금지, API 클라이언트 강제 |
|
||||
| `database-guide.mdc` | PostgreSQL 쿼리 패턴 |
|
||||
| `project-overview.mdc` | 기술 스택 개요 |
|
||||
| `security-guide.mdc` | 인증/인가 |
|
||||
| `multi-tenancy-guide.mdc` | company_code 필터링 |
|
||||
| `admin-page-style-guide.mdc` | 관리자 페이지 스타일 (glob) |
|
||||
| `modal-design.mdc` | 모달 디자인 (glob) |
|
||||
| `component-development-guide.mdc` | V2 컴포넌트 상세 (요청 시) |
|
||||
|
||||
## Layer 2: Agents (전문가, 격리 컨텍스트)
|
||||
|
||||
| 에이전트 | 역할 | 자동 위임 |
|
||||
|---------|------|----------|
|
||||
| `code-reviewer` | 리포트 코드 품질/보안 검수 | Yes |
|
||||
| `debugger` | 리포트 에러 진단/수정 | Yes |
|
||||
| `pm` | 리포트 요구사항/명세서 | No |
|
||||
| `web-verifier` | 리포트 UI 스크린샷 검증 | No |
|
||||
|
||||
## Layer 3: Skills (워크플로우, 메인 컨텍스트)
|
||||
|
||||
| Skill | 용도 | 자동 호출 |
|
||||
|-------|------|----------|
|
||||
| `implement` | 리포트 4단계 구현 워크플로우 | Yes |
|
||||
| `plan` | 리포트 구현 계획서 + reportdocs 갱신 | Yes |
|
||||
| `react-component` | 리포트 컴포넌트 클린코드 | Yes |
|
||||
| `next-feature` | 리포트 Next.js 페이지/라우트 | Yes |
|
||||
| `code-review` | 리포트 코드 검수 절차 | No |
|
||||
| `code-fix` | 리포트 버그 수정 절차 | No |
|
||||
| `github` | 리포트 변경 커밋 | No |
|
||||
| `web-verify` | 리포트 UI 검증 절차 | No |
|
||||
| `ui-debugging` | 리포트 UI 레이아웃/스크롤/스타일 | Yes |
|
||||
| `component-registry` | 리포트 디자이너 컴포넌트 구조 | Yes |
|
||||
| `table-sql` | 리포트 테이블 DDL/메타데이터 | Yes |
|
||||
| `component-dev` | 리포트 V2 컴포넌트 개발 | Yes |
|
||||
| `notion-writing` | Notion MCP 작성 규칙 (블록 제약, 서식, 사용자 스타일 가이드) | Yes |
|
||||
|
||||
## 백업
|
||||
|
||||
- `cursor-rules-backup-20260309.tar.gz` (프로젝트 루트)
|
||||
- 복원: `tar xzf cursor-rules-backup-20260309.tar.gz`
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: code-reviewer
|
||||
description: WACE PLM 코드 리뷰 전문가. 코드 변경 후 품질, 보안, 멀티테넌시를 검수. 코드 리뷰 요청 시 즉시 사용. Use proactively after code modifications.
|
||||
---
|
||||
|
||||
## 수정 범위 제약 (최우선)
|
||||
|
||||
**리포트 관련 파일만 수정 허용. 그 외 파일은 절대 수정하지 않는다.**
|
||||
|
||||
허용 범위:
|
||||
- `frontend/components/report/**`
|
||||
- `frontend/app/(main)/admin/screenMng/reportList/**`
|
||||
- `frontend/contexts/ReportDesignerContext.tsx`
|
||||
- `frontend/hooks/useReportList.ts`
|
||||
- `frontend/lib/api/reportApi.ts`
|
||||
- `frontend/types/report.ts`
|
||||
- `backend-node/src/routes/reportRoutes.ts`
|
||||
- `backend-node/src/controllers/reportController.ts`
|
||||
- `backend-node/src/services/reportService.ts`
|
||||
- `backend-node/src/types/report.ts`
|
||||
|
||||
리뷰 중 허용 범위 밖 파일에서 문제를 발견하면 **수정하지 말고 보고만** 한다.
|
||||
|
||||
## 리뷰 절차
|
||||
|
||||
1. git diff로 최근 변경 확인
|
||||
2. 변경된 파일이 허용 범위 내인지 확인
|
||||
3. 체크리스트 기반 검수
|
||||
4. 우선순위별 피드백 제공
|
||||
|
||||
## 필수 검수 체크리스트
|
||||
|
||||
### 보안 / 멀티테넌시
|
||||
- [ ] SELECT/INSERT/UPDATE/DELETE에 `company_code` 필터링 적용
|
||||
- [ ] JOIN 쿼리의 ON 절에 `company_code` 매칭
|
||||
- [ ] `req.user.companyCode` 사용 (클라이언트 입력 금지)
|
||||
|
||||
### API / 프론트엔드
|
||||
- [ ] `fetch` 직접 사용 금지 → `lib/api/reportApi.ts` 사용
|
||||
- [ ] shadcn/ui 스타일 가이드 준수
|
||||
- [ ] CSS 변수 사용 (하드코딩 색상 금지)
|
||||
|
||||
### 클린코드
|
||||
- [ ] 500줄 초과 컴포넌트 없음
|
||||
- [ ] `any` 타입 남용 없음
|
||||
- [ ] 사용하지 않는 import 없음
|
||||
- [ ] interface props가 실제 전달과 일치
|
||||
|
||||
## 피드백 형식
|
||||
|
||||
- **치명적**: 반드시 수정 (보안, 빌드 실패)
|
||||
- **경고**: 수정 권장 (성능, 유지보수성)
|
||||
- **제안**: 선택적 개선
|
||||
- **범위 밖 발견**: 리포트 외 파일 문제 (수정 금지, 보고만)
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: debugger
|
||||
description: WACE PLM 디버깅 전문가. 에러, 테스트 실패, 예상치 못한 동작을 체계적으로 진단하고 수정. 오류 발생 시 자동 사용. Use proactively when encountering any issues.
|
||||
---
|
||||
|
||||
## 수정 범위 제약 (최우선)
|
||||
|
||||
**리포트 관련 파일만 수정 허용. 그 외 파일은 절대 수정하지 않는다.**
|
||||
|
||||
허용 범위:
|
||||
- `frontend/components/report/**`
|
||||
- `frontend/app/(main)/admin/screenMng/reportList/**`
|
||||
- `frontend/contexts/ReportDesignerContext.tsx`
|
||||
- `frontend/hooks/useReportList.ts`
|
||||
- `frontend/lib/api/reportApi.ts`
|
||||
- `frontend/types/report.ts`
|
||||
- `backend-node/src/routes/reportRoutes.ts`
|
||||
- `backend-node/src/controllers/reportController.ts`
|
||||
- `backend-node/src/services/reportService.ts`
|
||||
- `backend-node/src/types/report.ts`
|
||||
|
||||
에러 원인이 허용 범위 밖 파일에 있으면 **수정하지 말고 원인만 보고**한다.
|
||||
|
||||
## 진단 절차
|
||||
|
||||
1. 에러 메시지와 스택 트레이스 캡처
|
||||
2. 에러 발생 파일이 허용 범위 내인지 확인
|
||||
3. 실패 위치 격리
|
||||
4. 허용 범위 내에서 최소한의 수정 구현
|
||||
5. 수정 검증
|
||||
|
||||
## 프로젝트 특화 디버깅 포인트
|
||||
|
||||
### 리포트 프론트엔드
|
||||
- ReportDesignerContext 상태 관리 문제
|
||||
- 디자이너 컴포넌트 간 props 불일치
|
||||
- 리포트 프리뷰 렌더링 오류
|
||||
- API 클라이언트 환경별 URL 문제
|
||||
|
||||
### 리포트 백엔드
|
||||
- reportService PostgreSQL 쿼리 오류
|
||||
- company_code 필터링 누락
|
||||
- 리포트 데이터 직렬화/역직렬화 오류
|
||||
|
||||
## 출력 형식
|
||||
|
||||
각 이슈에 대해:
|
||||
- 근본 원인 설명
|
||||
- 수정 파일이 허용 범위 내인지 명시
|
||||
- 구체적 코드 수정 (허용 범위 내만)
|
||||
- 테스트 방법
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: pm
|
||||
description: WACE PLM 리포트 프로젝트 매니저. 리포트 기능 요구사항 분석, 명세서 작성, 작업 분해를 담당. 기능 기획이나 요구사항 정리가 필요할 때 사용.
|
||||
---
|
||||
|
||||
## 수정 범위 제약 (최우선)
|
||||
|
||||
**리포트 관련 기능만 기획/분석한다. 그 외 기능은 범위 밖이다.**
|
||||
|
||||
현재 프로젝트 범위:
|
||||
- Phase 1: 리포트 관리 페이지 + 디자이너 고도화
|
||||
- Phase 2: 내부 리포트 목록 (컨텍스트 뷰어)
|
||||
- Phase 3: 화면관리 컴포넌트화 (리포트 컴포넌트 삽입)
|
||||
|
||||
## 필수 참조 문서 (작업 전)
|
||||
|
||||
1. `reportdocs/STATUS.md` → 현재 진행 상태
|
||||
2. `reportdocs/PLAN.md` → 구현 계획
|
||||
3. `reportdocs/ARCHITECTURE.md` → 코드 구조
|
||||
4. `reportdocs/INDEX.md` → 기능별 파일 색인
|
||||
|
||||
## Notion 작성 규칙
|
||||
|
||||
Notion 페이지 생성/작성 시 반드시 `.cursor/skills/notion-writing/SKILL.md`를 참조한다.
|
||||
- WACE 페이지 하위에 저장
|
||||
- paragraph, bulleted_list_item만 사용 (heading, divider, code 블록 불가)
|
||||
- 마크다운 문법(##, ---, ```) 텍스트에 넣지 않음
|
||||
- bold/code annotation으로 서식 적용
|
||||
|
||||
## 역할
|
||||
|
||||
1. 리포트 기능 요구사항 분석 및 구조화
|
||||
2. 기능 명세서 작성
|
||||
3. 작업 분해 (WBS)
|
||||
4. reportdocs/ 갱신
|
||||
|
||||
## 명세서 작성 형식
|
||||
|
||||
```markdown
|
||||
# [리포트 기능명] 명세서
|
||||
|
||||
## 개요
|
||||
[기능 설명 1-2문장]
|
||||
|
||||
## 요구사항
|
||||
- FR-1: ...
|
||||
|
||||
## 영향 범위
|
||||
- 프론트엔드: components/report/ 내 파일
|
||||
- 백엔드: reportRoutes/reportController/reportService
|
||||
- DB: report_master, report_details 등
|
||||
|
||||
## 작업 분해
|
||||
1. [ ] 작업 1 (예상: Xh)
|
||||
|
||||
## 리스크
|
||||
- ...
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: web-verifier
|
||||
description: WACE PLM UI 검증 전문가. 로컬 서버에 자동 로그인 후 스크린샷으로 UI 변경사항을 검증. 화면 구현 후 시각적 확인이 필요할 때 사용.
|
||||
---
|
||||
|
||||
## 검증 절차
|
||||
1. 문제 상황 분석 및 로컬서버 상태 확인 (프론트엔드: 9771, 백엔드: 9090)
|
||||
2. 브라우저로 로그인 페이지 접속
|
||||
3. 아래 계정으로 자동 로그인
|
||||
4. 요청된 화면으로 이동으로 이동하고, 스크린샷 캡처 및 분석
|
||||
5. 요청된 문제 상황과 현재의 화면 구성 비교하고, 요구된 내용으로 수정
|
||||
6. 요구된 내용으로 수정이 되었는지 일한 페이지에서 다시 스크린샷 캡처 및 분석
|
||||
7. 결과 정리 및 반환
|
||||
|
||||
## 로그인 정보 (자동 적용)
|
||||
|
||||
- URL: http://localhost:9771
|
||||
- 아이디: wace
|
||||
- 비밀번호: qlalfqjsgh11
|
||||
|
||||
## 검증 체크리스트
|
||||
|
||||
### 리포트 목록
|
||||
- [ ] 테이블 데이터 정상 로딩
|
||||
- [ ] 생성/수정/삭제 버튼 동작
|
||||
- [ ] 검색/필터 동작
|
||||
|
||||
### 리포트 디자이너
|
||||
- [ ] 캔버스 렌더링
|
||||
- [ ] 컴포넌트 드래그&드롭
|
||||
- [ ] 속성 패널 동작
|
||||
- [ ] 프리뷰 모달
|
||||
|
||||
### 공통
|
||||
- [ ] 스크롤 정상 작동
|
||||
- [ ] 중첩 박스 없음
|
||||
- [ ] 콘솔 에러 없음
|
||||
@@ -0,0 +1,374 @@
|
||||
# 대규모 파일 모듈 분리 리팩토링 계획
|
||||
|
||||
> 작성일: 2026-03-10
|
||||
> 대상: dohyeons 작성 코드 중 대규모 파일 7개
|
||||
|
||||
---
|
||||
|
||||
## 전체 현황
|
||||
|
||||
| # | 파일 | 줄 수 | 외부 소비자 수 | 분리 난이도 |
|
||||
|---|------|-------|---------------|------------|
|
||||
| 1 | `frontend/lib/utils/buttonActions.ts` | 7,835 | 3곳 | 중 |
|
||||
| 2 | `frontend/components/screen/ScreenDesigner.tsx` | 7,572 | 2곳 | 상 |
|
||||
| 3 | `frontend/lib/registry/components/table-list/TableListComponent.tsx` | 6,815 | 3곳 | 상 |
|
||||
| 4 | `backend-node/src/services/screenManagementService.ts` | 6,614 | **1곳** | **하** |
|
||||
| 5 | `backend-node/src/services/tableManagementService.ts` | 5,346 | 3곳 | 중 |
|
||||
| 6 | `frontend/components/screen/ScreenSettingModal.tsx` | 5,108 | **1곳** | **하** |
|
||||
| 7 | `frontend/components/screen/config-panels/ButtonConfigPanel.tsx` | 4,693 | 5곳 | 상 |
|
||||
|
||||
---
|
||||
|
||||
## 1. `buttonActions.ts` (7,835줄)
|
||||
|
||||
### 현재 구조
|
||||
단일 `ButtonActionExecutor` 클래스에 20+ 핸들러 메서드가 모두 포함.
|
||||
|
||||
### 외부에서 사용하는 곳
|
||||
|
||||
| 소비자 파일 | 가져오는 심볼 |
|
||||
|------------|-------------|
|
||||
| `lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx` | `ButtonActionExecutor`, `ButtonActionContext`, `ButtonActionType`, `DEFAULT_BUTTON_ACTIONS` |
|
||||
| `lib/registry/components/button-primary/ButtonPrimaryComponent.tsx` | `ButtonActionExecutor`, `ButtonActionContext`, `ButtonActionType`, `DEFAULT_BUTTON_ACTIONS` |
|
||||
| `lib/registry/components/v2-button-primary/types.ts` | `ButtonActionConfig` |
|
||||
| `lib/registry/components/button-primary/types.ts` | `ButtonActionConfig` |
|
||||
| `components/screen/EditModal.tsx` | `ButtonActionExecutor` (동적 import) |
|
||||
|
||||
### 외부에서 호출하는 메서드 (2개만)
|
||||
- `executeAction()` ← ButtonPrimaryComponent에서 호출
|
||||
- `executeAfterSaveControl()` ← EditModal에서 호출
|
||||
|
||||
### 나머지 20+ 핸들러는 모두 내부 전용
|
||||
`handleSave`, `handleDelete`, `handleModal`, `handleControl`, `handleExcelDownload` 등은 `executeAction` 내부에서만 분기 호출됨.
|
||||
|
||||
### 분리 계획
|
||||
|
||||
```
|
||||
frontend/lib/utils/buttonActions/
|
||||
├── index.ts # 기존 export 유지 (호환성)
|
||||
├── types.ts # ButtonActionType, ButtonActionConfig, ButtonActionContext (~300줄)
|
||||
├── utils.ts # normalizeFormDataArrays, resolveSpecialKeyword (~50줄)
|
||||
├── defaults.ts # DEFAULT_BUTTON_ACTIONS (~130줄)
|
||||
├── ButtonActionExecutor.ts # executeAction 라우터 + 공통 메서드 (~500줄)
|
||||
└── handlers/
|
||||
├── saveHandler.ts # handleSave, handleSubmit, handleBatchSave (~700줄)
|
||||
├── deleteHandler.ts # handleDelete (~130줄)
|
||||
├── modalHandler.ts # handleModal, handleOpenRelatedModal (~500줄)
|
||||
├── editHandler.ts # handleEdit, handleCopy (~370줄)
|
||||
├── controlHandler.ts # handleControl (~850줄)
|
||||
├── excelHandler.ts # handleExcelDownload/Upload (~600줄)
|
||||
├── trackingHandler.ts # handleTrackingStart/Stop (~500줄)
|
||||
├── dataHandler.ts # handleTransferData, handleSwapFields, handleQuickInsert (~500줄)
|
||||
├── operationHandler.ts # handleOperationControl (~320줄)
|
||||
├── specialHandler.ts # handleBarcodeScan, handleCodeMerge, handleEvent (~300줄)
|
||||
└── rackHandler.ts # handleRackStructureBatchSave 등 (~400줄)
|
||||
```
|
||||
|
||||
### 영향 범위
|
||||
- `index.ts`에서 기존 심볼 re-export → **외부 코드 변경 0건**
|
||||
- 내부 핸들러 분리는 외부에 영향 없음
|
||||
|
||||
---
|
||||
|
||||
## 2. `ScreenDesigner.tsx` (7,572줄)
|
||||
|
||||
### 현재 구조
|
||||
상태 50+개, 이벤트 핸들러 30+개, JSX 1,700줄이 단일 함수 컴포넌트에 포함.
|
||||
|
||||
### 외부에서 사용하는 곳 (2곳만)
|
||||
|
||||
| 소비자 파일 | 전달 props |
|
||||
|------------|-----------|
|
||||
| `app/(main)/admin/screenMng/screenMngList/page.tsx` | `selectedScreen`, `onBackToList`, `onScreenUpdate` |
|
||||
| `components/screen/ScreenSettingModal.tsx` | `selectedScreen`, `onBackToList` |
|
||||
|
||||
### Props 인터페이스
|
||||
```typescript
|
||||
interface ScreenDesignerProps {
|
||||
selectedScreen: ScreenDefinition | null;
|
||||
onBackToList: () => void;
|
||||
onScreenUpdate?: (updatedScreen: Partial<ScreenDefinition>) => void;
|
||||
isPop?: boolean;
|
||||
defaultDevicePreview?: "mobile" | "tablet";
|
||||
}
|
||||
```
|
||||
|
||||
### ScreenDesigner가 의존하는 것들
|
||||
|
||||
| 카테고리 | 주요 의존성 |
|
||||
|---------|-----------|
|
||||
| UI 컴포넌트 (15+) | `SlimToolbar`, `ComponentsPanel`, `PropertiesPanel`, `LayerManagerPanel`, `FlowButtonGroup`, `FlowButtonGroupDialog`, `MenuAssignmentModal` 등 |
|
||||
| 유틸 (10+) | `gridUtils`, `alignmentUtils`, `groupingUtils`, `flowButtonGroupUtils`, `webTypeMapping`, `layoutV2Converter` 등 |
|
||||
| API (4) | `screenApi`, `tableTypeApi`, `tableManagementApi`, `ExternalRestApiConnectionAPI` |
|
||||
| 타입 (10+) | `ScreenDefinition`, `ComponentData`, `LayoutData`, `GridSettings` 등 |
|
||||
| Context (3) | `LayerProvider`, `ScreenPreviewProvider`, `TableOptionsProvider` |
|
||||
|
||||
### 분리 계획
|
||||
|
||||
```
|
||||
frontend/components/screen/screen-designer/
|
||||
├── index.ts # export default ScreenDesigner (호환성)
|
||||
├── ScreenDesigner.tsx # 메인 (조합만, ~800줄)
|
||||
├── types.ts # ScreenDesignerProps
|
||||
├── constants.ts # panelConfigs
|
||||
│
|
||||
├── hooks/
|
||||
│ ├── useDesignerState.ts # 50+ useState 묶음 (~200줄)
|
||||
│ ├── useLayoutLoader.ts # loadLayout, loadScreenDataSource (~400줄)
|
||||
│ ├── useLayoutHistory.ts # saveToHistory, undo, redo (~150줄)
|
||||
│ ├── useComponentProperty.ts # updateComponentProperty (~300줄)
|
||||
│ ├── usePanZoom.ts # Pan/Zoom/Grid (~250줄)
|
||||
│ ├── useDesignerKeyboard.ts # 키보드 단축키 (~500줄)
|
||||
│ ├── useAlignmentHandlers.ts # 정렬/배분/크기맞춤 (~200줄)
|
||||
│ ├── useClipboard.ts # copy, paste, delete (~400줄)
|
||||
│ ├── useDragHandlers.ts # startDrag, updateDrag, endDrag (~400줄)
|
||||
│ └── useDropHandlers.ts # handleDrop 통합 (~1,000줄)
|
||||
│
|
||||
├── components/
|
||||
│ ├── DesignerCanvas.tsx # 캔버스 영역 (~400줄)
|
||||
│ ├── DesignerPropertiesPanel.tsx # 속성 패널 분기 (~600줄)
|
||||
│ ├── FlowButtonGroupPanel.tsx # 플로우 버튼 그룹 UI (~200줄)
|
||||
│ ├── ActiveLayerIndicator.tsx # 레이어 인디케이터 (~50줄)
|
||||
│ └── DesignerModals.tsx # 모달 묶음 (~200줄)
|
||||
│
|
||||
└── utils/
|
||||
├── gridSnapUtils.ts # snapTo10px, calculateGridInfo (~50줄)
|
||||
├── webTypeDefaults.ts # getDefaultWebTypeConfig (~50줄)
|
||||
└── fileComponentRestore.ts # restoreFileComponentsData (~100줄)
|
||||
```
|
||||
|
||||
### 영향 범위
|
||||
- `screen-designer/index.ts`에서 `export default ScreenDesigner` → **외부 import 변경 필요**
|
||||
- 변경 대상: `screenMngList/page.tsx`, `ScreenSettingModal.tsx` (2곳만)
|
||||
- 또는 기존 `ScreenDesigner.tsx`를 re-export 래퍼로 남겨두면 **변경 0건**
|
||||
|
||||
---
|
||||
|
||||
## 3. `TableListComponent.tsx` (6,815줄)
|
||||
|
||||
### 현재 구조
|
||||
데이터 fetch, 필터링, 인라인 편집, WebSocket, Excel/PDF 내보내기가 모두 한 컴포넌트에 포함.
|
||||
|
||||
### 외부에서 사용하는 곳
|
||||
|
||||
| 소비자 파일 | 가져오는 심볼 |
|
||||
|------------|-------------|
|
||||
| `table-list/TableListRenderer.tsx` | `TableListComponent` |
|
||||
| `table-list/index.ts` | `TableListWrapper` |
|
||||
| `components/v2/V2List.tsx` | `TableListComponent` |
|
||||
|
||||
### 분리 계획
|
||||
|
||||
```
|
||||
frontend/lib/registry/components/table-list/
|
||||
├── TableListComponent.tsx # 메인 (조합, ~800줄)
|
||||
├── types.ts # 인터페이스, 상수 (~200줄)
|
||||
│
|
||||
├── hooks/
|
||||
│ ├── useTableData.ts # fetch, 페이지네이션, 정렬 (~500줄)
|
||||
│ ├── useTableFilters.ts # 헤더 필터, 고급 검색 (~400줄)
|
||||
│ ├── useTableSelection.ts # 행 선택 (~200줄)
|
||||
│ ├── useTableEditing.ts # 인라인 편집 (~300줄)
|
||||
│ ├── useTableState.ts # 컬럼 순서/너비 저장 (~200줄)
|
||||
│ ├── useTableWebSocket.ts # WebSocket (~150줄)
|
||||
│ └── useTableExport.ts # Excel/PDF (~200줄)
|
||||
│
|
||||
├── components/
|
||||
│ ├── TableHeader.tsx # 헤더 렌더링 (~300줄)
|
||||
│ ├── TableBody.tsx # 바디 렌더링 (~400줄)
|
||||
│ ├── TableContextMenu.tsx # 우클릭 메뉴 (~150줄)
|
||||
│ ├── FilterPanel.tsx # 필터 패널 (~200줄)
|
||||
│ └── ColumnOptionsPanel.tsx # 컬럼 옵션 (~200줄)
|
||||
│
|
||||
└── utils/
|
||||
├── formatCellValue.ts # 셀 값 포맷팅 (~100줄)
|
||||
└── filterUtils.ts # 필터 조건 평가 (~100줄)
|
||||
```
|
||||
|
||||
### 영향 범위
|
||||
- `TableListComponent`, `TableListWrapper` export 유지 → **외부 변경 0건**
|
||||
|
||||
---
|
||||
|
||||
## 4. `screenManagementService.ts` (6,614줄) - 소비자 1곳
|
||||
|
||||
### 외부에서 사용하는 곳
|
||||
|
||||
| 소비자 파일 | 가져오는 심볼 |
|
||||
|------------|-------------|
|
||||
| `controllers/screenManagementController.ts` | `screenManagementService` (싱글톤 인스턴스) |
|
||||
|
||||
### 분리 계획
|
||||
|
||||
```
|
||||
backend-node/src/services/screen/
|
||||
├── index.ts # screenManagementService 싱글톤 re-export
|
||||
├── ScreenManagementService.ts # 클래스 정의 + 메서드 위임 (~300줄)
|
||||
├── screenCrudService.ts # createScreen, getScreen*, updateScreen* (~600줄)
|
||||
├── screenDeletionService.ts # delete, restore, permanentDelete, bulk* (~800줄)
|
||||
├── screenLayoutService.ts # saveLayout, getLayout (~600줄)
|
||||
├── screenMenuService.ts # assignScreenToMenu, getScreensByMenu (~300줄)
|
||||
├── screenTemplateService.ts # getTemplatesByCompany, createTemplate (~200줄)
|
||||
├── screenColumnService.ts # getColumnInfo, setColumnWebType, generateWidget (~400줄)
|
||||
├── screenCodeGenerator.ts # generateScreenCode (~200줄)
|
||||
└── screenTableService.ts # getTables, getTableInfo, getTableColumns (~400줄)
|
||||
```
|
||||
|
||||
### 영향 범위
|
||||
- `index.ts`에서 `screenManagementService` re-export → **컨트롤러 변경 0건**
|
||||
- **소비자 1곳** → 가장 안전한 리팩토링 대상
|
||||
|
||||
---
|
||||
|
||||
## 5. `tableManagementService.ts` (5,346줄)
|
||||
|
||||
### 외부에서 사용하는 곳
|
||||
|
||||
| 소비자 파일 | 가져오는 심볼 |
|
||||
|------------|-------------|
|
||||
| `controllers/tableManagementController.ts` | `TableManagementService` |
|
||||
| `controllers/entityJoinController.ts` | `TableManagementService` |
|
||||
| `services/multiConnectionQueryService.ts` | `TableManagementService` |
|
||||
|
||||
### 분리 계획
|
||||
|
||||
```
|
||||
backend-node/src/services/table/
|
||||
├── index.ts # TableManagementService re-export
|
||||
├── TableManagementService.ts # 클래스 정의 + 메서드 위임 (~300줄)
|
||||
├── tableMasterService.ts # getTableList, getTableLabels (~400줄)
|
||||
├── columnSettingsService.ts # getColumnList, updateColumnSettings (~800줄)
|
||||
├── tableDataService.ts # getTableData, addTableData, editTableData, deleteTableData (~800줄)
|
||||
├── tableEntityJoinService.ts # getTableDataWithEntityJoins (~1,000줄)
|
||||
├── tableValidationService.ts # validateNotNull, validateUnique (~200줄)
|
||||
├── tableLogService.ts # createLogTable, getLogConfig, toggleLogTable (~400줄)
|
||||
└── tableSchemaService.ts # getTableSchema, checkTableExists (~400줄)
|
||||
```
|
||||
|
||||
### 영향 범위
|
||||
- `index.ts`에서 `TableManagementService` re-export → **외부 변경 0건**
|
||||
|
||||
---
|
||||
|
||||
## 6. `ScreenSettingModal.tsx` (5,108줄) - 소비자 1곳
|
||||
|
||||
### 현재 구조
|
||||
4개 탭 컴포넌트(`OverviewTab`, `FieldMappingTab`, `DataFlowTab`, `ControlManagementTab`)와 서브 컴포넌트(`SearchableSelect`, `TableColumnAccordion`, `JoinSettingEditor`)가 모두 인라인 정의.
|
||||
|
||||
### 외부에서 사용하는 곳
|
||||
|
||||
| 소비자 파일 | 가져오는 심볼 |
|
||||
|------------|-------------|
|
||||
| `components/screen/ScreenRelationFlow.tsx` | `ScreenSettingModal` |
|
||||
|
||||
### 분리 계획
|
||||
|
||||
```
|
||||
frontend/components/screen/screen-setting/
|
||||
├── index.ts # ScreenSettingModal re-export
|
||||
├── ScreenSettingModal.tsx # 메인 모달 셸 (~500줄)
|
||||
├── hooks/
|
||||
│ └── useScreenSettingData.ts # loadData, dataFlows (~300줄)
|
||||
├── components/
|
||||
│ ├── SearchableSelect.tsx # 검색 가능 셀렉트 (~60줄)
|
||||
│ ├── TableColumnAccordion.tsx # 컬럼 아코디언 (~500줄)
|
||||
│ └── JoinSettingEditor.tsx # 조인 설정 에디터 (~200줄)
|
||||
└── tabs/
|
||||
├── OverviewTab.tsx # 개요 탭 (~900줄)
|
||||
├── FieldMappingTab.tsx # 필드 매핑 탭 (~400줄)
|
||||
├── DataFlowTab.tsx # 데이터 플로우 탭 (~300줄)
|
||||
└── ControlManagementTab.tsx # 버튼 제어 탭 (~2,000줄)
|
||||
```
|
||||
|
||||
### 영향 범위
|
||||
- `index.ts`에서 re-export → **외부 변경 0건**
|
||||
- **소비자 1곳** → 안전한 리팩토링 대상
|
||||
|
||||
---
|
||||
|
||||
## 7. `ButtonConfigPanel.tsx` (4,693줄) - 소비자 5곳
|
||||
|
||||
### 현재 구조
|
||||
액션 타입별 설정 UI가 조건부 렌더링으로 3,200줄, 인라인 엑셀 설정 컴포넌트 4개가 포함.
|
||||
|
||||
### 외부에서 사용하는 곳
|
||||
|
||||
| 소비자 파일 | 가져오는 심볼 | import 방식 |
|
||||
|------------|-------------|------------|
|
||||
| `lib/registry/init.ts` | `ButtonConfigPanel` | 정적 import |
|
||||
| `lib/utils/getConfigPanelComponent.tsx` | `OriginalButtonConfigPanel` | 정적 import (별칭) |
|
||||
| `lib/utils/getComponentConfigPanel.tsx` | `ButtonConfigPanel` | 동적 import |
|
||||
| `components/screen/panels/V2PropertiesPanel.tsx` | `ButtonConfigPanel` | 정적 import |
|
||||
| `components/screen/panels/DetailSettingsPanel.tsx` | `NewButtonConfigPanel` | 정적 import (별칭) |
|
||||
|
||||
### 분리 계획
|
||||
|
||||
```
|
||||
frontend/components/screen/config-panels/button-config/
|
||||
├── index.ts # ButtonConfigPanel re-export
|
||||
├── ButtonConfigPanel.tsx # 메인 (액션 타입별 라우팅, ~500줄)
|
||||
├── hooks/
|
||||
│ ├── useButtonConfigState.ts # 50+ useState (~200줄)
|
||||
│ └── useButtonConfigData.ts # loadTableColumns, filterScreens (~300줄)
|
||||
├── sections/
|
||||
│ ├── SaveActionConfig.tsx # 저장 액션 설정 (~400줄)
|
||||
│ ├── ModalActionConfig.tsx # 모달 액션 설정 (~400줄)
|
||||
│ ├── NavigateActionConfig.tsx # 네비게이션 설정 (~300줄)
|
||||
│ ├── EditActionConfig.tsx # 편집 설정 (~200줄)
|
||||
│ ├── ControlActionConfig.tsx # 제어 설정 (~300줄)
|
||||
│ └── ExcelActionConfig.tsx # 엑셀 설정 (~500줄)
|
||||
└── components/
|
||||
├── MasterDetailExcelUploadConfig.tsx (~340줄)
|
||||
├── ExcelNumberingRuleInfo.tsx (~15줄)
|
||||
├── ExcelAfterUploadControlConfig.tsx (~155줄)
|
||||
└── ExcelUploadConfigSection.tsx (~160줄)
|
||||
```
|
||||
|
||||
### 영향 범위
|
||||
- `button-config/index.ts`에서 `ButtonConfigPanel` re-export
|
||||
- 기존 `ButtonConfigPanel.tsx` 파일 경로가 바뀌므로 **5곳 import 경로 수정 필요**
|
||||
- 또는 기존 위치에 re-export 래퍼를 남겨두면 **변경 0건**
|
||||
|
||||
---
|
||||
|
||||
## 선택적 실행 가이드
|
||||
|
||||
### 안전도 기준 (소비자 수 기반)
|
||||
|
||||
| 안전도 | 파일 | 소비자 | 비고 |
|
||||
|--------|------|--------|------|
|
||||
| **매우 안전** | `screenManagementService.ts` | 1곳 | 컨트롤러 1개만 사용 |
|
||||
| **매우 안전** | `ScreenSettingModal.tsx` | 1곳 | ScreenRelationFlow만 사용 |
|
||||
| **안전** | `ScreenDesigner.tsx` | 2곳 | 페이지 1 + 모달 1 |
|
||||
| **안전** | `buttonActions.ts` | 3곳 | 버튼 컴포넌트 2 + EditModal 1 |
|
||||
| **안전** | `TableListComponent.tsx` | 3곳 | 렌더러 + index + V2List |
|
||||
| **안전** | `tableManagementService.ts` | 3곳 | 컨트롤러 2 + 서비스 1 |
|
||||
| **주의** | `ButtonConfigPanel.tsx` | 5곳 | 정적 3 + 동적 1 + 별칭 1 |
|
||||
|
||||
### 독립 실행 가능 여부
|
||||
|
||||
각 파일은 **서로 의존하지 않으므로** 원하는 것만 선택적으로 진행 가능합니다.
|
||||
|
||||
| 파일 | 다른 대상 파일과의 의존성 | 독립 실행 |
|
||||
|------|------------------------|----------|
|
||||
| `buttonActions.ts` | 없음 | 가능 |
|
||||
| `ScreenDesigner.tsx` | 없음 | 가능 |
|
||||
| `TableListComponent.tsx` | 없음 | 가능 |
|
||||
| `screenManagementService.ts` | 없음 | 가능 |
|
||||
| `tableManagementService.ts` | 없음 | 가능 |
|
||||
| `ScreenSettingModal.tsx` | ScreenDesigner를 import하지만 분리와 무관 | 가능 |
|
||||
| `ButtonConfigPanel.tsx` | 없음 | 가능 |
|
||||
|
||||
---
|
||||
|
||||
## 권장 실행 순서 (효과 대비 안전도)
|
||||
|
||||
| 순서 | 파일 | 이유 |
|
||||
|------|------|------|
|
||||
| 1 | `screenManagementService.ts` | 소비자 1곳, 백엔드라 UI 영향 없음 |
|
||||
| 2 | `ScreenSettingModal.tsx` | 소비자 1곳, 탭 분리라 구조 명확 |
|
||||
| 3 | `buttonActions.ts` | 핸들러별 분리라 패턴 명확, 외부 변경 0건 |
|
||||
| 4 | `ScreenDesigner.tsx` | 가장 효과 큼 (7,572줄), 소비자 2곳 |
|
||||
| 5 | `tableManagementService.ts` | 백엔드, 패턴이 #1과 동일 |
|
||||
| 6 | `TableListComponent.tsx` | 훅 추출 복잡도 높음 |
|
||||
| 7 | `ButtonConfigPanel.tsx` | 소비자 5곳, import 경로 관리 필요 |
|
||||
@@ -0,0 +1,568 @@
|
||||
# 리포트 컴포넌트화 (Phase 3 확장) — 실행 계획서
|
||||
|
||||
> **작성일**: 2026-03-10 | **최종 업데이트**: 2026-03-10 (v5)
|
||||
> **목표**: 리포트 디자이너에서 만든 리포트를 화면관리의 V2 컴포넌트(`v2-report-viewer`)에서 **reportId를 직접 지정**하여 배치하고, 인라인 또는 모달로 렌더링하는 것.
|
||||
|
||||
### 참조 문서
|
||||
|
||||
| 순서 | 문서 | 경로 | 반영 내용 |
|
||||
|------|------|------|----------|
|
||||
| 1 | V2 컴포넌트 분석 가이드 | `docs/V2_컴포넌트_분석_가이드.md` | 파일 구조, `v2-` 접두사, Definition 네이밍 |
|
||||
| 2 | V2 컴포넌트 연동 가이드 | `docs/V2_컴포넌트_연동_가이드.md` | ScreenContext, V2 이벤트 시스템, formData 공유 |
|
||||
| 3 | 화면개발 표준 가이드 | `docs/screen-implementation-guide/화면개발_표준_가이드.md` | V2 컴포넌트 목록, screen_layouts_v2 저장 구조 |
|
||||
| 4 | CLAUDE.md | `CLAUDE.md` | 네이밍 규칙, 표준 파일 구조, 코딩 규칙 |
|
||||
|
||||
---
|
||||
|
||||
## 완성 후 동작 플로우
|
||||
|
||||
### 플로우 A: 관리자가 화면에 리포트를 배치하는 과정 (설정 시점)
|
||||
|
||||
```
|
||||
[1] 관리자가 화면 디자이너에 접속
|
||||
URL: /admin/screenMng/screenMngList
|
||||
→ 화면 목록에서 "견적 관리" 화면을 더블클릭
|
||||
→ 화면 디자이너 진입 (URL 변화 없음, ScreenDesigner.tsx 렌더링)
|
||||
|
||||
[2] 좌측 컴포넌트 패널에서 "리포트 뷰어" (v2-report-viewer)를 캔버스에 드래그&드롭
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ [좌측 패널] [캔버스] [우측 패널] │
|
||||
│ │
|
||||
│ ▸ 입력 ┌────────────────────────┐ │
|
||||
│ ▸ 버튼 │ v2-input: 주문번호 │ │
|
||||
│ ▸ 테이블 ├────────────────────────┤ │
|
||||
│ ▸ 표시 │ v2-table-list │ │
|
||||
│ ├ 리포트 뷰어 ◀ │ (주문 목록 테이블) │ │
|
||||
│ ├ 텍스트 ├────────────────────────┤ │
|
||||
│ └ ... │ ┌──────────────────┐ │ │
|
||||
│ │ │ 📄 리포트 (뷰어) │ │ ← 방금 배치 │
|
||||
│ │ │ │ │ │
|
||||
│ │ └──────────────────┘ │ │
|
||||
│ └────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
[3] 배치한 리포트 뷰어 컴포넌트를 클릭 → 우측 설정 패널이 열림
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 리포트 뷰어 설정 │
|
||||
│ │
|
||||
│ 컴포넌트 제목 │
|
||||
│ [견적서 미리보기] ___________________ │
|
||||
│ │
|
||||
│ 리포트 선택 │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ 리포트를 선택해주세요 [선택] │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 표시 모드 │
|
||||
│ [모달 (클릭 시 팝업) ▼] │
|
||||
│ │
|
||||
│ 파라미터 매핑 │
|
||||
│ 매핑 없음 — 폼 데이터가 자동 주입됩니다 │
|
||||
│ [+ 추가] │
|
||||
└─────────────────────────────────────────────┘
|
||||
|
||||
[4] "선택" 버튼 클릭 → 리포트 선택 모달이 열림
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ 리포트 선택 [×] │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ 🔍 견적... │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────┬──────────────┬──────────┬─────────────┐ │
|
||||
│ │ ID │ 리포트명 │ 유형 │ 사용여부 │ │
|
||||
│ ├──────┼──────────────┼──────────┼─────────────┤ │
|
||||
│ │ 12 │ ▶ 견적서 ◀ │ 견적 │ Y │ ← 클릭!
|
||||
│ │ 15 │ 발주서 │ 발주 │ Y │ │
|
||||
│ │ 18 │ 검수 보고서 │ 검사 │ Y │ │
|
||||
│ └──────┴──────────────┴──────────┴─────────────┘ │
|
||||
│ │
|
||||
│ * 리포트 관리에서 만든 리포트 목록이 표시됩니다 │
|
||||
│ * 리포트 관리 URL: /admin/screenMng/reportList │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
|
||||
[5] "견적서" 행 클릭 → 모달 닫히고 설정 패널에 선택 결과 표시
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 리포트 뷰어 설정 │
|
||||
│ │
|
||||
│ 컴포넌트 제목 │
|
||||
│ [견적서 미리보기] ___________________ │
|
||||
│ │
|
||||
│ 리포트 선택 │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ 📄 견적서 [변경] [×] │ │
|
||||
│ │ ID: 12 | 유형: 견적 │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 표시 모드 │
|
||||
│ [인라인 (화면 내 직접 표시) ▼] │ ← "인라인"으로 변경
|
||||
│ │
|
||||
│ 파라미터 매핑 │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ $1 ← order_no [×] │ │ ← 매핑 추가
|
||||
│ │ [+ 추가] │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ 쿼리의 $1에 폼 데이터의 order_no 값 전달 │
|
||||
└─────────────────────────────────────────────┘
|
||||
|
||||
[6] 화면 저장 → screen_layouts_v2 테이블에 JSONB로 저장됨
|
||||
저장되는 componentConfig:
|
||||
{
|
||||
"title": "견적서 미리보기",
|
||||
"reportId": 12,
|
||||
"reportName": "견적서",
|
||||
"displayMode": "inline",
|
||||
"paramMappings": [{ "param": "$1", "formField": "order_no" }]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 플로우 B: 사용자가 화면에서 리포트를 보는 과정 (실행 시점 — 인라인 모드)
|
||||
|
||||
```
|
||||
[1] 사용자가 "견적 관리" 화면에 접속
|
||||
URL: /screens/45?menuObjid=789
|
||||
→ DynamicComponentRenderer가 screen_layouts_v2에서 레이아웃 로드
|
||||
→ v2-report-viewer 컴포넌트 렌더링 시작
|
||||
|
||||
[2] 화면 초기 상태 (formData에 order_no 없음)
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 견적 관리 │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ 주문번호: [_______________] [조회] │ │
|
||||
│ ├────────────────────────────────────────────────────────┤ │
|
||||
│ │ 주문 목록 테이블 │ │
|
||||
│ │ ┌──────┬──────────┬──────────┬──────────┐ │ │
|
||||
│ │ │ 번호 │ 주문번호 │ 고객명 │ 금액 │ │ │
|
||||
│ │ │ │ │ │ │ │ │
|
||||
│ │ │ (데이터 없음) │ │ │
|
||||
│ │ └──────┴──────────┴──────────┴──────────┘ │ │
|
||||
│ ├────────────────────────────────────────────────────────┤ │
|
||||
│ │ 견적서 미리보기 [↻ 새로고침] [↗ 전체보기] │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ 파라미터가 없어 리포트를 표시할 수 없습니다 │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
[3] 사용자가 주문번호 입력 후 조회 → 테이블에 데이터 표시 → 행 선택
|
||||
→ formData가 변경됨: { order_no: "ORD-2026-001", ... }
|
||||
→ v2-report-viewer가 ScreenContext.formData 변경 감지
|
||||
→ buildContextParams: { "$1": "ORD-2026-001" }
|
||||
→ useReportExecution 훅이 즉시 쿼리 실행
|
||||
→ reportApi.executeQuery(12, queryId, { "$1": "ORD-2026-001" })
|
||||
|
||||
[4] 리포트가 인라인으로 렌더링됨 (축소 표시)
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 견적 관리 │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ 주문번호: [ORD-2026-001] [조회] │ │
|
||||
│ ├────────────────────────────────────────────────────────┤ │
|
||||
│ │ 주문 목록 테이블 │ │
|
||||
│ │ ┌──────┬──────────────┬──────────┬──────────┐ │ │
|
||||
│ │ │ 번호 │ 주문번호 │ 고객명 │ 금액 │ │ │
|
||||
│ │ │ 1 │ ORD-2026-001 │ (주)가나 │ 1,500만 │ ← 선택 │ │
|
||||
│ │ │ 2 │ ORD-2026-002 │ (주)다라 │ 800만 │ │ │
|
||||
│ │ └──────┴──────────────┴──────────┴──────────┘ │ │
|
||||
│ ├────────────────────────────────────────────────────────┤ │
|
||||
│ │ 견적서 미리보기 [↻ 새로고침] [↗ 전체보기] │ │
|
||||
│ │ ┌──────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ ╔══════════════════════════════════════════╗ │ │ │
|
||||
│ │ │ ║ 견 적 서 ║ │ │ │
|
||||
│ │ │ ║──────────────────────────────────────────║ │ │ │
|
||||
│ │ │ ║ 수신: (주)가나 ║ │ │ │
|
||||
│ │ │ ║ 주문번호: ORD-2026-001 ║ │ │ │
|
||||
│ │ │ ║ ┌────┬──────────┬────┬──────────┐ ║ │ │ │
|
||||
│ │ │ ║ │ No │ 품목명 │ 수량│ 단가 │ ║ │ │ │
|
||||
│ │ │ ║ │ 1 │ 부품A │ 100│ 50,000 │ ║ │ │ │
|
||||
│ │ │ ║ │ 2 │ 부품B │ 50 │ 100,000 │ ║ │ │ │
|
||||
│ │ │ ║ └────┴──────────┴────┴──────────┘ ║ │ │ │
|
||||
│ │ │ ║ 합계: 15,000,000원 ║ │ │ │
|
||||
│ │ │ ╚══════════════════════════════════════════╝ │ │ │
|
||||
│ │ └──────────────────────────────────────────────────┘ │ │
|
||||
│ │ * 컴포넌트 크기에 맞게 축소(scale) 렌더링 │ │
|
||||
│ │ * 클릭하면 전체 보기 모달 열림 │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
[5] "전체보기" 버튼 또는 인라인 리포트 클릭 → 모달로 전체 크기 표시
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 견적서 — ORD-2026-001 [PDF] [×] │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ (A4 크기 리포트 전체 표시) │ │
|
||||
│ │ ReportListPreviewModal 사용 │ │
|
||||
│ │ (기존 모달 그대로) │ │
|
||||
│ │ │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ 페이지: [< 1 / 1 >] │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 플로우 C: 모달 모드로 설정한 경우 (실행 시점 — 모달 모드)
|
||||
|
||||
```
|
||||
[1] 관리자가 설정 패널에서 displayMode = "모달" 선택 + reportId = 12 (견적서) 설정
|
||||
|
||||
[2] 사용자가 화면 접속 시 → 리포트 이름 + "보기" 버튼만 표시
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 견적서 미리보기 │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ 📄 견적서 [보기] │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
|
||||
[3] "보기" 버튼 클릭 → ReportListPreviewModal 모달 열림 (기존과 동일)
|
||||
→ formData의 order_no 값이 $1 파라미터로 자동 바인딩
|
||||
→ 모달 안에서 리포트 렌더링 + PDF 다운로드 가능
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 플로우 D: reportId 미지정 시 (하위 호환 — 기존 menuObjid 기반)
|
||||
|
||||
```
|
||||
[1] 관리자가 설정 패널에서 reportId를 선택하지 않음 (또는 선택 해제)
|
||||
|
||||
[2] 사용자가 /screens/45?menuObjid=789 로 접속
|
||||
→ menuObjid=789에 연결된 리포트 목록 자동 조회
|
||||
→ 기존과 동일하게 리포트 버튼 목록 표시
|
||||
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 리포트 │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ 📄 견적서 │ │
|
||||
│ │ 📄 발주서 │ │
|
||||
│ │ 📄 거래명세서 │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
|
||||
[3] 버튼 클릭 → 해당 리포트의 ReportListPreviewModal 모달 열림
|
||||
(현재 동작과 100% 동일)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 데이터 흐름 요약
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
|
||||
│ 화면 디자이너 │ │ 화면 뷰어 │ │ 백엔드 API │
|
||||
│ (설정 시점) │ │ (실행 시점) │ │ │
|
||||
├─────────────────┤ ├──────────────────┤ ├─────────────────────┤
|
||||
│ │ │ │ │ │
|
||||
│ ConfigPanel │ │ ReportViewer │ │ GET /admin/reports │
|
||||
│ ↓ reportId │ 저장 │ Component │ │ → 리포트 목록 │
|
||||
│ ↓ displayMode │ ──→ │ ↓ │ │ │
|
||||
│ ↓ paramMappings │ ↓ config 로드 │ │ GET /admin/reports │
|
||||
│ │ │ ↓ │ │ /:reportId │
|
||||
│ ReportSelect │ │ reportId 있음? │ │ → 리포트 상세 │
|
||||
│ Modal │ │ ├─ Yes │ │ │
|
||||
│ ↓ │ │ │ displayMode?│ │ POST /admin/reports │
|
||||
│ reportApi │ │ │ ├─ inline │ │ /:reportId/queries│
|
||||
│ .getReports() │ │ │ │ → Inline │ │ /:queryId/execute │
|
||||
│ │ │ │ │ Renderer │ ──→ │ → 쿼리 실행 │
|
||||
│ │ │ │ └─ modal │ │ params: { $1: ... }│
|
||||
│ │ │ │ → 버튼 │ │ │
|
||||
│ │ │ │ → 클릭시 │ │ │
|
||||
│ │ │ │ 모달 │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ └─ No (fallback) │ GET /admin/reports │
|
||||
│ │ │ → menuObjid │ │ /by-menu/:objid │
|
||||
│ │ │ 기반 목록 │ │ → 메뉴별 리포트 │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ formData 변경 │ │ │
|
||||
│ │ │ → 즉시 재실행 │ │ │
|
||||
└─────────────────┘ └──────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 관련 URL 정리
|
||||
|
||||
| 화면 | URL | 역할 |
|
||||
|------|-----|------|
|
||||
| 화면 목록 (관리자) | `/admin/screenMng/screenMngList` | 화면 목록 + 더블클릭 시 디자이너 진입 |
|
||||
| 화면 디자이너 (관리자) | (URL 변화 없음, 같은 페이지 내 ScreenDesigner 렌더링) | 컴포넌트 배치 + 설정 |
|
||||
| 리포트 관리 (관리자) | `/admin/screenMng/reportList` | 리포트 CRUD + 디자이너 진입 |
|
||||
| 리포트 디자이너 (관리자) | `/admin/screenMng/reportList/designer/{reportId}` | SQL + 레이아웃 + 바인딩 설정 |
|
||||
| 화면 뷰어 (사용자) | `/screens/{screenId}?menuObjid={menuObjid}` | 실제 화면 사용 |
|
||||
| POP 화면 뷰어 (사용자) | `/pop/screens/{screenId}` | POP 화면 사용 |
|
||||
|
||||
---
|
||||
|
||||
## 확정된 결정 사항
|
||||
|
||||
| # | 결정 항목 | 확정 내용 |
|
||||
|---|----------|----------|
|
||||
| 1 | **리포트 선택 방식** | **reportId 직접 지정** — 설정 패널에서 "리포트 선택" 버튼 → 리포트 목록 모달 → 선택하면 reportId 저장. menuObjid 기반은 fallback으로 유지 |
|
||||
| 2 | **표시 모드** | **모달 + 인라인 선택 가능** — 설정에서 displayMode 선택 (modal / inline) |
|
||||
| 3 | **파라미터 바인딩** | **단순 키만** — `formData['order_no']` 같은 1단계 키만 지원 (현재 방식 유지) |
|
||||
| 4 | **자동 갱신** | **즉시 자동 갱신** — formData 변경 시 즉시 리포트 재실행 |
|
||||
| 5 | **버그 수정 범위** | **screens + pop/screens 모두 수정** — menuObjid 미전달 버그 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 현재 코드 현황
|
||||
|
||||
### v2-report-viewer 현재 파일 구조
|
||||
|
||||
```
|
||||
frontend/lib/registry/components/v2-report-viewer/
|
||||
├── index.ts # V2ReportViewerDefinition (28줄)
|
||||
├── types.ts # ReportViewerConfig, ReportParamMapping (18줄)
|
||||
├── ReportViewerComponent.tsx # 메인 컴포넌트 (133줄)
|
||||
├── ReportViewerConfigPanel.tsx # 설정 패널 (115줄)
|
||||
└── ReportViewerRenderer.tsx # ComponentRegistry 등록 (12줄)
|
||||
```
|
||||
|
||||
### 현재 문제점
|
||||
|
||||
| 문제 | 원인 |
|
||||
|------|------|
|
||||
| reportId를 직접 지정할 수 없음 | 설정 패널에 리포트 선택 UI 없음 |
|
||||
| menuObjid 없으면 아무것도 안 보임 | reportId fallback 없음 |
|
||||
| 인라인 렌더링 불가 | 모달만 지원 |
|
||||
| formData 변경 시 자동 갱신 없음 | 감지 로직 없음 |
|
||||
| `/screens/` 페이지에서 menuObjid 전달 안 됨 | ScreenContextProvider에 props 미전달 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 구현 단계 (7 Steps)
|
||||
|
||||
### Step 1: menuObjid 미전달 버그 수정 [난이도: 낮음]
|
||||
|
||||
**수정 파일 (2개):**
|
||||
|
||||
| 파일 | 현재 | 변경 |
|
||||
|------|------|------|
|
||||
| `frontend/app/(main)/screens/[screenId]/page.tsx` | `<ScreenContextProvider>` (props 없음, 1377행) | Wrapper에서 `useSearchParams`로 `menuObjid` 파싱 후 전달 |
|
||||
| `frontend/app/(pop)/pop/screens/[screenId]/page.tsx` | `<ScreenContextProvider>` (props 없음, 348행) | 동일 |
|
||||
|
||||
**검증:**
|
||||
- [ ] `/screens/{screenId}?menuObjid=123` 접속 → `screenContext.menuObjid === 123` 확인
|
||||
|
||||
---
|
||||
|
||||
### Step 2: ReportViewerConfig 타입 확장 [난이도: 낮음]
|
||||
|
||||
**수정 파일:** `frontend/lib/registry/components/v2-report-viewer/types.ts`
|
||||
|
||||
```typescript
|
||||
export interface ReportViewerConfig extends ComponentConfig {
|
||||
title?: string;
|
||||
paramMappings?: ReportParamMapping[];
|
||||
|
||||
reportId?: number; // 리포트 목록 모달에서 선택한 리포트 ID
|
||||
reportName?: string; // 선택한 리포트명 (설정 패널 표시용)
|
||||
displayMode?: "modal" | "inline"; // 기본: "modal"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3: ConfigPanel에 리포트 선택 UI 추가 [난이도: 중간]
|
||||
|
||||
**수정 파일:** `frontend/lib/registry/components/v2-report-viewer/ReportViewerConfigPanel.tsx`
|
||||
|
||||
**추가되는 UI 섹션:**
|
||||
1. 리포트 선택 영역 (선택 버튼 → 리포트 목록 모달 → 선택 결과 표시 + 해제 버튼)
|
||||
2. 표시 모드 Select (모달 / 인라인)
|
||||
|
||||
**리포트 선택 모달:** `reportApi.getReports({ limit: 100, useYn: 'Y' })` → 검색 + 테이블 → 행 클릭 시 선택 완료.
|
||||
|
||||
---
|
||||
|
||||
### Step 4: ReportInlineRenderer + useReportExecution 추출 [난이도: 높음]
|
||||
|
||||
**신규 파일 (2개):**
|
||||
|
||||
| 파일 | 역할 | 위치 근거 |
|
||||
|------|------|----------|
|
||||
| `frontend/hooks/useReportExecution.ts` | 리포트 로드 + 쿼리 실행 공용 훅 (~120줄) | CLAUDE.md 네이밍 규칙: 훅은 `frontend/hooks/`에 배치. `ReportListPreviewModal`과 `ReportInlineRenderer` 양쪽에서 공유하므로 특정 컴포넌트 폴더가 아닌 공용 위치가 적합 |
|
||||
| `frontend/components/report/ReportInlineRenderer.tsx` | 모달 없이 인라인 렌더링 (~200줄) | `ReportListPreviewModal`과 동일 레벨에 배치. v2-report-viewer 전용이 아니라 향후 다른 컨텍스트(예: 대시보드 위젯)에서도 재사용 가능한 범용 렌더러이므로 `components/report/`가 적합 |
|
||||
|
||||
**useReportExecution:** `ReportListPreviewModal`에서 리포트 로드 + 쿼리 실행 로직을 추출한 공용 훅.
|
||||
|
||||
**ReportInlineRenderer:** `useReportExecution` 훅 사용 + ResizeObserver로 scale 축소 렌더링 + 첫 페이지만 표시.
|
||||
|
||||
---
|
||||
|
||||
### Step 5: ReportViewerComponent에 reportId 직접 지정 + displayMode 분기 [난이도: 중간]
|
||||
|
||||
**수정 파일:** `frontend/lib/registry/components/v2-report-viewer/ReportViewerComponent.tsx`
|
||||
|
||||
**렌더링 분기:**
|
||||
|
||||
```
|
||||
config.reportId 있음:
|
||||
├─ displayMode === "inline" → ReportInlineRenderer + 헤더(제목, 새로고침, 전체보기)
|
||||
└─ displayMode === "modal" → 리포트명 + "보기" 버튼 → 클릭 시 모달
|
||||
|
||||
config.reportId 없음 (fallback):
|
||||
→ menuObjid 기반 리포트 목록 (기존 동작 100% 유지)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 6: formData 변경 시 즉시 자동 갱신 [난이도: 중간]
|
||||
|
||||
**수정 파일:** `ReportViewerComponent.tsx`
|
||||
|
||||
`ScreenContext.formData` 변경 감지 → `contextParams` 재계산 → `refreshKey` 증가 → `ReportInlineRenderer`에 전달하여 즉시 재실행.
|
||||
|
||||
---
|
||||
|
||||
### Step 7: 통합 테스트 + 가이드 문서 업데이트 [난이도: 낮음]
|
||||
|
||||
**검증 시나리오:** 플로우 A~D 전체 검증 + `npx tsc --noEmit` 오류 없음.
|
||||
|
||||
**가이드 문서 업데이트:**
|
||||
- [ ] `docs/V2_컴포넌트_분석_가이드.md` — V2 컴포넌트 목록에 `v2-report-viewer` 추가 (18개 → 19개)
|
||||
- [ ] `docs/V2_컴포넌트_연동_가이드.md` — 6.1 연동 능력 매트릭스에 `v2-report-viewer` 행 추가
|
||||
- [ ] `docs/screen-implementation-guide/화면개발_표준_가이드.md` — V2 컴포넌트 목록에 `v2-report-viewer` 추가 (23개 → 24개)
|
||||
|
||||
---
|
||||
|
||||
## 3. 파일 변경 요약
|
||||
|
||||
### 수정 파일 (5개)
|
||||
|
||||
| 파일 | Step | 핵심 변경 |
|
||||
|------|------|-----------|
|
||||
| `frontend/app/(main)/screens/[screenId]/page.tsx` | 1 | ScreenContextProvider에 menuObjid 전달 |
|
||||
| `frontend/app/(pop)/pop/screens/[screenId]/page.tsx` | 1 | 동일 |
|
||||
| `frontend/lib/registry/components/v2-report-viewer/types.ts` | 2 | `reportId`, `reportName`, `displayMode` 필드 추가 |
|
||||
| `frontend/lib/registry/components/v2-report-viewer/ReportViewerConfigPanel.tsx` | 3 | 리포트 선택 모달 + displayMode Select |
|
||||
| `frontend/lib/registry/components/v2-report-viewer/ReportViewerComponent.tsx` | 5,6 | reportId 분기 + displayMode 분기 + 자동 갱신 |
|
||||
|
||||
### 신규 파일 (2개)
|
||||
|
||||
| 파일 | Step | 역할 |
|
||||
|------|------|------|
|
||||
| `frontend/hooks/useReportExecution.ts` | 4 | 리포트 로드 + 쿼리 실행 공용 훅 |
|
||||
| `frontend/components/report/ReportInlineRenderer.tsx` | 4 | 모달 없이 인라인 리포트 렌더링 |
|
||||
|
||||
### 수정 대상 (리팩토링, 선택적)
|
||||
|
||||
| 파일 | Step | 변경 |
|
||||
|------|------|------|
|
||||
| `frontend/components/report/ReportListPreviewModal.tsx` | 4 | 내부 로드/실행 로직을 `useReportExecution`으로 교체 (기능 변경 없음) |
|
||||
|
||||
### 가이드 문서 업데이트 (3개)
|
||||
|
||||
| 파일 | Step | 변경 |
|
||||
|------|------|------|
|
||||
| `docs/V2_컴포넌트_분석_가이드.md` | 7 | V2 컴포넌트 목록에 `v2-report-viewer` 추가 |
|
||||
| `docs/V2_컴포넌트_연동_가이드.md` | 7 | 연동 능력 매트릭스에 `v2-report-viewer` 행 추가 |
|
||||
| `docs/screen-implementation-guide/화면개발_표준_가이드.md` | 7 | V2 컴포넌트 목록에 `v2-report-viewer` 추가 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 구현 순서 및 의존성
|
||||
|
||||
```
|
||||
Step 1 menuObjid 버그 수정 ─────────────────── (독립)
|
||||
↓
|
||||
Step 2 types.ts 확장 ───────────────────────── (Step 3, 5의 기반)
|
||||
↓
|
||||
Step 3 ConfigPanel 리포트 선택 UI ──────────── (Step 2 의존)
|
||||
↓
|
||||
Step 4 useReportExecution + ReportInlineRenderer (가장 큰 작업)
|
||||
↓
|
||||
Step 5 ReportViewerComponent 분기 렌더링 ───── (Step 2, 4 의존)
|
||||
↓
|
||||
Step 6 즉시 자동 갱신 ──────────────────────── (Step 5 의존)
|
||||
↓
|
||||
Step 7 통합 테스트 + 가이드 문서 업데이트
|
||||
```
|
||||
|
||||
**병렬 가능:**
|
||||
- Step 1 + Step 2: 동시 진행 가능
|
||||
- Step 3 + Step 4: Step 2 완료 후 동시 진행 가능
|
||||
|
||||
---
|
||||
|
||||
## 5. V2 이벤트 시스템과의 관계
|
||||
|
||||
`V2_컴포넌트_연동_가이드.md`에서 정의한 V2 표준 이벤트 시스템(`V2_EVENTS`, `dispatchV2Event`, `subscribeV2Event`)과의 관계를 정리합니다.
|
||||
|
||||
### 현재 v2-report-viewer가 사용하지 않는 이유
|
||||
|
||||
| V2 이벤트 | 사용 여부 | 이유 |
|
||||
|-----------|----------|------|
|
||||
| `tableListDataChange` | 구독 안 함 | 리포트 뷰어는 테이블 데이터 변경이 아닌 `ScreenContext.formData`를 통해 파라미터를 받음 |
|
||||
| `beforeFormSave` / `afterFormSave` | 구독 안 함 | 리포트 뷰어는 데이터를 저장하지 않음 (읽기 전용 표시 컴포넌트) |
|
||||
| `refreshTable` | 구독 안 함 | 리포트 갱신은 `refreshKey` prop으로 처리. 테이블 갱신 이벤트와는 무관 |
|
||||
| `componentDataTransfer` | 구독 안 함 | 리포트 뷰어는 DataReceivable이 아님 (데이터를 수신하여 편집하는 컴포넌트가 아님) |
|
||||
|
||||
### formData 공유 방식
|
||||
|
||||
`v2-report-viewer`는 `ScreenContext`의 `formData`를 통해 다른 컴포넌트와 통신합니다:
|
||||
|
||||
```
|
||||
v2-input (order_no 입력)
|
||||
→ ScreenContext.formData 업데이트
|
||||
→ v2-report-viewer가 formData 변경 감지
|
||||
→ buildContextParams로 쿼리 파라미터 생성
|
||||
→ useReportExecution으로 쿼리 실행
|
||||
```
|
||||
|
||||
이 방식은 `V2_컴포넌트_연동_가이드.md` 4.3절 ScreenContext의 `formData` 공유 패턴과 일치합니다.
|
||||
|
||||
### 향후 확장 시 이벤트 도입 가능성
|
||||
|
||||
리포트 실행 완료 후 다른 컴포넌트에 알림이 필요한 경우(예: 리포트 로드 완료 시 집계 위젯 갱신), V2 이벤트를 추가할 수 있습니다. 현재 Phase에서는 불필요합니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 충돌 사전 검사 대상
|
||||
|
||||
구현 시작 전 아래 이름들이 현재 코드베이스에 **0건**인지 Grep 확인 필수:
|
||||
|
||||
```
|
||||
ReportInlineRenderer, useReportExecution,
|
||||
ReportSelectModal, displayMode (v2-report-viewer 내),
|
||||
reportName (v2-report-viewer/types.ts 내)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 주의사항
|
||||
|
||||
1. **하위 호환 필수**: 모든 신규 필드는 optional. `reportId` 없으면 기존 menuObjid 기반 동작 그대로 유지.
|
||||
2. **reportId 타입**: `ReportMaster.report_id`는 `string`이지만 실제 값은 숫자 문자열. API 호출 시 `String(reportId)`로 변환.
|
||||
3. **멀티테넌시**: `reportApi.getReports()` 호출 시 백엔드에서 자동으로 company_code 필터링됨.
|
||||
4. **디자인 모드 보호**: `isDesignMode`일 때 API 호출, 자동 갱신 모두 스킵.
|
||||
5. **ReportListPreviewModal 수정 최소화**: 기존 모달은 그대로 유지. 공통 로직만 훅으로 추출.
|
||||
6. **인라인 렌더링 스케일**: `ResizeObserver`로 컨테이너 크기 감지 → `transform: scale(containerWidth / canvasWidth)`.
|
||||
7. **V2 컴포넌트 규칙 준수**: `v2-` 접두사, `V2ReportViewerDefinition` 네이밍, `screen_layouts_v2` JSONB 저장.
|
||||
8. **각 Step 완료 시 필수**: `cd frontend && npx tsc --noEmit`
|
||||
|
||||
---
|
||||
|
||||
## 8. 핵심 원칙
|
||||
|
||||
| 역할 | 담당 |
|
||||
|------|------|
|
||||
| SQL 작성, 컴포넌트 레이아웃, queryId+field 연결, 숫자 포맷/합계 | **리포트 디자이너** (기존, 수정 없음) |
|
||||
| 어떤 리포트를 보여줄지 (reportId), 언제 실행할지 (자동 갱신), 어디에 표시할지 (displayMode) | **화면관리 v2-report-viewer** (이번 구현) |
|
||||
|
||||
리포트 디자이너의 코드는 이번 작업에서 **수정하지 않는다**.
|
||||
|
||||
---
|
||||
|
||||
## 9. 연동 능력 매트릭스 (Step 7에서 가이드 문서에 추가할 내용)
|
||||
|
||||
| 컴포넌트 | 이벤트 발행 | 이벤트 구독 | DataProvider | DataReceiver | Context 사용 |
|
||||
|----------|:-----------:|:-----------:|:------------:|:------------:|:------------:|
|
||||
| `v2-report-viewer` | - | - | - | - | Screen (formData, menuObjid) |
|
||||
|
||||
| 소스 컴포넌트 | 타겟 컴포넌트 | 연동 방식 | 용도 |
|
||||
|--------------|--------------|----------|------|
|
||||
| `v2-input` / `v2-table-list` | `v2-report-viewer` | ScreenContext.formData | 파라미터 바인딩 |
|
||||
| `v2-report-viewer` | `ReportListPreviewModal` | props (report, contextParams) | 전체 보기 모달 |
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
description: 모달(Dialog) 컴포넌트 구현 시 WACE 디자인 시스템 적용
|
||||
globs: **/*.tsx
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
모달 컴포넌트 구현 시 반드시 @design-system.md 의 패턴을 따를 것.
|
||||
|
||||
## 핵심 규칙 요약
|
||||
|
||||
1. **Shell**: `DialogContent`에 `p-0 [&>button]:hidden flex flex-col h-[80vh] overflow-hidden` 필수
|
||||
2. **접근성**: `<DialogTitle className="sr-only">`, `<DialogDescription className="sr-only">` 반드시 포함
|
||||
3. **헤더**: `px-6 py-4 border-b` + 아이콘(`w-4 h-4 text-blue-600`) + 제목(`text-base font-semibold`) + X 닫기 버튼
|
||||
4. **탭**: shadcn `<Tabs>` 사용 금지 → `@design-system.md` Section 2의 커스텀 버튼 패턴 사용
|
||||
5. **콘텐츠**: `flex-1 overflow-y-auto px-6 py-4`
|
||||
6. **Footer**: `px-6 py-4 border-t flex justify-end gap-2` + 취소(`outline`) + 저장(`bg-blue-600`)
|
||||
7. **폼 필드**: Label `text-xs font-medium` + Input/Select `h-9 text-sm`, 그룹 간격 `space-y-3`
|
||||
8. **섹션**: 강조 섹션 `bg-teal-50 border-teal-200 rounded-xl`, 일반 섹션 `bg-white border-border rounded-xl`
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# 웹 검증 로그인 정보
|
||||
|
||||
웹 검증(web-verify), 브라우저 테스트, UI 확인 등 로컬 서버에 접속이 필요한 모든 작업에서 아래 정보를 사용해야 합니다.
|
||||
|
||||
## 로컬 서버 정보
|
||||
|
||||
- 프론트엔드: http://localhost:9771
|
||||
- 백엔드: http://localhost:8080 (API 베이스: http://localhost:8080/api)
|
||||
|
||||
## 로그인 계정 (필수)
|
||||
|
||||
- 아이디: wace
|
||||
- 비밀번호: qlalfqjsgh11
|
||||
|
||||
## 주의사항
|
||||
|
||||
- `admin / admin123` 등 임의의 계정을 사용하지 마세요.
|
||||
- 서브에이전트(web-verifier 등)에 작업을 위임할 때도 반드시 위 계정 정보를 prompt에 포함시켜야 합니다.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: code-fix
|
||||
description: 리포트 코드 문제 수정 워크플로우. 리포트 관련 버그, 에러를 진단하고 수정. 에러 수정 요청 시 적용.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 리포트 코드 문제 수정 워크플로우
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
리포트 관련 파일만 수정. 원인이 리포트 밖에 있으면 보고만.
|
||||
|
||||
## 진단 절차
|
||||
|
||||
1. 에러 메시지 분석
|
||||
2. 에러 파일이 리포트 범위 내인지 확인
|
||||
3. 근본 원인 파악
|
||||
4. 리포트 범위 내에서 최소한의 수정
|
||||
5. 린트/타입 검사로 검증
|
||||
|
||||
## 리포트 특화 에러 패턴
|
||||
|
||||
| 에러 | 원인 | 해결 |
|
||||
|------|------|------|
|
||||
| 디자이너 렌더링 실패 | Context 상태 불일치 | ReportDesignerContext 확인 |
|
||||
| 프리뷰 빈 화면 | 데이터 직렬화 오류 | report.ts 타입 확인 |
|
||||
| API 404 | 라우트 미등록 | reportRoutes.ts 확인 |
|
||||
| company_code 누락 | 서비스 필터링 빠짐 | reportService.ts 확인 |
|
||||
|
||||
## 수정 후 검증
|
||||
|
||||
```bash
|
||||
cd frontend && npx tsc --noEmit
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: code-review
|
||||
description: 리포트 코드 검수 워크플로우. 리포트 관련 코드 변경 검토 시 사용.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 리포트 코드 검수 워크플로우
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
리포트 관련 파일 변경만 검수. 범위 밖 파일 문제는 보고만.
|
||||
|
||||
## 절차
|
||||
|
||||
1. `git diff`로 변경 확인
|
||||
2. 변경 파일이 리포트 범위 내인지 확인
|
||||
3. 체크리스트 기반 검수
|
||||
4. 피드백 작성
|
||||
|
||||
## 검수 체크리스트
|
||||
|
||||
### 필수
|
||||
- [ ] 멀티테넌시: company_code 필터링
|
||||
- [ ] API: `reportApi.ts` 클라이언트 사용
|
||||
- [ ] 타입: `npx tsc --noEmit` 통과
|
||||
- [ ] 리포트 밖 파일 수정 없음
|
||||
|
||||
### 권장
|
||||
- [ ] 컴포넌트 500줄 이하
|
||||
- [ ] any 타입 미사용
|
||||
- [ ] console.log 잔류 없음
|
||||
|
||||
## 피드백 형식
|
||||
|
||||
```markdown
|
||||
## 코드 리뷰 결과
|
||||
|
||||
### 치명적 (반드시 수정)
|
||||
- [파일:라인] 설명
|
||||
|
||||
### 범위 밖 발견 (수정 금지, 보고만)
|
||||
- [파일] 설명
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: component-dev
|
||||
description: 리포트 뷰어 V2 컴포넌트 개발 가이드. v2-report-viewer 등 리포트 관련 V2 컴포넌트 개발 시 사용.
|
||||
---
|
||||
|
||||
# 리포트 V2 컴포넌트 개발 가이드
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
리포트 관련 V2 컴포넌트만 수정:
|
||||
- `v2-report-viewer/`
|
||||
- 리포트 연동 컴포넌트
|
||||
|
||||
다른 V2 컴포넌트(`v2-table-list`, `v2-input` 등)는 수정하지 않는다.
|
||||
|
||||
## V2 컴포넌트 핵심 규칙
|
||||
|
||||
- `v2-` 접두사 필수 (원본 폴더 수정 금지)
|
||||
- 저장: `component_url + overrides` (차이값만)
|
||||
- Zod 스키마에 `.passthrough()` 필수
|
||||
- `isDesignMode` 체크하여 API 호출 스킵
|
||||
- `beforeFormSave` 이벤트로 느슨한 결합
|
||||
- `autoFilter`로 멀티테넌시 필터링
|
||||
|
||||
## 표준 파일 구조
|
||||
|
||||
```
|
||||
frontend/lib/registry/components/v2-report-viewer/
|
||||
├── index.ts
|
||||
├── ReportViewerRenderer.tsx
|
||||
├── ReportViewerComponent.tsx
|
||||
├── ReportViewerConfigPanel.tsx
|
||||
└── types.ts
|
||||
```
|
||||
|
||||
## 표준 Props
|
||||
|
||||
```typescript
|
||||
interface StandardComponentProps {
|
||||
component: ComponentData;
|
||||
isDesignMode?: boolean;
|
||||
formData?: Record<string, any>;
|
||||
onFormDataChange?: (fieldName: string, value: any) => void;
|
||||
companyCode?: string;
|
||||
refreshKey?: number;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: component-registry
|
||||
description: 리포트 디자이너 컴포넌트 가이드. 리포트 디자이너 내 컴포넌트 구조, 추가/수정 방법. 리포트 디자이너 컴포넌트 작업 시 사용.
|
||||
---
|
||||
|
||||
# 리포트 디자이너 컴포넌트 가이드
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
`frontend/components/report/designer/` 내 파일만 수정.
|
||||
화면 빌더의 일반 컴포넌트 레지스트리(`lib/registry/components/`)는 수정하지 않는다.
|
||||
|
||||
## 리포트 디자이너 컴포넌트 구조
|
||||
|
||||
```
|
||||
frontend/components/report/designer/
|
||||
├── ReportDesignerCanvas.tsx # 캔버스
|
||||
├── ReportDesignerToolbar.tsx # 툴바
|
||||
├── ReportComponentPalette.tsx # 컴포넌트 팔레트
|
||||
├── properties/ # 속성 패널
|
||||
│ ├── TextProperties.tsx
|
||||
│ ├── ImageProperties.tsx
|
||||
│ ├── TableProperties.tsx
|
||||
│ ├── CardProperties.tsx
|
||||
│ └── PageNumberProperties.tsx
|
||||
└── modals/ # 설정 모달
|
||||
├── ComponentSettingsModal.tsx
|
||||
├── SettingsModalShell.tsx
|
||||
├── TextLayoutTabs.tsx
|
||||
├── ImageLayoutTabs.tsx
|
||||
├── TableLayoutTabs.tsx
|
||||
└── GridCellDropZone.tsx
|
||||
```
|
||||
|
||||
## 컴포넌트 추가 시 절차
|
||||
|
||||
1. `types/report.ts`에 새 컴포넌트 타입 추가
|
||||
2. `designer/properties/`에 속성 패널 생성
|
||||
3. `designer/modals/`에 설정 모달 생성 (필요 시)
|
||||
4. `ReportDesignerCanvas.tsx`에 렌더링 로직 추가
|
||||
5. `ReportComponentPalette.tsx`에 팔레트 항목 추가
|
||||
|
||||
## 전역 상태
|
||||
|
||||
`frontend/contexts/ReportDesignerContext.tsx`로 관리:
|
||||
- 선택된 컴포넌트
|
||||
- 캔버스 상태
|
||||
- 저장/불러오기
|
||||
@@ -0,0 +1,96 @@
|
||||
# TableListComponent 상세 참조
|
||||
|
||||
## 주요 상태 (State)
|
||||
|
||||
```typescript
|
||||
// 데이터
|
||||
const [tableData, setTableData] = useState<any[]>([]);
|
||||
const [filteredData, setFilteredData] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 편집
|
||||
const [editingCell, setEditingCell] = useState<{
|
||||
rowIndex: number; colIndex: number; columnName: string; originalValue: any;
|
||||
} | null>(null);
|
||||
const [pendingChanges, setPendingChanges] = useState<Map<string, Map<string, any>>>(new Map());
|
||||
const [validationErrors, setValidationErrors] = useState<Map<string, Map<string, string>>>(new Map());
|
||||
|
||||
// 필터
|
||||
const [headerFilters, setHeaderFilters] = useState<Map<string, Set<string>>>(new Map());
|
||||
const [filterGroups, setFilterGroups] = useState<FilterGroup[]>([]);
|
||||
const [globalSearchText, setGlobalSearchText] = useState("");
|
||||
|
||||
// 컬럼
|
||||
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
|
||||
const [columnOrder, setColumnOrder] = useState<string[]>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<Record<string, boolean>>({});
|
||||
|
||||
// 선택/정렬/페이지네이션
|
||||
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
|
||||
const [sortBy, setSortBy] = useState<string>("");
|
||||
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
```
|
||||
|
||||
## 타입 정의
|
||||
|
||||
```typescript
|
||||
type ValidationRule = {
|
||||
required?: boolean;
|
||||
min?: number; max?: number;
|
||||
minLength?: number; maxLength?: number;
|
||||
pattern?: RegExp;
|
||||
customMessage?: string;
|
||||
validate?: (value: any, row: any) => string | null;
|
||||
};
|
||||
|
||||
interface FilterCondition {
|
||||
id: string; column: string;
|
||||
operator: "equals" | "notEquals" | "contains" | "notContains" |
|
||||
"startsWith" | "endsWith" | "greaterThan" | "lessThan" |
|
||||
"greaterOrEqual" | "lessOrEqual" | "isEmpty" | "isNotEmpty";
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface FilterGroup { id: string; logic: "AND" | "OR"; conditions: FilterCondition[]; }
|
||||
|
||||
interface TableState {
|
||||
columnWidths: Record<string, number>;
|
||||
columnOrder: string[];
|
||||
sortBy: string; sortOrder: "asc" | "desc";
|
||||
frozenColumns: string[];
|
||||
columnVisibility: Record<string, boolean>;
|
||||
}
|
||||
```
|
||||
|
||||
## 캐싱 전략
|
||||
|
||||
```typescript
|
||||
const tableColumnCache = new Map<string, { columns: any[]; timestamp: number }>();
|
||||
const TABLE_CACHE_TTL = 5 * 60 * 1000; // 5분
|
||||
```
|
||||
|
||||
## 키보드 네비게이션
|
||||
|
||||
| 키 | 동작 |
|
||||
|---|---|
|
||||
| Arrow Keys | 셀 이동 |
|
||||
| Tab/Shift+Tab | 다음/이전 셀 |
|
||||
| F2 | 편집 모드 |
|
||||
| Enter | 저장 후 아래로 |
|
||||
| Escape | 편집 취소 |
|
||||
| Ctrl+C | 복사 |
|
||||
| Delete | 셀 값 삭제 |
|
||||
|
||||
## 필수 Import
|
||||
|
||||
```typescript
|
||||
import React, { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
||||
import { TableListConfig, ColumnConfig } from "./types";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import { codeCache } from "@/lib/caching/codeCache";
|
||||
import * as XLSX from "xlsx";
|
||||
import { toast } from "sonner";
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: github
|
||||
description: Git 작업 워크플로우. 리포트 관련 변경사항 커밋 시 사용.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Git 작업 워크플로우
|
||||
|
||||
## 수정 범위 확인
|
||||
|
||||
커밋 전 `git diff`로 리포트 관련 파일만 변경되었는지 확인.
|
||||
리포트 밖 파일이 변경되어 있으면 **사용자에게 확인** 후 진행.
|
||||
|
||||
## 커밋 메시지 형식
|
||||
|
||||
```
|
||||
type(report): 설명
|
||||
```
|
||||
|
||||
타입:
|
||||
- `feat(report)`: 리포트 새 기능
|
||||
- `fix(report)`: 리포트 버그 수정
|
||||
- `refactor(report)`: 리포트 리팩토링
|
||||
- `style(report)`: 리포트 스타일 변경
|
||||
- `docs(report)`: 리포트 문서
|
||||
|
||||
## 커밋 절차
|
||||
|
||||
1. `git status`로 변경 확인
|
||||
2. 리포트 밖 파일 변경 여부 체크
|
||||
3. `git add`로 리포트 관련 파일만 스테이징
|
||||
4. Conventional Commit 형식으로 커밋
|
||||
|
||||
## 주의사항
|
||||
|
||||
- `git push --force` 금지
|
||||
- `git commit --amend` 주의 (push 전에만)
|
||||
- git config 수정 금지
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: implement
|
||||
description: 리포트 기능 4단계 구현 워크플로우. 조사→정리→구현→통합 단계로 리포트 관련 기능을 체계적으로 구현. 리포트 기능 구현 요청 시 사용.
|
||||
---
|
||||
|
||||
# 리포트 기능 4단계 구현 워크플로우
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
리포트 관련 파일만 수정. 그 외 파일은 절대 수정하지 않는다.
|
||||
허용: `components/report/**`, `reportRoutes`, `reportController`, `reportService`, `reportApi.ts`, `report.ts` 등
|
||||
|
||||
## 단계 1: 조사 (Explore)
|
||||
|
||||
Task tool의 `explore` subagent를 사용:
|
||||
- 리포트 관련 파일 구조 파악 (`reportdocs/INDEX.md` 참조)
|
||||
- 기존 디자이너 컴포넌트 패턴 분석
|
||||
- 영향 범위가 리포트 밖으로 나가지 않는지 확인
|
||||
|
||||
## 단계 2: 정리 (Plan)
|
||||
|
||||
- 구현 계획 수립 (리포트 파일만 변경 목록)
|
||||
- 인터페이스/타입 설계 (`types/report.ts`)
|
||||
- API 엔드포인트 설계 (`reportRoutes.ts`)
|
||||
- `reportdocs/STATUS.md` 갱신
|
||||
|
||||
## 단계 3: 구현 (Implement)
|
||||
|
||||
- 타입 → 백엔드 → 프론트엔드 순서
|
||||
- 각 파일 완료 시 린트 확인
|
||||
- 리포트 밖 파일 수정 필요 시 **중단하고 사용자에게 확인**
|
||||
|
||||
## 단계 4: 통합 (Integrate)
|
||||
|
||||
- `npx tsc --noEmit`으로 타입 검사
|
||||
- import 정합성 확인
|
||||
- 멀티테넌시 체크리스트 검증
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: next-feature
|
||||
description: 리포트 관련 Next.js 15 App Router 기능 구현 워크플로우. 리포트 페이지, API 라우트 구현 시 사용.
|
||||
---
|
||||
|
||||
# 리포트 Next.js 기능 구현 워크플로우
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
리포트 관련 라우트만 수정. 다른 페이지는 수정하지 않는다.
|
||||
|
||||
허용 라우트:
|
||||
- `app/(main)/admin/screenMng/reportList/page.tsx`
|
||||
- `app/(main)/admin/screenMng/reportList/designer/[reportId]/page.tsx`
|
||||
|
||||
## 프로젝트 구조 (리포트 관련)
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── app/(main)/admin/screenMng/reportList/
|
||||
│ ├── page.tsx # 리포트 목록
|
||||
│ └── designer/[reportId]/page.tsx # 리포트 디자이너
|
||||
├── components/report/ # 리포트 컴포넌트
|
||||
├── lib/api/reportApi.ts # 리포트 API 클라이언트
|
||||
├── hooks/useReportList.ts # 리포트 훅
|
||||
└── types/report.ts # 리포트 타입
|
||||
```
|
||||
|
||||
## API 호출 규칙
|
||||
|
||||
```typescript
|
||||
import { getReportList, createReport } from "@/lib/api/reportApi";
|
||||
```
|
||||
|
||||
환경별 URL 자동 처리:
|
||||
- `v1.vexplor.com` → `api.vexplor.com`
|
||||
- `localhost:9771` → `localhost:8080`
|
||||
@@ -0,0 +1,572 @@
|
||||
---
|
||||
name: notion-writing
|
||||
description: Notion MCP로 페이지를 작성할 때 반드시 따라야 하는 규칙. Notion 페이지 생성, 콘텐츠 작성, 문서화 요청 시 자동 적용.
|
||||
---
|
||||
|
||||
# Notion 작성 규칙
|
||||
|
||||
## 저장 위치
|
||||
|
||||
모든 페이지는 WACE 페이지 하위에 생성한다.
|
||||
|
||||
```
|
||||
WACE 페이지 ID: 31e2a200-9533-80ac-9fcf-d4ad3c676929
|
||||
```
|
||||
|
||||
## MCP 서버 정보
|
||||
|
||||
- 서버명: `project-0-ERP-node-notion`
|
||||
- 주요 API: `API-post-search`, `API-post-page`, `API-patch-block-children`, `API-retrieve-a-page`
|
||||
|
||||
## MCP 지원 블록 타입
|
||||
|
||||
`API-patch-block-children`은 다음 6가지 블록 타입을 지원한다.
|
||||
|
||||
| 블록 타입 | 용도 |
|
||||
|-----------|------|
|
||||
| `paragraph` | 일반 텍스트, 핵심 요약(📌), 경고(⚠), 빈 줄 |
|
||||
| `heading_2` | 대섹션 제목 (## H2) |
|
||||
| `heading_3` | 소제목 (### H3) |
|
||||
| `divider` | 섹션 구분선 |
|
||||
| `code` | 코드 블록, **Mermaid 다이어그램** |
|
||||
| `bulleted_list_item` | 불릿 리스트 |
|
||||
|
||||
## 마크다운 문법 사용 금지
|
||||
|
||||
Notion API는 마크다운을 자동 변환하지 않는다. 텍스트에 마크다운을 넣으면 그대로 문자열로 표시된다.
|
||||
|
||||
금지:
|
||||
- `## 제목` → 그대로 "## 제목"으로 표시됨
|
||||
- `---` → 그대로 "---"로 표시됨
|
||||
- `` ``` `` → 그대로 백틱 문자로 표시됨
|
||||
- `> 인용` → 그대로 "> 인용"으로 표시됨
|
||||
|
||||
---
|
||||
|
||||
## 계층 페이지 생성 패턴
|
||||
|
||||
### 작업 순서 (필수)
|
||||
|
||||
1. `API-post-search`로 대상 페이지/데이터베이스 검색
|
||||
2. 검색 결과에서 `object` 필드로 `page`인지 `database`인지 구분
|
||||
3. `API-post-page`로 상위 페이지 생성 (반환된 ID 기록)
|
||||
4. 생성된 페이지 ID를 parent로 하위 페이지 생성
|
||||
5. `API-patch-block-children`으로 각 페이지에 콘텐츠 추가
|
||||
|
||||
### 검색 (API-post-search)
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "검색할 제목",
|
||||
"page_size": 10
|
||||
}
|
||||
```
|
||||
|
||||
검색 결과에서 `object` 필드 확인:
|
||||
- `"object": "page"` → page_id parent 사용
|
||||
- `"object": "database"` → database_id parent 사용
|
||||
|
||||
### 데이터베이스(피드보기) 하위에 페이지 생성
|
||||
|
||||
DB의 title 속성명을 키로 사용한다 (보통 `"이름"`).
|
||||
|
||||
```json
|
||||
{
|
||||
"parent": {"database_id": "<DB-ID>"},
|
||||
"properties": {"이름": {"title": [{"text": {"content": "페이지 제목"}}]}},
|
||||
"icon": "{\"type\": \"emoji\", \"emoji\": \"📘\"}"
|
||||
}
|
||||
```
|
||||
|
||||
### 페이지 하위에 서브 페이지 생성
|
||||
|
||||
properties 키는 `"title"`을 사용한다 (DB 하위와 다름에 주의).
|
||||
|
||||
```json
|
||||
{
|
||||
"parent": {"page_id": "<PAGE-ID>"},
|
||||
"properties": {"title": {"title": [{"text": {"content": "서브 페이지 제목"}}]}},
|
||||
"icon": "{\"type\": \"emoji\", \"emoji\": \"📦\"}"
|
||||
}
|
||||
```
|
||||
|
||||
### parent 유형별 차이 요약
|
||||
|
||||
| 대상 | parent | properties 키 | 예시 |
|
||||
|------|--------|---------------|------|
|
||||
| DB 하위 | `{"database_id": "..."}` | DB의 title 속성명 (예: `"이름"`) | `{"이름": {"title": [...]}}` |
|
||||
| 페이지 하위 | `{"page_id": "..."}` | `"title"` (고정) | `{"title": {"title": [...]}}` |
|
||||
| WACE 직접 하위 | `{"page_id": "31e2a200-9533-80ac-9fcf-d4ad3c676929"}` | `"title"` | `{"title": {"title": [...]}}` |
|
||||
|
||||
### icon 설정
|
||||
|
||||
icon은 반드시 **JSON 문자열**로 전달한다 (객체가 아님):
|
||||
|
||||
```
|
||||
"{\"type\": \"emoji\", \"emoji\": \"📘\"}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 코드 블록 language 목록
|
||||
|
||||
Notion API가 지원하는 주요 language 값. 지원하지 않는 값을 넣으면 **400 에러** 발생.
|
||||
|
||||
| language | 용도 | 비고 |
|
||||
|----------|------|------|
|
||||
| `shell` | 터미널 명령어 실행 | |
|
||||
| `bash` | Shell 스크립트 파일 내용 | |
|
||||
| `docker` | Dockerfile 내용 | `dockerfile`은 미지원 |
|
||||
| `yaml` | docker-compose.yml, CI/CD 워크플로우 | |
|
||||
| `json` | JSON 설정 파일 | |
|
||||
| `javascript` | JS 코드 | |
|
||||
| `typescript` | TS 코드 | |
|
||||
| `python` | Python 코드 | |
|
||||
| `sql` | SQL 쿼리 | |
|
||||
| `html` | HTML 마크업 | |
|
||||
| `css` | CSS 스타일 | |
|
||||
| `hcl` | Terraform 설정 | |
|
||||
| `mermaid` | 다이어그램 (Notion이 자동 시각화) | |
|
||||
| `plain text` | 일반 텍스트 | |
|
||||
|
||||
코드 블록 JSON 예시:
|
||||
|
||||
```json
|
||||
{"type": "code", "code": {"rich_text": [{"type": "text", "text": {"content": "docker-compose up -d\ndocker-compose ps"}}], "language": "shell"}}
|
||||
```
|
||||
|
||||
### 코드 블록 작성 규칙
|
||||
|
||||
- 코드 블록 앞에 `bulleted_list_item`으로 **작업명** 라벨 배치
|
||||
- 코드 블록 뒤에 `paragraph`로 부가 설명 추가 (code annotation으로 명령어 강조)
|
||||
- 코드 블록 내 `#` 주석으로 각 명령어 설명 가능
|
||||
- 명령어 실행: `language: "shell"` / 스크립트 파일: `language: "bash"` 구분
|
||||
|
||||
---
|
||||
|
||||
## 제목 계층 구조 (필수)
|
||||
|
||||
원본 문서의 #/##/### 제목 계층을 반드시 Notion 네이티브 블록으로 변환한다.
|
||||
|
||||
### H2 (대섹션 제목)
|
||||
|
||||
`heading_2` 블록을 사용한다. 앞에 `divider`를 넣어 시각적으로 구분한다.
|
||||
|
||||
```json
|
||||
{"type": "divider", "divider": {}}
|
||||
{"type": "heading_2", "heading_2": {"rich_text": [{"type": "text", "text": {"content": "🔹 대섹션 제목"}}]}}
|
||||
```
|
||||
|
||||
### H3 (소제목)
|
||||
|
||||
`heading_3` 블록을 사용한다. divider 없이 바로 사용.
|
||||
|
||||
```json
|
||||
{"type": "heading_3", "heading_3": {"rich_text": [{"type": "text", "text": {"content": "1️⃣ 소제목"}}]}}
|
||||
```
|
||||
|
||||
### 결론/완료 기준 섹션
|
||||
|
||||
```json
|
||||
{"type": "divider", "divider": {}}
|
||||
{"type": "heading_2", "heading_2": {"rich_text": [{"type": "text", "text": {"content": "✅ 완료 기준"}}]}}
|
||||
```
|
||||
|
||||
## Mermaid 다이어그램 (필수 — 적극 활용)
|
||||
|
||||
다이어그램은 반드시 `code` 블록 + `language: "mermaid"`로 작성한다.
|
||||
Notion은 Mermaid 코드 블록을 자동으로 시각화 렌더링한다.
|
||||
|
||||
### code 블록 사용법
|
||||
|
||||
```json
|
||||
{"type": "code", "code": {"rich_text": [{"type": "text", "text": {"content": "graph TD\n A[시작] --> B[처리]\n B --> C[완료]"}}], "language": "mermaid"}}
|
||||
```
|
||||
|
||||
### 다이어그램 유형별 Mermaid 코드
|
||||
|
||||
**시퀀스 다이어그램 (API 호출 흐름):**
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as 사용자
|
||||
participant V as v2-report-viewer
|
||||
participant B as Backend API
|
||||
participant D as PostgreSQL
|
||||
U->>V: 1. 메뉴 진입
|
||||
V->>B: 2. 매핑 리포트 조회
|
||||
B->>D: 3. report_menu_mapping 조회
|
||||
D-->>B: 4. 리포트 목록
|
||||
B-->>V: 5. 리포트 목록 반환
|
||||
U->>V: 6. 리포트 선택
|
||||
V->>B: 7. 데이터 + 레이아웃 요청
|
||||
B->>D: 8. report_master + 쿼리 실행
|
||||
D-->>B: 9. 결과 반환
|
||||
B-->>V: 10. 렌더링 데이터
|
||||
V-->>U: 11. PDF/미리보기
|
||||
```
|
||||
|
||||
**플로우 다이어그램 (업무 프로세스):**
|
||||
```mermaid
|
||||
graph LR
|
||||
A[거래처관리] --> B[견적관리]
|
||||
B --> C[수주관리]
|
||||
C --> D[생산계획]
|
||||
D --> E[작업지시]
|
||||
E --> F[POP실적]
|
||||
F --> G[입고]
|
||||
G --> H[출고]
|
||||
H --> I[세금계산서]
|
||||
C --> J[발주관리]
|
||||
J --> K[입고관리]
|
||||
K --> L[품질검사]
|
||||
L --> G
|
||||
```
|
||||
|
||||
**아키텍처 다이어그램 (시스템 구조):**
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Frontend
|
||||
A[업무 화면] --> B[v2-report-viewer]
|
||||
end
|
||||
subgraph Backend
|
||||
C[reportController] --> D[reportService]
|
||||
end
|
||||
subgraph Database
|
||||
E[report_master]
|
||||
F[report_menu_mapping]
|
||||
end
|
||||
B -->|API 호출| C
|
||||
D -->|쿼리| E
|
||||
D -->|쿼리| F
|
||||
```
|
||||
|
||||
**ER 다이어그램 (DB 구조):**
|
||||
```mermaid
|
||||
erDiagram
|
||||
report_master ||--o{ report_menu_mapping : "1:N"
|
||||
report_master {
|
||||
int report_id PK
|
||||
string report_name
|
||||
text query_text
|
||||
jsonb layout_json
|
||||
string company_code
|
||||
}
|
||||
report_menu_mapping {
|
||||
int id PK
|
||||
int report_id FK
|
||||
int menu_objid FK
|
||||
int sort_order
|
||||
string company_code
|
||||
}
|
||||
```
|
||||
|
||||
**상태 다이어그램:**
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 초안
|
||||
초안 --> 검토중
|
||||
검토중 --> 승인
|
||||
검토중 --> 반려
|
||||
반려 --> 수정
|
||||
수정 --> 검토중
|
||||
승인 --> 활성
|
||||
활성 --> 비활성
|
||||
초안 --> 삭제
|
||||
```
|
||||
|
||||
### 다이어그램 삽입 시점 (적극 활용)
|
||||
|
||||
| 설명 내용 | 다이어그램 유형 | Mermaid 타입 |
|
||||
|-----------|----------------|-------------|
|
||||
| 시스템 아키텍처 | 아키텍처 다이어그램 | `graph TD` + `subgraph` |
|
||||
| API 호출 흐름 | 시퀀스 다이어그램 | `sequenceDiagram` |
|
||||
| 업무 프로세스 | 플로우 다이어그램 | `graph LR` 또는 `graph TD` |
|
||||
| DB 구조 | ER 다이어그램 | `erDiagram` |
|
||||
| 상태 변화 | 상태 다이어그램 | `stateDiagram-v2` |
|
||||
|
||||
---
|
||||
|
||||
## 올바른 서식 적용 방법
|
||||
|
||||
### 핵심 요약 (📌)
|
||||
|
||||
대섹션 시작 직후 paragraph로 핵심 개념을 요약한다.
|
||||
|
||||
```json
|
||||
{"type": "paragraph", "paragraph": {"rich_text": [
|
||||
{"type": "text", "text": {"content": "📌 "}, "annotations": {"bold": true}},
|
||||
{"type": "text", "text": {"content": "리포트 시스템"}, "annotations": {"bold": true, "italic": true}},
|
||||
{"type": "text", "text": {"content": "은 모든 단계에 걸쳐 있는 "}, "annotations": {"bold": true}},
|
||||
{"type": "text", "text": {"content": "횡단 출력 레이어"}, "annotations": {"bold": true, "italic": true}},
|
||||
{"type": "text", "text": {"content": "이다."}, "annotations": {"bold": true}}
|
||||
]}}
|
||||
```
|
||||
|
||||
### 경고/안내 텍스트 (⚠)
|
||||
|
||||
주의사항이나 미완성 안내에 사용한다.
|
||||
|
||||
```json
|
||||
{"type": "paragraph", "paragraph": {"rich_text": [
|
||||
{"type": "text", "text": {"content": "⚠ 절대 main/develop에 push 금지. 개인 브랜치에서만 작업할 것"}, "annotations": {"bold": true}}
|
||||
]}}
|
||||
```
|
||||
|
||||
### 강조 텍스트
|
||||
|
||||
```json
|
||||
{"type": "text", "text": {"content": "강조할 텍스트"}, "annotations": {"bold": true}}
|
||||
```
|
||||
|
||||
### 인라인 코드
|
||||
|
||||
```json
|
||||
{"type": "text", "text": {"content": "report_master"}, "annotations": {"code": true}}
|
||||
```
|
||||
|
||||
### 불릿 리스트
|
||||
|
||||
```json
|
||||
{"type": "bulleted_list_item", "bulleted_list_item": {"rich_text": [
|
||||
{"type": "text", "text": {"content": "항목 이름"}, "annotations": {"bold": true}},
|
||||
{"type": "text", "text": {"content": " — 설명 텍스트"}}
|
||||
]}}
|
||||
```
|
||||
|
||||
### 불릿 + 인라인 코드 조합
|
||||
|
||||
명령어/URL을 검증 항목으로 나열할 때 사용한다.
|
||||
|
||||
```json
|
||||
{"type": "bulleted_list_item", "bulleted_list_item": {"rich_text": [
|
||||
{"type": "text", "text": {"content": "docker-compose up -d"}, "annotations": {"code": true}},
|
||||
{"type": "text", "text": {"content": " 한 번으로 전체 스택 기동 성공"}}
|
||||
]}}
|
||||
```
|
||||
|
||||
### 빈 줄
|
||||
|
||||
```json
|
||||
{"type": "paragraph", "paragraph": {"rich_text": []}}
|
||||
```
|
||||
|
||||
### 구분선
|
||||
|
||||
```json
|
||||
{"type": "divider", "divider": {}}
|
||||
```
|
||||
|
||||
## 사용 가능한 annotations
|
||||
|
||||
| annotation | 용도 |
|
||||
|---|---|
|
||||
| `bold: true` | 제목, 라벨, 강조, 경고 |
|
||||
| `italic: true` | 부가 설명 |
|
||||
| `bold: true` + `italic: true` | 핵심 기술 용어 첫 등장 |
|
||||
| `code: true` | 파일 경로, 명령어, 인라인 코드 |
|
||||
| `bold: true` + `code: true` | 코드 강조 (예: pg_dump 라벨) |
|
||||
| `strikethrough: true` | 취소선 |
|
||||
| `underline: true` | 밑줄 |
|
||||
|
||||
---
|
||||
|
||||
## 페이지 생성 절차
|
||||
|
||||
### 단일 페이지 (WACE 직접 하위)
|
||||
|
||||
1. `API-post-page`로 빈 페이지 생성 (WACE 하위)
|
||||
2. `API-patch-block-children`으로 콘텐츠 추가
|
||||
3. 한 번에 최대 100블록까지 추가 가능, 초과 시 나눠서 호출
|
||||
|
||||
### 계층 페이지 (DB/페이지 하위 트리)
|
||||
|
||||
1. `API-post-search`로 대상 페이지/DB 검색 → ID 확인
|
||||
2. `API-post-page`로 상위 페이지 생성 (DB 또는 페이지 하위) → ID 기록
|
||||
3. 생성된 ID를 parent로 하위 페이지 생성 → ID 기록
|
||||
4. 각 페이지에 `API-patch-block-children`으로 콘텐츠 추가
|
||||
5. 콘텐츠가 많으면 여러 번 나눠서 호출 (100블록 제한)
|
||||
|
||||
병렬 생성 가능: 같은 레벨의 페이지는 동시에 생성할 수 있다.
|
||||
순차 생성 필수: 상위 페이지 ID가 있어야 하위 페이지를 생성할 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## 페이지 구조 템플릿
|
||||
|
||||
### 기본 문서 페이지
|
||||
|
||||
```
|
||||
paragraph: 📌 핵심 요약 (bold, 기술용어 bold+italic)
|
||||
|
||||
divider
|
||||
heading_2: 🔹 대섹션 제목 1
|
||||
|
||||
paragraph: 📌 핵심 요약 (bold, 기술용어 bold+italic)
|
||||
|
||||
heading_3: 1️⃣ 소제목 1
|
||||
bulleted_list_item: **키워드** — 설명
|
||||
bulleted_list_item: **키워드** — 설명
|
||||
|
||||
code (mermaid): 시퀀스/플로우/아키텍처/ER 다이어그램
|
||||
|
||||
heading_3: 2️⃣ 소제목 2
|
||||
paragraph: 설명 텍스트
|
||||
bulleted_list_item: 항목들
|
||||
|
||||
divider
|
||||
heading_2: 🔹 대섹션 제목 2
|
||||
...
|
||||
|
||||
divider
|
||||
heading_2: ✅ 결론
|
||||
bulleted_list_item: 결론 1
|
||||
bulleted_list_item: 결론 2
|
||||
```
|
||||
|
||||
### Phase(개요) 페이지
|
||||
|
||||
로드맵, 프로젝트 계획 등 상위 개요 페이지에 사용한다.
|
||||
|
||||
```
|
||||
paragraph: 📌 Phase 핵심 요약 (bold, 기술용어 bold+italic)
|
||||
(빈 줄)
|
||||
|
||||
divider
|
||||
heading_2: 🔹 Phase 개요
|
||||
bulleted_list_item: **기간** — 날짜 범위
|
||||
bulleted_list_item: **학습 도구** — 도구 목록
|
||||
bulleted_list_item: **목표** — 목표 설명
|
||||
(빈 줄)
|
||||
|
||||
divider
|
||||
heading_2: 🔹 Sprint 목록 (또는 작업 목록)
|
||||
bulleted_list_item: **S0N (날짜)** — Sprint 설명
|
||||
bulleted_list_item: **S0N (날짜)** — Sprint 설명
|
||||
bulleted_list_item: **S0N (날짜)** — Sprint 설명
|
||||
(빈 줄)
|
||||
|
||||
divider
|
||||
heading_2: ✅ 완료 기준
|
||||
bulleted_list_item: 검증 항목 (`코드/명령어` 인라인 코드 포함)
|
||||
bulleted_list_item: 검증 항목
|
||||
```
|
||||
|
||||
### Sprint(실습) 페이지
|
||||
|
||||
기술 학습, 실습 가이드, 단계별 튜토리얼에 사용한다.
|
||||
|
||||
```
|
||||
paragraph: 📌 Sprint 핵심 요약 (bold, 기술용어 bold+italic)
|
||||
(빈 줄)
|
||||
|
||||
divider
|
||||
heading_2: 🔹 1단계: 단계 제목
|
||||
paragraph: 📌 이 단계의 목적 (bold)
|
||||
(빈 줄)
|
||||
bulleted_list_item: **작업명** (bold)
|
||||
code (shell): 실행할 명령어
|
||||
(빈 줄)
|
||||
bulleted_list_item: **다음 작업명** (bold)
|
||||
code (shell/bash/yaml/docker): 파일 내용 또는 명령어
|
||||
paragraph: 부가 설명 (`명령어` code annotation 강조)
|
||||
(빈 줄)
|
||||
|
||||
divider
|
||||
heading_2: 🔹 2단계: 단계 제목
|
||||
paragraph: 📌 이 단계의 목적 (bold)
|
||||
(빈 줄)
|
||||
bulleted_list_item: **작업명** (bold)
|
||||
code (language): 코드/명령어
|
||||
(빈 줄)
|
||||
bulleted_list_item: **핵심 개념** (bold)
|
||||
bulleted_list_item: `키워드` — 설명 (code + 일반 텍스트)
|
||||
bulleted_list_item: `키워드` — 설명
|
||||
(빈 줄)
|
||||
|
||||
... (단계 반복)
|
||||
|
||||
divider
|
||||
heading_2: ✅ 완료 기준
|
||||
bulleted_list_item: `명령어/URL` 검증 항목 (code annotation)
|
||||
bulleted_list_item: 검증 항목
|
||||
```
|
||||
|
||||
### 간략 페이지 (미래 작업용)
|
||||
|
||||
아직 상세 내용이 없는 페이지에 사용한다.
|
||||
|
||||
```
|
||||
paragraph: 📌 핵심 요약 한 줄 (bold)
|
||||
(빈 줄)
|
||||
paragraph: ⚠ 상세 실습 콘텐츠는 해당 Phase 진입 시 추가 예정 (bold)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 텍스트 서식 규칙
|
||||
|
||||
| 용도 | 서식 | 예시 |
|
||||
|------|------|------|
|
||||
| 핵심 기술 용어 (첫 등장) | bold + italic | ***Docker***, ***Terraform*** |
|
||||
| 핵심 개념/키워드 | bold | **레이어 캐싱**, **멀티스테이지 빌드** |
|
||||
| 코드/명령어/경로/URL | code annotation | `docker-compose up -d`, `/api/health` |
|
||||
| 코드 + 강조 | bold + code | **`pg_dump`** |
|
||||
| 부가 설명 | 일반 텍스트 (괄호) | (만료 전까지 사용 가능) |
|
||||
| 경고/주의 | ⚠ + bold | **⚠ 절대 main에 push 금지** |
|
||||
|
||||
## 이모지 사용 규칙
|
||||
|
||||
| 이모지 | 용도 |
|
||||
|--------|------|
|
||||
| ✅ | 완료 기준 섹션, 장점, 완료 상태 |
|
||||
| 🔹 | 대섹션 제목 (heading_2), 단계 제목 |
|
||||
| 📌 | 핵심 요약, 단계 목적 설명 |
|
||||
| ⚠ | 경고, 주의사항, 미완성 안내 |
|
||||
| 1️⃣2️⃣3️⃣ | 소제목 번호 (heading_3) |
|
||||
| 🧩🔗 | 개념 설명 소제목 |
|
||||
| 📦📝⚙️🏗️🔧🚀📚🌐🔥📊📈🎯 | Sprint/페이지 icon |
|
||||
|
||||
---
|
||||
|
||||
## 체크리스트
|
||||
|
||||
Notion 콘텐츠 작성 전 확인:
|
||||
|
||||
**계층 페이지 생성:**
|
||||
- API-post-search로 대상 페이지/DB를 먼저 검색했는가
|
||||
- 검색 결과의 object 필드로 page/database를 구분했는가
|
||||
- DB 하위 페이지는 `database_id` parent + DB의 title 속성명(예: "이름")을 사용했는가
|
||||
- 페이지 하위 페이지는 `page_id` parent + `"title"` 속성명을 사용했는가
|
||||
- 생성된 페이지 ID를 기록하여 하위 페이지/콘텐츠 추가에 사용했는가
|
||||
- icon을 JSON 문자열 형식으로 전달했는가
|
||||
|
||||
**제목 계층 구조 (필수):**
|
||||
- heading_2 블록으로 대섹션(H2)을 만들었는가
|
||||
- heading_3 블록으로 소제목(H3)을 만들었는가
|
||||
- divider 블록으로 대섹션 사이를 구분했는가
|
||||
- 원본 문서의 #/##/### 계층이 heading_2/heading_3으로 정확히 반영되었는가
|
||||
|
||||
**코드 블록:**
|
||||
- language 값이 Notion API 지원 목록에 있는가 (`dockerfile` → `docker`)
|
||||
- 코드 블록 앞에 bulleted_list_item으로 라벨을 배치했는가
|
||||
- 코드 블록 내 주석(#)으로 각 명령어를 설명했는가
|
||||
- 명령어 실행은 `shell`, 스크립트 파일은 `bash`로 구분했는가
|
||||
|
||||
**Mermaid 다이어그램 (필수):**
|
||||
- 시스템 아키텍처 → code 블록 + mermaid (graph TD + subgraph)
|
||||
- API 호출 흐름 → code 블록 + mermaid (sequenceDiagram)
|
||||
- 업무 프로세스 → code 블록 + mermaid (graph LR)
|
||||
- DB 구조 → code 블록 + mermaid (erDiagram)
|
||||
- 상태 변화 → code 블록 + mermaid (stateDiagram-v2)
|
||||
- code 블록의 language가 "mermaid"로 설정되었는가
|
||||
|
||||
**마크다운 금지:**
|
||||
- 텍스트에 `##`, `###` 마크다운 헤딩을 넣지 않았는가
|
||||
- 텍스트에 `---` 마크다운 구분선을 넣지 않았는가
|
||||
- annotations에 code: true를 넣고 텍스트에도 백틱을 넣지 않았는가
|
||||
|
||||
**서식 규칙:**
|
||||
- 이모지 접두사를 적절히 사용했는가 (✅, 🔹, 📌, ⚠ 등)
|
||||
- 핵심 기술 용어 첫 등장 시 bold + italic 조합을 사용했는가
|
||||
- 불릿 리스트에서 "**키워드** — 설명" 패턴을 따랐는가
|
||||
- 경고/안내 텍스트에 ⚠ + bold를 사용했는가
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: plan
|
||||
description: 리포트 기능 구현 계획서 작성 워크플로우. 현재 대화 내용을 분석하여 리포트 관련 구현 계획을 수립하고 reportdocs를 갱신. 계획 수립이나 설계가 필요할 때 사용.
|
||||
---
|
||||
|
||||
# 리포트 구현 계획서 작성 워크플로우
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
리포트 관련 기능만 계획한다. 그 외 기능은 범위 밖.
|
||||
|
||||
## 절차
|
||||
|
||||
### 1. 현황 파악
|
||||
- `reportdocs/STATUS.md` 현재 진행 상태 확인
|
||||
- `reportdocs/PLAN.md` 기존 계획 확인
|
||||
- 대화에서 도출된 요구사항 정리
|
||||
|
||||
### 2. 코드베이스 분석
|
||||
Task tool의 `explore` subagent로:
|
||||
- 리포트 관련 파일만 대상으로 영향 분석
|
||||
- 리포트 밖 파일에 영향이 가는지 확인
|
||||
|
||||
### 3. 계획서 작성
|
||||
|
||||
```markdown
|
||||
# [리포트 기능명] 구현 계획
|
||||
|
||||
## 목표
|
||||
[1-2문장 요약]
|
||||
|
||||
## 변경 파일 목록 (리포트 범위 내만)
|
||||
| 파일 | 변경 유형 | 설명 |
|
||||
|------|----------|------|
|
||||
|
||||
## 구현 순서
|
||||
1. [ ] 단계 1
|
||||
2. [ ] 단계 2
|
||||
|
||||
## 리포트 밖 영향 여부
|
||||
- 없음 / 있음 (있으면 상세 기술)
|
||||
```
|
||||
|
||||
### 4. reportdocs 갱신
|
||||
- `reportdocs/STATUS.md` 업데이트
|
||||
- `reportdocs/PLAN.md`에 새 계획 추가
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: react-component
|
||||
description: 리포트 React 컴포넌트 클린코드 구현/수정 워크플로우. 리포트 디자이너 컴포넌트 생성, 리팩토링, 최적화 시 사용.
|
||||
---
|
||||
|
||||
# 리포트 컴포넌트 클린코드 워크플로우
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
`frontend/components/report/` 내 파일만 수정. 그 외 컴포넌트는 수정하지 않는다.
|
||||
|
||||
## 표준 컴포넌트 구조
|
||||
|
||||
```typescript
|
||||
"use client";
|
||||
|
||||
// 1. 외부 라이브러리
|
||||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||
|
||||
// 2. 내부 유틸/컴포넌트
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
// 3. 타입
|
||||
import type { ReportComponent } from "@/types/report";
|
||||
|
||||
// 4. 상수 (컴포넌트 외부)
|
||||
const DEFAULT_CONFIG = { ... } as const;
|
||||
|
||||
// 5. 타입 정의 (컴포넌트 외부)
|
||||
interface Props { ... }
|
||||
|
||||
// 6. 컴포넌트 본체
|
||||
export const MyComponent: React.FC<Props> = ({ ... }) => {
|
||||
// 6-1 ~ 6-8 순서 준수
|
||||
};
|
||||
```
|
||||
|
||||
## 필수 규칙
|
||||
|
||||
- 500줄 초과 금지 → 서브 컴포넌트 분리
|
||||
- `any` 금지 → `Record<string, unknown>` 이상
|
||||
- shadcn/ui 컴포넌트 우선 사용
|
||||
- CSS 변수 사용 (하드코딩 색상 금지)
|
||||
|
||||
## 리포트 디자이너 컴포넌트 패턴
|
||||
|
||||
- `ReportDesignerContext`로 전역 상태 관리
|
||||
- 속성 패널: `designer/properties/` 디렉토리
|
||||
- 모달: `designer/modals/` 디렉토리
|
||||
- 캔버스: `designer/ReportDesignerCanvas.tsx`
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: table-sql
|
||||
description: 리포트 관련 테이블 SQL 작성 가이드. 리포트 테이블 생성 DDL, 메타데이터 등록 시 사용.
|
||||
---
|
||||
|
||||
# 리포트 테이블 SQL 작성 가이드
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
리포트 관련 테이블(report_master, report_details 등)만 대상.
|
||||
기존 테이블 구조는 수정하지 않는다.
|
||||
|
||||
## 핵심 원칙
|
||||
|
||||
1. 모든 비즈니스 컬럼은 `VARCHAR(500)`로 통일
|
||||
2. 날짜/시간 컬럼만 `TIMESTAMP` 사용
|
||||
3. 기본 컬럼 5개 자동 포함: id, created_date, updated_date, writer, company_code
|
||||
4. 3개 메타데이터 테이블 등록 필수: `table_labels`, `column_labels`, `table_type_columns`
|
||||
|
||||
## 테이블 생성 DDL 템플릿
|
||||
|
||||
```sql
|
||||
CREATE TABLE "테이블명" (
|
||||
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
||||
"created_date" timestamp DEFAULT now(),
|
||||
"updated_date" timestamp DEFAULT now(),
|
||||
"writer" varchar(500) DEFAULT NULL,
|
||||
"company_code" varchar(500),
|
||||
-- 사용자 정의 컬럼
|
||||
"컬럼1" varchar(500),
|
||||
"컬럼2" varchar(500)
|
||||
);
|
||||
```
|
||||
|
||||
## 메타데이터 등록 (3개 필수)
|
||||
|
||||
```sql
|
||||
INSERT INTO table_labels (table_name, display_name, description, company_code)
|
||||
VALUES ('테이블명', '표시명', '설명', '회사코드');
|
||||
|
||||
INSERT INTO column_labels (table_name, column_name, display_name, company_code)
|
||||
VALUES ('테이블명', '컬럼명', '표시명', '회사코드');
|
||||
|
||||
INSERT INTO table_type_columns (table_name, column_name, column_type, display_order, company_code)
|
||||
VALUES ('테이블명', '컬럼명', 'VARCHAR', 순서, '회사코드');
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: ui-debugging
|
||||
description: 리포트 UI/UX 문제 디버깅 가이드. 리포트 화면의 레이아웃, 스크롤, 스타일 문제 진단과 해결. 리포트 UI 버그, 레이아웃, CSS 관련 이슈 시 사용.
|
||||
---
|
||||
|
||||
# 리포트 UI/UX 디버깅 가이드
|
||||
|
||||
## 수정 범위 제약
|
||||
|
||||
`frontend/components/report/` 내 파일만 수정. 공통 레이아웃(AppLayout 등)은 수정하지 않는다.
|
||||
|
||||
## 디버깅 공통 절차
|
||||
|
||||
1. 브라우저 개발자 도구로 문제 요소 식별
|
||||
2. Computed Style 확인
|
||||
3. 부모-자식 관계 추적
|
||||
4. 리포트 컴포넌트 내에서 최소한의 수정
|
||||
5. 반응형/다크모드에서도 검증
|
||||
|
||||
## 문제 유형별 진단
|
||||
|
||||
### 레이아웃 깨짐
|
||||
- [ ] Flexbox 부모에 `display: flex` 확인
|
||||
- [ ] 부모 체인에 명시적 높이/너비 확인
|
||||
- [ ] `overflow: hidden` 누락 여부
|
||||
|
||||
### 스크롤 문제
|
||||
- [ ] 부모 높이 확정
|
||||
- [ ] 스크롤 영역: `flex: 1, minHeight: 0, overflowY: auto`
|
||||
- [ ] 중간 컨테이너: `overflow-hidden`
|
||||
- 상세 패턴: [reference.md](reference.md) 참조
|
||||
|
||||
### 스타일 불일치
|
||||
- [ ] CSS 변수 사용 (하드코딩 색상 금지)
|
||||
- [ ] shadcn/ui 컴포넌트 우선
|
||||
- [ ] 다크모드 호환 (`bg-background`)
|
||||
|
||||
### 테이블 고정 헤더 (Sticky Header)
|
||||
|
||||
```tsx
|
||||
<div className="relative overflow-auto" style={{ height: "450px" }}>
|
||||
<Table noWrapper>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="bg-background sticky top-0 z-10 border-b shadow-[0_1px_0_0_rgb(0,0,0,0.1)]">
|
||||
헤더
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>{/* 데이터 행들 */}</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
```
|
||||
|
||||
필수: `noWrapper`, `bg-background sticky top-0 z-10`, 고정 높이
|
||||
@@ -0,0 +1,70 @@
|
||||
# 스크롤 문제 상세 패턴 및 예시
|
||||
|
||||
## 패턴 A: 최상위 Fixed/Absolute 컨테이너
|
||||
|
||||
```tsx
|
||||
<div className="fixed inset-0 z-50 bg-background">
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex items-center gap-4 border-b bg-background p-4">헤더</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<FlowEditor />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 패턴 B: 중첩된 Flex 컨테이너
|
||||
|
||||
```tsx
|
||||
<div className="flex h-full w-full" style={{ height: '100%', overflow: 'hidden' }}>
|
||||
<div className="h-full w-[300px] border-r">사이드바</div>
|
||||
<div className="relative flex-1">캔버스</div>
|
||||
<div style={{ height: "100%", width: "350px", display: "flex", flexDirection: "column" }} className="border-l bg-white">
|
||||
<PropertiesPanel />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 패턴 C: 스크롤 가능 영역
|
||||
|
||||
```tsx
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
<div style={{ flexShrink: 0, height: '64px' }} className="flex items-center justify-between border-b p-4">헤더</div>
|
||||
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden' }}>
|
||||
<PropertiesContent />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 일반적인 실수
|
||||
|
||||
### 부모 높이 미확정
|
||||
```tsx
|
||||
// Bad
|
||||
<div className="flex flex-col"><div className="flex-1"><ScrollComponent /></div></div>
|
||||
// Good
|
||||
<div className="flex flex-col h-screen"><div className="flex-1 overflow-hidden"><ScrollComponent /></div></div>
|
||||
```
|
||||
|
||||
### minHeight: 0 누락
|
||||
```tsx
|
||||
// Bad
|
||||
<div style={{ flex: 1, overflowY: 'auto' }}>{/* 스크롤 안 됨 */}</div>
|
||||
// Good
|
||||
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>{/* 스크롤 됨 */}</div>
|
||||
```
|
||||
|
||||
## 최종 구조
|
||||
|
||||
```
|
||||
페이지 (fixed inset-0)
|
||||
└─ flex flex-col h-full
|
||||
├─ 헤더 (고정)
|
||||
└─ 컨테이너 (flex-1 overflow-hidden)
|
||||
└─ 에디터 (height: 100%, overflow: hidden)
|
||||
└─ flex row
|
||||
└─ 패널 (display: flex, flexDirection: column)
|
||||
└─ 패널 내부 (height: 100%)
|
||||
├─ 헤더 (flexShrink: 0, height: 64px)
|
||||
└─ 스크롤 (flex: 1, minHeight: 0, overflowY: auto)
|
||||
```
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: web-verify
|
||||
description: WACE PLM UI 검증 워크플로우. 화면 구현 후 스크린샷으로 시각적 확인이 필요할 때 사용.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# UI 검증 워크플로우
|
||||
|
||||
## 로그인 정보 (자동 적용)
|
||||
|
||||
- URL: http://localhost:9771
|
||||
- 아이디: wace
|
||||
- 비밀번호: qlalfqjsgh11
|
||||
|
||||
## 절차
|
||||
|
||||
1. 로컬 서버 상태 확인 (9771, 9090)
|
||||
2. browser-use subagent로 브라우저 실행
|
||||
3. 위 계정으로 자동 로그인 → 요청된 화면으로 이동
|
||||
4. 스크린샷 캡처 및 분석
|
||||
|
||||
## 화면별 접근 방법
|
||||
|
||||
### 리포트 관리 페이지
|
||||
1. 좌측 메뉴 > 화면관리 > 리포트 관리 클릭
|
||||
2. URL: `/admin/screenMng/reportList`
|
||||
|
||||
### 리포트 디자이너 진입
|
||||
1. 리포트 관리 페이지에서 리포트 행의 "수정" 버튼(연필 아이콘) 클릭
|
||||
2. 또는 리포트명 텍스트를 직접 클릭
|
||||
3. URL: `/admin/screenMng/reportList/designer/{reportId}`
|
||||
|
||||
### 리포트 디자이너 — 컴포넌트 설정 모달 열기
|
||||
캔버스 내부의 컴포넌트는 일반 클릭으로 선택되지 않을 수 있음. 아래 방법을 순서대로 시도할 것:
|
||||
|
||||
1. **방법 1: 캔버스 내 컴포넌트 더블클릭**
|
||||
- 캔버스 영역에서 텍스트/테이블 등 컴포넌트 위치를 더블클릭
|
||||
- 모달이 열리면 상단에 "{타입} 설정" 제목이 표시됨 (예: "텍스트 설정")
|
||||
- 탭: 기능 설정 / 데이터 소스 / 미리보기
|
||||
|
||||
2. **방법 2: 브라우저 콘솔에서 직접 모달 열기** (방법 1 실패 시)
|
||||
- 브라우저 콘솔에서 컴포넌트 ID를 찾아 `openComponentModal` 호출
|
||||
- 캔버스 내 요소의 `data-component-id` 속성 확인
|
||||
|
||||
3. **방법 3: 우측 패널 활용**
|
||||
- 캔버스에서 컴포넌트를 단일 클릭하면 우측 패널에 "스타일 편집" 표시
|
||||
- 우측 패널 상단의 컴포넌트명 확인으로 선택 여부 판단
|
||||
|
||||
### 리포트 디자이너 — 모달 탭 구조
|
||||
- **기능 설정**: 데이터 바인딩(쿼리 Select + 필드 Select) + 컴포넌트별 설정
|
||||
- **데이터 소스**: 비주얼 데이터 소스 빌더 (마스터-디테일 테이블 설정)
|
||||
- **미리보기**: 컴포넌트 미리보기
|
||||
|
||||
## 검증 항목
|
||||
|
||||
### 리포트 목록
|
||||
- 테이블 데이터 로딩
|
||||
- CRUD 버튼 동작
|
||||
- 검색/필터
|
||||
|
||||
### 리포트 디자이너
|
||||
- 캔버스 렌더링
|
||||
- 컴포넌트 더블클릭 → 설정 모달 열기
|
||||
- 데이터 소스 탭: 마스터 테이블 선택, 컬럼 체크, 디테일 추가, FK 자동 감지
|
||||
- 속성 패널
|
||||
- 프리뷰 모달
|
||||
|
||||
### 공통
|
||||
- 스크롤 정상
|
||||
- 콘솔 에러 없음
|
||||
Reference in New Issue
Block a user