"use client"; import React, { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Plus, Pencil, Trash2, Database, RefreshCw, Layers } from "lucide-react"; import { toast } from "sonner"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; import { hierarchyColumnApi, HierarchyColumnGroup, CreateHierarchyGroupRequest, } from "@/lib/api/hierarchyColumn"; import { commonCodeApi } from "@/lib/api/commonCode"; import apiClient from "@/lib/api/client"; interface TableInfo { tableName: string; displayName?: string; } interface ColumnInfo { columnName: string; displayName?: string; dataType?: string; } interface CategoryInfo { categoryCode: string; categoryName: string; } export default function HierarchyColumnTab() { // 상태 const [groups, setGroups] = useState([]); const [loading, setLoading] = useState(true); const [modalOpen, setModalOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [selectedGroup, setSelectedGroup] = useState(null); const [isEditing, setIsEditing] = useState(false); // 폼 상태 const [formData, setFormData] = useState({ groupCode: "", groupName: "", description: "", codeCategory: "", tableName: "", maxDepth: 3, mappings: [ { depth: 1, levelLabel: "대분류", columnName: "", placeholder: "대분류 선택", isRequired: true }, { depth: 2, levelLabel: "중분류", columnName: "", placeholder: "중분류 선택", isRequired: false }, { depth: 3, levelLabel: "소분류", columnName: "", placeholder: "소분류 선택", isRequired: false }, ], }); // 참조 데이터 const [tables, setTables] = useState([]); const [columns, setColumns] = useState([]); const [categories, setCategories] = useState([]); const [loadingTables, setLoadingTables] = useState(false); const [loadingColumns, setLoadingColumns] = useState(false); const [loadingCategories, setLoadingCategories] = useState(false); // 그룹 목록 로드 const loadGroups = useCallback(async () => { setLoading(true); try { const response = await hierarchyColumnApi.getAll(); if (response.success && response.data) { setGroups(response.data); } else { toast.error(response.error || "계층구조 그룹 로드 실패"); } } catch (error) { console.error("계층구조 그룹 로드 에러:", error); toast.error("계층구조 그룹을 로드하는 중 오류가 발생했습니다."); } finally { setLoading(false); } }, []); // 테이블 목록 로드 const loadTables = useCallback(async () => { setLoadingTables(true); try { const response = await apiClient.get("/table-management/tables"); if (response.data?.success && response.data?.data) { setTables(response.data.data); } } catch (error) { console.error("테이블 로드 에러:", error); } finally { setLoadingTables(false); } }, []); // 카테고리 목록 로드 const loadCategories = useCallback(async () => { setLoadingCategories(true); try { const response = await commonCodeApi.categories.getList(); if (response.success && response.data) { setCategories( response.data.map((cat: any) => ({ categoryCode: cat.categoryCode || cat.category_code, categoryName: cat.categoryName || cat.category_name, })) ); } } catch (error) { console.error("카테고리 로드 에러:", error); } finally { setLoadingCategories(false); } }, []); // 테이블 선택 시 컬럼 로드 const loadColumns = useCallback(async (tableName: string) => { if (!tableName) { setColumns([]); return; } setLoadingColumns(true); try { const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); if (response.data?.success && response.data?.data) { setColumns(response.data.data); } } catch (error) { console.error("컬럼 로드 에러:", error); } finally { setLoadingColumns(false); } }, []); // 초기 로드 useEffect(() => { loadGroups(); loadTables(); loadCategories(); }, [loadGroups, loadTables, loadCategories]); // 테이블 선택 변경 시 컬럼 로드 useEffect(() => { if (formData.tableName) { loadColumns(formData.tableName); } }, [formData.tableName, loadColumns]); // 폼 초기화 const resetForm = () => { setFormData({ groupCode: "", groupName: "", description: "", codeCategory: "", tableName: "", maxDepth: 3, mappings: [ { depth: 1, levelLabel: "대분류", columnName: "", placeholder: "대분류 선택", isRequired: true }, { depth: 2, levelLabel: "중분류", columnName: "", placeholder: "중분류 선택", isRequired: false }, { depth: 3, levelLabel: "소분류", columnName: "", placeholder: "소분류 선택", isRequired: false }, ], }); setSelectedGroup(null); setIsEditing(false); }; // 모달 열기 (신규) const openCreateModal = () => { resetForm(); setModalOpen(true); }; // 모달 열기 (수정) const openEditModal = (group: HierarchyColumnGroup) => { setSelectedGroup(group); setIsEditing(true); // 매핑 데이터 변환 const mappings = [1, 2, 3].map((depth) => { const existing = group.mappings?.find((m) => m.depth === depth); return { depth, levelLabel: existing?.level_label || (depth === 1 ? "대분류" : depth === 2 ? "중분류" : "소분류"), columnName: existing?.column_name || "", placeholder: existing?.placeholder || `${depth === 1 ? "대분류" : depth === 2 ? "중분류" : "소분류"} 선택`, isRequired: existing?.is_required === "Y", }; }); setFormData({ groupCode: group.group_code, groupName: group.group_name, description: group.description || "", codeCategory: group.code_category, tableName: group.table_name, maxDepth: group.max_depth, mappings, }); // 컬럼 로드 loadColumns(group.table_name); setModalOpen(true); }; // 삭제 확인 열기 const openDeleteDialog = (group: HierarchyColumnGroup) => { setSelectedGroup(group); setDeleteDialogOpen(true); }; // 저장 const handleSave = async () => { // 필수 필드 검증 if (!formData.groupCode || !formData.groupName || !formData.codeCategory || !formData.tableName) { toast.error("필수 필드를 모두 입력해주세요."); return; } // 최소 1개 컬럼 매핑 검증 const validMappings = formData.mappings .filter((m) => m.depth <= formData.maxDepth && m.columnName) .map((m) => ({ depth: m.depth, levelLabel: m.levelLabel, columnName: m.columnName, placeholder: m.placeholder, isRequired: m.isRequired, })); if (validMappings.length === 0) { toast.error("최소 하나의 컬럼 매핑이 필요합니다."); return; } try { if (isEditing && selectedGroup) { // 수정 const response = await hierarchyColumnApi.update(selectedGroup.group_id, { groupName: formData.groupName, description: formData.description, maxDepth: formData.maxDepth, mappings: validMappings, }); if (response.success) { toast.success("계층구조 그룹이 수정되었습니다."); setModalOpen(false); loadGroups(); } else { toast.error(response.error || "수정 실패"); } } else { // 생성 const request: CreateHierarchyGroupRequest = { groupCode: formData.groupCode, groupName: formData.groupName, description: formData.description, codeCategory: formData.codeCategory, tableName: formData.tableName, maxDepth: formData.maxDepth, mappings: validMappings, }; const response = await hierarchyColumnApi.create(request); if (response.success) { toast.success("계층구조 그룹이 생성되었습니다."); setModalOpen(false); loadGroups(); } else { toast.error(response.error || "생성 실패"); } } } catch (error) { console.error("저장 에러:", error); toast.error("저장 중 오류가 발생했습니다."); } }; // 삭제 const handleDelete = async () => { if (!selectedGroup) return; try { const response = await hierarchyColumnApi.delete(selectedGroup.group_id); if (response.success) { toast.success("계층구조 그룹이 삭제되었습니다."); setDeleteDialogOpen(false); loadGroups(); } else { toast.error(response.error || "삭제 실패"); } } catch (error) { console.error("삭제 에러:", error); toast.error("삭제 중 오류가 발생했습니다."); } }; // 매핑 컬럼 변경 const handleMappingChange = (depth: number, field: string, value: any) => { setFormData((prev) => ({ ...prev, mappings: prev.mappings.map((m) => m.depth === depth ? { ...m, [field]: value } : m ), })); }; return (
{/* 헤더 */}

계층구조 컬럼 그룹

공통코드 계층구조를 테이블 컬럼에 매핑하여 대분류/중분류/소분류를 각각 별도 컬럼에 저장합니다.

{/* 그룹 목록 */} {loading ? (
로딩 중...
) : groups.length === 0 ? (

계층구조 컬럼 그룹이 없습니다.

) : (
{groups.map((group) => (
{group.group_name} {group.group_code}
{group.table_name}
{group.code_category} {group.max_depth}단계
{group.mappings && group.mappings.length > 0 && (
{group.mappings.map((mapping) => (
{mapping.level_label} {mapping.column_name}
))}
)}
))}
)} {/* 생성/수정 모달 */} {isEditing ? "계층구조 그룹 수정" : "계층구조 그룹 생성"} 공통코드 계층구조를 테이블 컬럼에 매핑합니다.
{/* 기본 정보 */}
setFormData({ ...formData, groupCode: e.target.value.toUpperCase() })} placeholder="예: ITEM_CAT_HIERARCHY" disabled={isEditing} />
setFormData({ ...formData, groupName: e.target.value })} placeholder="예: 품목분류 계층" />
setFormData({ ...formData, description: e.target.value })} placeholder="계층구조에 대한 설명" />
{/* 컬럼 매핑 */}

각 계층 레벨에 저장할 컬럼을 선택합니다.

{formData.mappings .filter((m) => m.depth <= formData.maxDepth) .map((mapping) => (
{mapping.depth}단계 handleMappingChange(mapping.depth, "levelLabel", e.target.value)} className="h-8 text-xs" placeholder="라벨" />
handleMappingChange(mapping.depth, "placeholder", e.target.value)} className="h-8 text-xs" placeholder="플레이스홀더" />
handleMappingChange(mapping.depth, "isRequired", e.target.checked)} className="h-4 w-4" /> 필수
))}
{/* 삭제 확인 다이얼로그 */} 계층구조 그룹 삭제 "{selectedGroup?.group_name}" 그룹을 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다.
); }