diff --git a/.cursor/rules/inputtype-usage-guide.mdc b/.cursor/rules/inputtype-usage-guide.mdc new file mode 100644 index 00000000..e9e29637 --- /dev/null +++ b/.cursor/rules/inputtype-usage-guide.mdc @@ -0,0 +1,279 @@ +# inputType 사용 가이드 + +## 핵심 원칙 + +**컬럼 타입 판단 시 반드시 `inputType`을 사용해야 합니다. `webType`은 레거시이며 더 이상 사용하지 않습니다.** + +--- + +## 올바른 사용법 + +### ✅ inputType 사용 (권장) + +```typescript +// 카테고리 타입 체크 +if (columnMeta.inputType === "category") { + // 카테고리 처리 로직 +} + +// 코드 타입 체크 +if (meta.inputType === "code") { + // 코드 처리 로직 +} + +// 필터링 +const categoryColumns = Object.entries(columnMeta) + .filter(([_, meta]) => meta.inputType === "category") + .map(([columnName, _]) => columnName); +``` + +### ❌ webType 사용 (금지) + +```typescript +// ❌ 절대 사용 금지! +if (columnMeta.webType === "category") { ... } + +// ❌ 이것도 금지! +const categoryColumns = columns.filter(col => col.webType === "category"); +``` + +--- + +## API에서 inputType 가져오기 + +### Backend API + +```typescript +// 컬럼 입력 타입 정보 가져오기 +const inputTypes = await tableTypeApi.getColumnInputTypes(tableName); + +// inputType 맵 생성 +const inputTypeMap: Record = {}; +inputTypes.forEach((col: any) => { + inputTypeMap[col.columnName] = col.inputType; +}); +``` + +### columnMeta 구조 + +```typescript +interface ColumnMeta { + webType?: string; // 레거시, 사용 금지 + codeCategory?: string; + inputType?: string; // ✅ 반드시 이것 사용! +} + +const columnMeta: Record = { + material: { + webType: "category", // 무시 + codeCategory: "", + inputType: "category", // ✅ 이것만 사용 + }, +}; +``` + +--- + +## 캐시 사용 시 주의사항 + +### ❌ 잘못된 캐시 처리 (inputType 누락) + +```typescript +const cached = tableColumnCache.get(cacheKey); +if (cached) { + const meta: Record = {}; + + cached.columns.forEach((col: any) => { + meta[col.columnName] = { + webType: col.webType, + codeCategory: col.codeCategory, + // ❌ inputType 누락! + }; + }); +} +``` + +### ✅ 올바른 캐시 처리 (inputType 포함) + +```typescript +const cached = tableColumnCache.get(cacheKey); +if (cached) { + const meta: Record = {}; + + // 캐시된 inputTypes 맵 생성 + const inputTypeMap: Record = {}; + if (cached.inputTypes) { + cached.inputTypes.forEach((col: any) => { + inputTypeMap[col.columnName] = col.inputType; + }); + } + + cached.columns.forEach((col: any) => { + meta[col.columnName] = { + webType: col.webType, + codeCategory: col.codeCategory, + inputType: inputTypeMap[col.columnName], // ✅ inputType 포함! + }; + }); +} +``` + +--- + +## 주요 inputType 종류 + +| inputType | 설명 | 사용 예시 | +| ---------- | ---------------- | ------------------ | +| `text` | 일반 텍스트 입력 | 이름, 설명 등 | +| `number` | 숫자 입력 | 금액, 수량 등 | +| `date` | 날짜 입력 | 생성일, 수정일 등 | +| `datetime` | 날짜+시간 입력 | 타임스탬프 등 | +| `category` | 카테고리 선택 | 분류, 상태 등 | +| `code` | 공통 코드 선택 | 코드 마스터 데이터 | +| `boolean` | 예/아니오 | 활성화 여부 등 | +| `email` | 이메일 입력 | 이메일 주소 | +| `url` | URL 입력 | 웹사이트 주소 | +| `image` | 이미지 업로드 | 프로필 사진 등 | +| `file` | 파일 업로드 | 첨부파일 등 | + +--- + +## 실제 적용 사례 + +### 1. TableListComponent - 카테고리 매핑 로드 + +```typescript +// ✅ inputType으로 카테고리 컬럼 필터링 +const categoryColumns = Object.entries(columnMeta) + .filter(([_, meta]) => meta.inputType === "category") + .map(([columnName, _]) => columnName); + +// 각 카테고리 컬럼의 값 목록 조회 +for (const columnName of categoryColumns) { + const response = await apiClient.get( + `/table-categories/${tableName}/${columnName}/values` + ); + // 매핑 처리... +} +``` + +### 2. InteractiveDataTable - 셀 값 렌더링 + +```typescript +// ✅ inputType으로 렌더링 분기 +const inputType = columnMeta[column.columnName]?.inputType; + +switch (inputType) { + case "category": + // 카테고리 배지 렌더링 + return {categoryLabel}; + + case "code": + // 코드명 표시 + return codeName; + + case "date": + // 날짜 포맷팅 + return formatDate(value); + + default: + return value; +} +``` + +### 3. 검색 필터 생성 + +```typescript +// ✅ inputType에 따라 다른 검색 UI 제공 +const renderSearchInput = (column: ColumnConfig) => { + const inputType = columnMeta[column.columnName]?.inputType; + + switch (inputType) { + case "category": + return ; + + case "code": + return ; + + case "date": + return ; + + case "number": + return ; + + default: + return ; + } +}; +``` + +--- + +## 마이그레이션 체크리스트 + +기존 코드에서 `webType`을 `inputType`으로 전환할 때: + +- [ ] `webType` 참조를 모두 `inputType`으로 변경 +- [ ] API 호출 시 `getColumnInputTypes()` 포함 확인 +- [ ] 캐시 사용 시 `cached.inputTypes` 매핑 확인 +- [ ] 타입 정의에서 `inputType` 필드 포함 +- [ ] 조건문에서 `inputType` 체크로 변경 +- [ ] 테스트 실행하여 정상 동작 확인 + +--- + +## 디버깅 팁 + +### inputType이 undefined인 경우 + +```typescript +// 디버깅 로그 추가 +console.log("columnMeta:", columnMeta); +console.log("inputType:", columnMeta[columnName]?.inputType); + +// 체크 포인트: +// 1. getColumnInputTypes() 호출 확인 +// 2. inputTypeMap 생성 확인 +// 3. meta 객체에 inputType 할당 확인 +// 4. 캐시 사용 시 cached.inputTypes 확인 +``` + +### webType만 있고 inputType이 없는 경우 + +```typescript +// ❌ 잘못된 데이터 구조 +{ + material: { + webType: "category", + codeCategory: "", + // inputType 누락! + } +} + +// ✅ 올바른 데이터 구조 +{ + material: { + webType: "category", // 레거시, 무시됨 + codeCategory: "", + inputType: "category" // ✅ 필수! + } +} +``` + +--- + +## 참고 자료 + +- **컴포넌트**: `/frontend/lib/registry/components/table-list/TableListComponent.tsx` +- **API 클라이언트**: `/frontend/lib/api/tableType.ts` +- **타입 정의**: `/frontend/types/table.ts` + +--- + +## 요약 + +1. **항상 `inputType` 사용**, `webType` 사용 금지 +2. **API에서 `getColumnInputTypes()` 호출** 필수 +3. **캐시 사용 시 `inputTypes` 포함** 확인 +4. **디버깅 시 `inputType` 값 확인** +5. **기존 코드 마이그레이션** 시 체크리스트 활용 diff --git a/frontend/components/admin/MenuManagement.tsx b/frontend/components/admin/MenuManagement.tsx index 0dc30248..6671504e 100644 --- a/frontend/components/admin/MenuManagement.tsx +++ b/frontend/components/admin/MenuManagement.tsx @@ -22,7 +22,7 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useMenu } from "@/contexts/MenuContext"; -import { useMenuManagementText, setTranslationCache } from "@/lib/utils/multilang"; +import { useMenuManagementText, setTranslationCache, getMenuTextSync } from "@/lib/utils/multilang"; import { useMultiLang } from "@/hooks/useMultiLang"; import { apiClient } from "@/lib/api/client"; @@ -545,9 +545,9 @@ export const MenuManagement: React.FC = () => { // uiTexts에서 번역 텍스트 찾기 let text = uiTexts[key]; - // uiTexts에 없으면 fallback 또는 키 사용 + // uiTexts에 없으면 getMenuTextSync로 기본 한글 텍스트 가져오기 if (!text) { - text = fallback || key; + text = getMenuTextSync(key, userLang) || fallback || key; } // 파라미터 치환 diff --git a/frontend/components/admin/MenuTable.tsx b/frontend/components/admin/MenuTable.tsx index eb790e93..55269e97 100644 --- a/frontend/components/admin/MenuTable.tsx +++ b/frontend/components/admin/MenuTable.tsx @@ -7,7 +7,7 @@ import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { toast } from "sonner"; -import { MENU_MANAGEMENT_KEYS } from "@/lib/utils/multilang"; +import { MENU_MANAGEMENT_KEYS, getMenuTextSync } from "@/lib/utils/multilang"; interface MenuTableProps { menus: MenuItem[]; @@ -39,7 +39,8 @@ export const MenuTable: React.FC = ({ }) => { // 다국어 텍스트 가져오기 함수 const getText = (key: string, fallback?: string): string => { - return uiTexts[key] || fallback || key; + // uiTexts에서 먼저 찾고, 없으면 기본 한글 텍스트를 가져옴 + return uiTexts[key] || getMenuTextSync(key, "KR") || fallback || key; }; // 다국어 텍스트 표시 함수 (기본값 처리) diff --git a/frontend/components/admin/RoleDeleteModal.tsx b/frontend/components/admin/RoleDeleteModal.tsx index cf363dbd..9d178351 100644 --- a/frontend/components/admin/RoleDeleteModal.tsx +++ b/frontend/components/admin/RoleDeleteModal.tsx @@ -133,7 +133,7 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete )} - +