diff --git a/backend-node/src/services/commonCodeService.ts b/backend-node/src/services/commonCodeService.ts index 153af7db..bfb93532 100644 --- a/backend-node/src/services/commonCodeService.ts +++ b/backend-node/src/services/commonCodeService.ts @@ -48,6 +48,7 @@ export interface CreateCategoryData { categoryNameEng?: string; description?: string; sortOrder?: number; + isActive?: string; } export interface CreateCodeData { @@ -56,6 +57,7 @@ export interface CreateCodeData { codeNameEng?: string; description?: string; sortOrder?: number; + isActive?: string; } export class CommonCodeService { @@ -176,6 +178,8 @@ export class CommonCodeService { updatedBy: string ) { try { + // 디버깅: 받은 데이터 로그 + logger.info(`카테고리 수정 데이터:`, { categoryCode, data }); const category = await prisma.code_category.update({ where: { category_code: categoryCode }, data: { @@ -183,6 +187,12 @@ export class CommonCodeService { category_name_eng: data.categoryNameEng, description: data.description, sort_order: data.sortOrder, + is_active: + typeof data.isActive === "boolean" + ? data.isActive + ? "Y" + : "N" + : data.isActive, // boolean이면 "Y"/"N"으로 변환 updated_by: updatedBy, updated_date: new Date(), }, @@ -256,6 +266,8 @@ export class CommonCodeService { updatedBy: string ) { try { + // 디버깅: 받은 데이터 로그 + logger.info(`코드 수정 데이터:`, { categoryCode, codeValue, data }); const code = await prisma.code_info.update({ where: { code_category_code_value: { @@ -268,6 +280,12 @@ export class CommonCodeService { code_name_eng: data.codeNameEng, description: data.description, sort_order: data.sortOrder, + is_active: + typeof data.isActive === "boolean" + ? data.isActive + ? "Y" + : "N" + : data.isActive, // boolean이면 "Y"/"N"으로 변환 updated_by: updatedBy, updated_date: new Date(), }, diff --git a/docs/공통코드_관리_시스템_설계.md b/docs/공통코드_관리_시스템_설계.md index 7d503bf6..3b0d5286 100644 --- a/docs/공통코드_관리_시스템_설계.md +++ b/docs/공통코드_관리_시스템_설계.md @@ -784,6 +784,122 @@ export class CommonCodeService { **목표 기간**: 1일 → **실제 소요**: 1일 +### ✅ Phase 4.7: 현대적 라이브러리 도입 (완료!) + +- [x] React Query 도입으로 데이터 페칭 최적화 +- [x] React Hook Form 도입으로 폼 관리 개선 +- [x] Zod 도입으로 스키마 기반 유효성 검사 +- [x] Query Key 기반 캐시 무효화로 CRUD 업데이트 자동화 +- [x] 컴포넌트 모듈화 (CategoryItem, SortableCodeItem 분리) +- [x] 로컬 상태 제거 및 서버 상태 단일화 +- [x] 린터 오류 0개 달성 + +**구현 완료 내역:** + +1. **React Query (@tanstack/react-query)** + + - [x] 자동 캐싱 및 백그라운드 리페칭 + - [x] Query Key 기반 캐시 무효화 (`frontend/lib/queryKeys.ts`) + - [x] Optimistic Updates (드래그앤드롭 순서 변경) + - [x] 로딩/에러 상태 자동 관리 + - [x] 네트워크 요청 최적화 + - [x] 커스텀 훅 구현: `useCategories`, `useCodes`, `useCreateCode`, `useUpdateCode`, `useDeleteCode`, `useReorderCodes` + +2. **React Hook Form** + + - [x] 성능 최적화 (불필요한 리렌더링 방지) + - [x] 간단한 폼 API 적용 (`CodeFormModal`, `CodeCategoryFormModal`) + - [x] Zod와 완벽 연동 + - [x] TypeScript 완벽 지원 + - [x] 실시간 검증 및 에러 메시지 표시 + +3. **Zod 스키마 검증** + - [x] 스키마 기반 데이터 구조 정의 (`frontend/lib/schemas/commonCode.ts`) + - [x] TypeScript 타입 자동 생성 + - [x] 런타임 검증 + 컴파일 타입 안전성 + - [x] 자동화된 에러 메시지 + - [x] 카테고리/코드 생성/수정 스키마 분리 + +**주요 성과:** + +- 🚀 **현대적 아키텍처 도입**: React Query + React Hook Form + Zod 완벽 통합 +- 📈 **성능 최적화**: 불필요한 리렌더링 제거, 효율적 캐싱, Optimistic Updates +- 🔒 **타입 안전성**: 완벽한 TypeScript 지원으로 런타임 오류 방지 +- 🧩 **컴포넌트 모듈화**: CategoryItem, SortableCodeItem 분리로 재사용성 향상 +- 🎯 **상태 관리 단순화**: 서버 상태와 클라이언트 상태 명확히 분리 +- ✨ **사용자 경험**: 즉시 반영되는 CRUD, 부드러운 드래그앤드롭 + +**목표 기간**: 2일 → **실제 소요**: 1일 + +**구현 계획 (완료):** + +1. **1단계: 의존성 설치 및 설정** + + ```bash + npm install @tanstack/react-query react-hook-form @hookform/resolvers zod + ``` + +2. **2단계: React Query 설정** + + - QueryClient 설정 및 Provider 추가 + - Query Key factory 함수 생성 + - 커스텀 훅 생성 (`useCategories`, `useCodes`, `useCreateCode` 등) + +3. **3단계: Zod 스키마 정의** + + ```typescript + const categorySchema = z.object({ + categoryCode: z.string().regex(/^[A-Z0-9_]+$/, "대문자, 숫자, _만 가능"), + categoryName: z.string().min(1, "필수 입력").max(20, "20자 이하"), + categoryNameEng: z.string().max(20, "20자 이하"), + description: z.string().max(50, "50자 이하"), + sortOrder: z.number().min(1, "1 이상"), + }); + + const codeSchema = z.object({ + codeValue: z.string().regex(/^[A-Z0-9_]+$/, "대문자, 숫자, _만 가능"), + codeName: z.string().min(1, "필수 입력").max(20, "20자 이하"), + codeNameEng: z.string().max(20, "20자 이하"), + description: z.string().max(50, "50자 이하"), + sortOrder: z.number().min(1, "1 이상"), + }); + ``` + +4. **4단계: Query Key 전략** + + ```typescript + // 카테고리 관련 + ["categories"][ // 모든 카테고리 + ("categories", { active: true }) + ][ // 활성 카테고리만 + // 코드 관련 + ("codes", categoryCode) + ][ // 특정 카테고리의 모든 코드 + ("codes", categoryCode, { active: true }) + ][("code", categoryCode, codeValue)]; // 특정 카테고리의 활성 코드만 // 특정 코드 상세 + ``` + +5. **5단계: React Hook Form 적용** + + - `CodeFormModal`, `CodeCategoryFormModal` 리팩토링 + - 기존 수동 검증 로직 제거 + - Zod resolver 적용 + +6. **6단계: 기존 코드 정리** + - `useCommonCode` 훅 단순화 + - 수동 상태 관리 코드 제거 + - 수동 Optimistic Updates 로직 제거 + +**예상 개선 효과:** + +- **코드량 40-50% 감소**: 보일러플레이트 코드 대폭 감소 +- **타입 안전성 100% 보장**: 런타임 + 컴파일 타임 검증 +- **성능 최적화**: 자동 캐싱, 불필요한 리렌더링 방지 +- **개발자 경험 향상**: 자동화된 폼 검증, 에러 처리 +- **유지보수성 향상**: 표준화된 패턴, 명확한 데이터 흐름 + +**목표 기간**: 2일 + ### ⏳ Phase 5: 화면관리 연계 (예정) - [ ] column_labels와 연동 확인 @@ -804,7 +920,7 @@ export class CommonCodeService { ## 🎯 현재 구현 상태 -### 📊 **전체 진행률: 83%** 🎉 +### 📊 **전체 진행률: 85%** 🎉 - ✅ **Phase 1**: 기본 구조 및 데이터베이스 (100%) - **완료!** - ✅ **Phase 2**: 백엔드 API 구현 (100%) - **완료!** @@ -812,6 +928,7 @@ export class CommonCodeService { - ✅ **Phase 4**: 고급 기능 구현 (100%) - **완료!** - ✅ **Phase 4.5**: UX/UI 개선 (100%) - **완료!** - ✅ **Phase 4.6**: CRUD 즉시 반영 개선 (100%) - **완료!** +- ✅ **Phase 4.7**: 현대적 라이브러리 도입 (100%) - **완료!** - ⏳ **Phase 5**: 화면관리 연계 (0%) - ⏳ **Phase 6**: 테스트 및 최적화 (0%) diff --git a/frontend/app/(main)/admin/commonCode/page.tsx b/frontend/app/(main)/admin/commonCode/page.tsx index 2c0c7896..b1941bb2 100644 --- a/frontend/app/(main)/admin/commonCode/page.tsx +++ b/frontend/app/(main)/admin/commonCode/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; import { CodeCategoryPanel } from "@/components/admin/CodeCategoryPanel"; import { CodeDetailPanel } from "@/components/admin/CodeDetailPanel"; // import { useMultiLang } from "@/hooks/useMultiLang"; // 무한 루프 방지를 위해 임시 제거 @@ -26,7 +25,7 @@ export default function CommonCodeManagementPage() {