diff --git a/claudedocs/architecture/[PLAN-2026-03-11] dynamic-multi-tenant-page-system.md b/claudedocs/architecture/[PLAN-2026-03-11] dynamic-multi-tenant-page-system.md index 862c9106..c8bce86a 100644 --- a/claudedocs/architecture/[PLAN-2026-03-11] dynamic-multi-tenant-page-system.md +++ b/claudedocs/architecture/[PLAN-2026-03-11] dynamic-multi-tenant-page-system.md @@ -1,11 +1,14 @@ # 동적 멀티테넌트 페이지 시스템 설계 > 작성일: 2026-03-11 -> 상태: 초안 (백엔드 논의 필요) +> 최종 업데이트: 2026-03-18 +> 상태: 초안 (백엔드 논의 진행 중) > 관련 문서: > - `[VISION-2026-02-19] dynamic-rendering-platform-strategy.md` > - `[PLAN-2026-02-06] multi-tenancy-optimization-roadmap.md` > - `[DESIGN-2026-02-11] dynamic-field-type-extension.md` +> - `[ANALYSIS-2026-03-17] tenant-module-separation-dependency-audit.md` +> - `[PLAN-2026-03-17] tenant-module-separation-plan.md` — **본 설계의 실행 계획 (Phase 0~3)** --- @@ -761,9 +764,61 @@ DynamicItemForm의 ComputedField → computed 타입으로 범용화 ### 규칙 17: 점진적 마이그레이션 전략 +#### 17-1. 3단계 아키텍처 방향 (2026-03-17 확인) + +``` +1단계: 현재 → 모듈 분리 + - 공통 ERP / 테넌트별 모듈 물리적 분리 + - 선결과제 해소 (아래 17-2 참조) + +2단계: 모듈 분리 → JSON 동적 조립 + - 테넌트 모듈을 manifest/JSON 기반으로 전환 + - 동적 페이지 렌더러 도입 + +3단계: 최종 — 빈 페이지 셸 + 백엔드 JSON으로 페이지 자동 조립 + - 이 문서의 최종 목표 +``` + +#### 17-2. 선결과제 (모듈 분리 전 해결 필수) + +| # | 과제 | 내용 | 예상 | +|---|------|------|------| +| 1 | CEO 대시보드 테넌트 의존성 해소 | 생산/건설 섹션 직접 import → 동적 로딩 전환 | - | +| 2 | 공유 컴포넌트 추출 | 결재/영업(공통)이 생산(경동) 코드 직접 import | - | +| 3 | 라우트 가드 추가 | 테넌트 미보유 모듈 URL 직접 접근 차단 | - | +| 4 | dashboard-invalidation 동적화 | production/construction 도메인 키 하드코딩 제거 | - | + +> 선결과제 해소 예상: 3~4일, 이후 모듈 분리 본작업은 별도 산정 + +**핵심 의존성 위반 (공통 → 테넌트 방향, 수정 필요)**: +``` +ApprovalBox → production/InspectionReportModal +Sales/production-orders → production/ProductionOrders (actions+types+UI) +Sales → router.push("/production/work-orders") 하드코딩 +CEODashboard → DailyProductionSection, ConstructionSection 직접 import +dashboard-invalidation.ts → production/construction 도메인 키 +``` + +**안전한 부분**: +- 테넌트 간 교차 의존성 없음 (생산↔건설 = 0) +- 건설(주일) 모듈 완전 독립 → 바로 분리 가능 +- Zustand 스토어, API 프록시, 메뉴 시스템은 무관 + +#### 17-3. 테넌트별 페이지 현황 (2026-03-17 분석) + +| 테넌트 | 업종 | 전용 모듈 | 페이지 수 | +|--------|------|----------|:---:| +| 공통 ERP | 전 업종 | 회계, 인사, 결재, 게시판, 설정, 고객센터 등 | ~165 | +| 경동 | 셔터 제조 (MES) | 생산, 품질관리 | ~27 | +| 주일 | 건설 시공 | 건설/프로젝트, 입찰, 기성 | ~48 | +| (옵션) | - | 차량관리 | ~13 | + +#### 17-4. 마이그레이션 Phase + | Phase | 범위 | 예상 기간 | 상태 | |-------|------|----------|------| -| **Phase 0** | 인프라 구축 | 2-3주 | ⏳ 준비 | +| **선결과제** | 의존성 해소 (17-2) | 3-4일 | ⏳ 준비 | +| **Phase 0** | 인프라 구축 | 2-3주 | ⏳ | | | - catch-all 라우터 | | | | | - pageConfigStore | | | | | - DynamicListPage/FormPage 렌더러 | | | @@ -776,13 +831,13 @@ DynamicItemForm의 ComputedField → computed 타입으로 범용화 | | - 거래처관리, 설비관리 등 | | | | **Phase 3** | 복잡한 비즈니스 페이지 전환 | 6-8주 | ⏳ | | | - 견적, 수주, 생산 등 로직 있는 페이지 | | | -| | - 로직 블록 구축 병행 | | | | **Phase 4** | 기존 정적 → 동적 완전 전환 | 지속적 | ⏳ | | | - 남은 하드코딩 페이지 점진적 전환 | | | ``` 전환 판단 기준: +[선행] 선결과제 해소 (의존성 분리) → 선결과제 Phase [쉬움] 순수 CRUD (리스트+폼) → Phase 2에서 전환 [보통] CRUD + 단순 계산 → Phase 2~3 [어려움] 복잡한 비즈니스 로직 → Phase 3 @@ -886,9 +941,10 @@ DynamicItemForm의 ComputedField → computed 타입으로 범용화 | 동적 필드 타입 설계 | `claudedocs/architecture/[DESIGN-2026-02-11]` | 4-Level 구조, 14종 필드 | | 동적 필드 구현 현황 | `claudedocs/architecture/[IMPL-2026-02-11]` | Phase 1~3 프론트 구현 완료 | | 백엔드 API 스펙 | `claudedocs/item-master/[API-REQUEST-2026-02-12]` | 동적 필드 타입 백엔드 요청서 | +| 테넌트 모듈 의존성 분석 | `claudedocs/architecture/[ANALYSIS-2026-03-17]` | 3테넌트 분리, 선결과제 4개, 의존성 위반 목록 | --- -**문서 버전**: 1.2 -**마지막 업데이트**: 2026-03-11 +**문서 버전**: 1.3 +**마지막 업데이트**: 2026-03-18 **다음 단계**: 백엔드 회의 → 협의 필요 항목 확정 → v2.0 작성 → `sam-docs/frontend/v2/`에 최종본 등록 diff --git a/claudedocs/architecture/[PLAN-2026-03-17] tenant-module-separation-plan.md b/claudedocs/architecture/[PLAN-2026-03-17] tenant-module-separation-plan.md index b9e35fe8..11dd8e1c 100644 --- a/claudedocs/architecture/[PLAN-2026-03-17] tenant-module-separation-plan.md +++ b/claudedocs/architecture/[PLAN-2026-03-17] tenant-module-separation-plan.md @@ -6,6 +6,39 @@ **Estimated Total Effort**: 12-16 working days across 4 phases **Zero Downtime Requirement**: All changes are additive; no page removal until Phase 3 +### 관련 문서 (로드맵 상 위치) + +| 문서 | 역할 | 관계 | +|------|------|------| +| `[VISION-2026-02-19] dynamic-rendering-platform-strategy.md` | 플랫폼 비전 | 최상위 방향성 | +| `[PLAN-2026-02-06] multi-tenancy-optimization-roadmap.md` | 멀티테넌시 로드맵 | 전체 단계 정의 | +| `[DESIGN-2026-02-11] dynamic-field-type-extension.md` | 동적 필드 타입 설계 | v3 렌더러 기초 | +| `[PLAN-2026-03-11] dynamic-multi-tenant-page-system.md` | **v3 최종 설계 (JSON 동적 페이지)** | **본 계획의 도착점** | +| `[ANALYSIS-2026-03-17] tenant-module-separation-dependency-audit.md` | 의존성 감사 | 본 계획의 근거 데이터 | +| **본 문서 (Phase 0~3)** | **프론트 모듈 분리 실행 계획** | **v3로 가는 징검다리** | + +``` +로드맵 전체 흐름: + +[VISION 02-19] 플랫폼 비전 + │ +[PLAN 02-06] 멀티테넌시 로드맵 + │ +[DESIGN 02-11] 동적 필드 타입 + │ +[PLAN 03-11] v3 최종 설계 (JSON 동적 페이지 + JSONB + pageType 렌더러) + │ +[ANALYSIS 03-17] 의존성 감사 ──→ 현재 코드의 모듈 간 결합 6건 발견 + │ +[PLAN 03-17] ★ 본 문서 ★ (Phase 0~3: 프론트 모듈 분리) + │ +[v2] 백엔드 page_configs JSONB API → useModules() 연결 + │ +[v3] catch-all route + DynamicListPage/FormPage 렌더러 + │ +[최종] 테넌트 추가 = 어드민 config 등록 → 코드 변경 0줄 +``` + --- ## Table of Contents diff --git a/public/sounds/default.wav b/public/sounds/default.wav index e69de29b..59238b1b 100644 Binary files a/public/sounds/default.wav and b/public/sounds/default.wav differ diff --git a/public/sounds/sam_voice.wav b/public/sounds/sam_voice.wav new file mode 100644 index 00000000..59238b1b Binary files /dev/null and b/public/sounds/sam_voice.wav differ diff --git a/src/components/settings/NotificationSettings/index.tsx b/src/components/settings/NotificationSettings/index.tsx index 4aaa7739..3df6464d 100644 --- a/src/components/settings/NotificationSettings/index.tsx +++ b/src/components/settings/NotificationSettings/index.tsx @@ -28,14 +28,27 @@ import { SOUND_OPTIONS, DEFAULT_ITEM_VISIBILITY } from './types'; import { saveNotificationSettings } from './actions'; import { ItemSettingsDialog } from './ItemSettingsDialog'; -// 미리듣기 함수 +// 미리듣기 - Audio API 재생 +let previewAudio: HTMLAudioElement | null = null; + function playPreviewSound(soundType: SoundType) { if (soundType === 'mute') { toast.info('무음으로 설정되어 있습니다.'); return; } - const soundName = soundType === 'default' ? '기본 알림음' : 'SAM 보이스'; - toast.info(`${soundName} 미리듣기`); + + // 이전 재생 중지 + if (previewAudio) { + previewAudio.pause(); + previewAudio = null; + } + + const soundFile = soundType === 'sam_voice' ? 'sam_voice.wav' : 'default.wav'; + previewAudio = new Audio(`/sounds/${soundFile}`); + previewAudio.play().catch(() => { + const soundName = soundType === 'default' ? '기본 알림음' : 'SAM 보이스'; + toast.info(`${soundName} 미리듣기 (음원 파일 준비 중)`); + }); } // 알림 항목 컴포넌트 diff --git a/src/components/settings/NotificationSettings/types.ts b/src/components/settings/NotificationSettings/types.ts index b9b878e6..a9fd013a 100644 --- a/src/components/settings/NotificationSettings/types.ts +++ b/src/components/settings/NotificationSettings/types.ts @@ -1,36 +1,12 @@ /** * 알림 설정 타입 정의 * - * ======================================== - * [2026-01-05] 백엔드 API 수정 필요 사항 - * ======================================== + * API 응답 구조: { enabled, email, soundType } per item + * soundType: 'default' | 'sam_voice' | 'mute' * - * 1. NotificationItem에 soundType 필드 추가 - * - 기존: { enabled: boolean, email: boolean } - * - 변경: { enabled: boolean, email: boolean, soundType: 'default' | 'sam_voice' | 'mute' } - * - * 2. OrderNotificationSettings에 approvalRequest 항목 추가 - * - 기존: { salesOrder, purchaseOrder } - * - 변경: { salesOrder, purchaseOrder, approvalRequest } - * - * 3. API 응답 예시: - * { - * "notice": { - * "enabled": true, - * "notice": { "enabled": true, "email": false, "soundType": "default" }, - * "event": { "enabled": true, "email": true, "soundType": "sam_voice" } - * }, - * "order": { - * "enabled": true, - * "salesOrder": { ... }, - * "purchaseOrder": { ... }, - * "approvalRequest": { "enabled": false, "email": false, "soundType": "default" } // 새로 추가 - * } - * } - * ======================================== + * [2026-03-18] soundType API 연동 완료 */ -// 알림 소리 타입 (NEW: 2026-01-05) export type SoundType = 'default' | 'sam_voice' | 'mute'; // 알림 소리 옵션