# 리포트 컴포넌트화 (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` | `` (props 없음, 1377행) | Wrapper에서 `useSearchParams`로 `menuObjid` 파싱 후 전달 | | `frontend/app/(pop)/pop/screens/[screenId]/page.tsx` | `` (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) | 전체 보기 모달 |