"use client"; import React, { useState, useCallback, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Plus, Save, Edit2, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { NumberingRuleConfig, NumberingRulePart } from "@/types/numbering-rule"; import { NumberingRuleCard } from "./NumberingRuleCard"; import { NumberingRulePreview } from "./NumberingRulePreview"; import { getNumberingRules, createNumberingRule, updateNumberingRule, deleteNumberingRule, } from "@/lib/api/numberingRule"; interface NumberingRuleDesignerProps { initialConfig?: NumberingRuleConfig; onSave?: (config: NumberingRuleConfig) => void; onChange?: (config: NumberingRuleConfig) => void; maxRules?: number; isPreview?: boolean; className?: string; currentTableName?: string; // 현재 화면의 테이블명 (자동 감지용) menuObjid?: number; // 현재 메뉴 OBJID (메뉴 스코프) } export const NumberingRuleDesigner: React.FC = ({ initialConfig, onSave, onChange, maxRules = 6, isPreview = false, className = "", currentTableName, menuObjid, }) => { const [savedRules, setSavedRules] = useState([]); const [selectedRuleId, setSelectedRuleId] = useState(null); const [currentRule, setCurrentRule] = useState(null); const [loading, setLoading] = useState(false); const [leftTitle, setLeftTitle] = useState("저장된 규칙 목록"); const [rightTitle, setRightTitle] = useState("규칙 편집"); const [editingLeftTitle, setEditingLeftTitle] = useState(false); const [editingRightTitle, setEditingRightTitle] = useState(false); useEffect(() => { loadRules(); }, []); const loadRules = useCallback(async () => { setLoading(true); try { const response = await getNumberingRules(menuObjid); if (response.success && response.data) { setSavedRules(response.data); } else { toast.error(response.error || "규칙 목록을 불러올 수 없습니다"); } } catch (error: any) { toast.error(`로딩 실패: ${error.message}`); } finally { setLoading(false); } }, [menuObjid]); useEffect(() => { if (currentRule) { onChange?.(currentRule); } }, [currentRule, onChange]); const handleAddPart = useCallback(() => { if (!currentRule) return; if (currentRule.parts.length >= maxRules) { toast.error(`최대 ${maxRules}개까지 추가할 수 있습니다`); return; } const newPart: NumberingRulePart = { id: `part-${Date.now()}`, order: currentRule.parts.length + 1, partType: "text", generationMethod: "auto", autoConfig: { textValue: "CODE" }, }; setCurrentRule((prev) => { if (!prev) return null; return { ...prev, parts: [...prev.parts, newPart] }; }); toast.success(`규칙 ${newPart.order}가 추가되었습니다`); }, [currentRule, maxRules]); const handleUpdatePart = useCallback((partId: string, updates: Partial) => { setCurrentRule((prev) => { if (!prev) return null; return { ...prev, parts: prev.parts.map((part) => (part.id === partId ? { ...part, ...updates } : part)), }; }); }, []); const handleDeletePart = useCallback((partId: string) => { setCurrentRule((prev) => { if (!prev) return null; return { ...prev, parts: prev.parts.filter((part) => part.id !== partId).map((part, index) => ({ ...part, order: index + 1 })), }; }); toast.success("규칙이 삭제되었습니다"); }, []); const handleSave = useCallback(async () => { if (!currentRule) { toast.error("저장할 규칙이 없습니다"); return; } if (currentRule.parts.length === 0) { toast.error("최소 1개 이상의 규칙을 추가해주세요"); return; } setLoading(true); try { const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId); // 저장 전에 현재 화면의 테이블명 자동 설정 const ruleToSave = { ...currentRule, scopeType: "table" as const, // 항상 table로 고정 tableName: currentTableName || currentRule.tableName || "", // 현재 테이블명 자동 설정 }; console.log("💾 채번 규칙 저장:", { currentTableName, "currentRule.tableName": currentRule.tableName, "ruleToSave.tableName": ruleToSave.tableName, "ruleToSave.scopeType": ruleToSave.scopeType, ruleToSave, }); let response; if (existing) { response = await updateNumberingRule(ruleToSave.ruleId, ruleToSave); } else { response = await createNumberingRule(ruleToSave); } if (response.success && response.data) { setSavedRules((prev) => { if (existing) { return prev.map((r) => (r.ruleId === ruleToSave.ruleId ? response.data! : r)); } else { return [...prev, response.data!]; } }); setCurrentRule(response.data); setSelectedRuleId(response.data.ruleId); await onSave?.(response.data); toast.success("채번 규칙이 저장되었습니다"); } else { toast.error(response.error || "저장 실패"); } } catch (error: any) { toast.error(`저장 실패: ${error.message}`); } finally { setLoading(false); } }, [currentRule, savedRules, onSave, currentTableName]); const handleSelectRule = useCallback((rule: NumberingRuleConfig) => { setSelectedRuleId(rule.ruleId); setCurrentRule(rule); toast.info(`"${rule.ruleName}" 규칙을 불러왔습니다`); }, []); const handleDeleteSavedRule = useCallback( async (ruleId: string) => { setLoading(true); try { const response = await deleteNumberingRule(ruleId); if (response.success) { setSavedRules((prev) => prev.filter((r) => r.ruleId !== ruleId)); if (selectedRuleId === ruleId) { setSelectedRuleId(null); setCurrentRule(null); } toast.success("규칙이 삭제되었습니다"); } else { toast.error(response.error || "삭제 실패"); } } catch (error: any) { toast.error(`삭제 실패: ${error.message}`); } finally { setLoading(false); } }, [selectedRuleId], ); const handleNewRule = useCallback(() => { console.log("📋 새 규칙 생성 - currentTableName:", currentTableName); const newRule: NumberingRuleConfig = { ruleId: `rule-${Date.now()}`, ruleName: "새 채번 규칙", parts: [], separator: "-", resetPeriod: "none", currentSequence: 1, scopeType: "table", // 기본값을 table로 설정 tableName: currentTableName || "", // 현재 화면의 테이블명 자동 설정 }; console.log("📋 생성된 규칙 정보:", newRule); setSelectedRuleId(newRule.ruleId); setCurrentRule(newRule); toast.success("새 규칙이 생성되었습니다"); }, [currentTableName]); return (
{/* 좌측: 저장된 규칙 목록 */}
{editingLeftTitle ? ( setLeftTitle(e.target.value)} onBlur={() => setEditingLeftTitle(false)} onKeyDown={(e) => e.key === "Enter" && setEditingLeftTitle(false)} className="h-8 text-sm font-semibold" autoFocus /> ) : (

{leftTitle}

)}
{loading ? (

로딩 중...

) : savedRules.length === 0 ? (

저장된 규칙이 없습니다

) : ( savedRules.map((rule) => ( handleSelectRule(rule)} >
{rule.ruleName}
)) )}
{/* 구분선 */}
{/* 우측: 편집 영역 */}
{!currentRule ? (

규칙을 선택해주세요

좌측에서 규칙을 선택하거나 새로 생성하세요

) : ( <>
{editingRightTitle ? ( setRightTitle(e.target.value)} onBlur={() => setEditingRightTitle(false)} onKeyDown={(e) => e.key === "Enter" && setEditingRightTitle(false)} className="h-8 text-sm font-semibold" autoFocus /> ) : (

{rightTitle}

)}
{/* 첫 번째 줄: 규칙명 + 미리보기 */}
setCurrentRule((prev) => ({ ...prev!, ruleName: e.target.value }))} className="h-9" placeholder="예: 프로젝트 코드" />
{/* 두 번째 줄: 자동 감지된 테이블 정보 표시 */} {currentTableName && (
{currentTableName}

이 규칙은 현재 화면의 테이블({currentTableName})에 자동으로 적용됩니다

)}

코드 구성

{currentRule.parts.length}/{maxRules}
{currentRule.parts.length === 0 ? (

규칙을 추가하여 코드를 구성하세요

) : (
{currentRule.parts.map((part) => ( handleUpdatePart(part.id, updates)} onDelete={() => handleDeletePart(part.id)} isPreview={isPreview} /> ))}
)}
)}
); };