"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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import {
Plus,
Trash2,
GripVertical,
ChevronUp,
ChevronDown,
Settings,
Database,
Layout,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
import { getNumberingRules } from "@/lib/api/numberingRule";
import {
UniversalFormModalConfig,
UniversalFormModalConfigPanelProps,
FormSectionConfig,
FormFieldConfig,
MODAL_SIZE_OPTIONS,
} from "./types";
import {
defaultSectionConfig,
generateSectionId,
} from "./config";
// 모달 import
import { FieldDetailSettingsModal } from "./modals/FieldDetailSettingsModal";
import { SaveSettingsModal } from "./modals/SaveSettingsModal";
import { SectionLayoutModal } from "./modals/SectionLayoutModal";
// 도움말 텍스트 컴포넌트
const HelpText = ({ children }: { children: React.ReactNode }) => (
{children}
);
export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFormModalConfigPanelProps) {
// 테이블 목록
const [tables, setTables] = useState<{ name: string; label: string }[]>([]);
const [tableColumns, setTableColumns] = useState<{
[tableName: string]: { name: string; type: string; label: string }[];
}>({});
// 채번규칙 목록
const [numberingRules, setNumberingRules] = useState<{ id: string; name: string }[]>([]);
// 모달 상태
const [saveSettingsModalOpen, setSaveSettingsModalOpen] = useState(false);
const [sectionLayoutModalOpen, setSectionLayoutModalOpen] = useState(false);
const [fieldDetailModalOpen, setFieldDetailModalOpen] = useState(false);
const [selectedSection, setSelectedSection] = useState(null);
const [selectedField, setSelectedField] = useState(null);
// 테이블 목록 로드
useEffect(() => {
loadTables();
loadNumberingRules();
}, []);
// 저장 테이블 변경 시 컬럼 로드
useEffect(() => {
if (config.saveConfig.tableName) {
loadTableColumns(config.saveConfig.tableName);
}
}, [config.saveConfig.tableName]);
const loadTables = async () => {
try {
const response = await apiClient.get("/table-management/tables");
const data = response.data?.data;
if (response.data?.success && Array.isArray(data)) {
setTables(
data.map((t: { tableName?: string; table_name?: string; tableLabel?: string; table_label?: string }) => ({
name: t.tableName || t.table_name || "",
label: t.tableLabel || t.table_label || t.tableName || t.table_name || "",
})),
);
}
} catch (error) {
console.error("테이블 목록 로드 실패:", error);
}
};
const loadTableColumns = async (tableName: string) => {
if (!tableName || (tableColumns[tableName] && tableColumns[tableName].length > 0)) return;
try {
const response = await apiClient.get(`/table-management/tables/${tableName}/columns`);
const data = response.data?.data;
if (response.data?.success && Array.isArray(data)) {
setTableColumns((prev) => ({
...prev,
[tableName]: data.map(
(c: {
columnName?: string;
column_name?: string;
dataType?: string;
data_type?: string;
columnComment?: string;
column_comment?: string;
}) => ({
name: c.columnName || c.column_name || "",
type: c.dataType || c.data_type || "text",
label: c.columnComment || c.column_comment || c.columnName || c.column_name || "",
}),
),
}));
}
} catch (error) {
console.error(`테이블 컬럼 로드 실패 (${tableName}):`, error);
}
};
const loadNumberingRules = async () => {
try {
const response = await getNumberingRules();
const data = response?.data;
if (response?.success && Array.isArray(data)) {
const rules = data.map(
(r: {
id?: string | number;
ruleId?: string;
rule_id?: string;
name?: string;
ruleName?: string;
rule_name?: string;
}) => ({
id: String(r.id || r.ruleId || r.rule_id || ""),
name: r.name || r.ruleName || r.rule_name || "",
}),
);
setNumberingRules(rules);
}
} catch (error) {
console.error("채번규칙 목록 로드 실패:", error);
}
};
// 설정 업데이트 헬퍼
const updateModalConfig = useCallback(
(updates: Partial) => {
onChange({
...config,
modal: { ...config.modal, ...updates },
});
},
[config, onChange],
);
// 섹션 관리
const addSection = useCallback(() => {
const newSection: FormSectionConfig = {
...defaultSectionConfig,
id: generateSectionId(),
title: `섹션 ${config.sections.length + 1}`,
};
onChange({
...config,
sections: [...config.sections, newSection],
});
}, [config, onChange]);
const updateSection = useCallback(
(sectionId: string, updates: Partial) => {
onChange({
...config,
sections: config.sections.map((s) => (s.id === sectionId ? { ...s, ...updates } : s)),
});
},
[config, onChange],
);
const removeSection = useCallback(
(sectionId: string) => {
onChange({
...config,
sections: config.sections.filter((s) => s.id !== sectionId),
});
},
[config, onChange],
);
const moveSectionUp = useCallback(
(index: number) => {
if (index <= 0) return;
const newSections = [...config.sections];
[newSections[index - 1], newSections[index]] = [newSections[index], newSections[index - 1]];
onChange({ ...config, sections: newSections });
},
[config, onChange],
);
const moveSectionDown = useCallback(
(index: number) => {
if (index >= config.sections.length - 1) return;
const newSections = [...config.sections];
[newSections[index], newSections[index + 1]] = [newSections[index + 1], newSections[index]];
onChange({ ...config, sections: newSections });
},
[config, onChange],
);
// 필드 타입별 색상
const getFieldTypeColor = (fieldType: FormFieldConfig["fieldType"]): string => {
switch (fieldType) {
case "text":
case "email":
case "password":
case "tel":
return "text-blue-600 bg-blue-50 border-blue-200";
case "number":
return "text-cyan-600 bg-cyan-50 border-cyan-200";
case "date":
case "datetime":
return "text-purple-600 bg-purple-50 border-purple-200";
case "select":
return "text-green-600 bg-green-50 border-green-200";
case "checkbox":
return "text-pink-600 bg-pink-50 border-pink-200";
case "textarea":
return "text-orange-600 bg-orange-50 border-orange-200";
default:
return "text-gray-600 bg-gray-50 border-gray-200";
}
};
// 섹션 레이아웃 모달 열기
const handleOpenSectionLayout = (section: FormSectionConfig) => {
setSelectedSection(section);
setSectionLayoutModalOpen(true);
};
// 필드 상세 설정 모달 열기
const handleOpenFieldDetail = (section: FormSectionConfig, field: FormFieldConfig) => {
setSelectedSection(section);
setSelectedField(field);
setFieldDetailModalOpen(true);
};
return (
{/* 모달 기본 설정 */}
모달 기본 설정
updateModalConfig({ title: e.target.value })}
className="h-9 text-sm w-full max-w-full"
/>
모달 상단에 표시될 제목입니다
모달 창의 크기를 선택하세요
{/* 저장 설정 */}
저장 설정
{config.saveConfig.tableName || "(미설정)"}
{config.saveConfig.customApiSave?.enabled && config.saveConfig.customApiSave?.multiTable?.enabled && (
다중 테이블 모드
)}
데이터를 저장할 테이블과 방식을 설정합니다.
"저장 설정 열기"를 클릭하여 상세 설정을 변경하세요.
{/* 섹션 구성 */}
섹션 구성
{config.sections.length}개
폼을 여러 섹션으로 나누어 구성할 수 있습니다.
예: 기본 정보, 배송 정보, 결제 정보
{config.sections.length === 0 ? (
섹션이 없습니다
"섹션 추가" 버튼으로 폼 섹션을 만드세요
) : (
{config.sections.map((section, index) => (
{/* 헤더: 제목 + 삭제 */}
{section.title}
{section.repeatable && (
반복
)}
{section.fields.length}개 필드
{/* 순서 조정 버튼 */}
{/* 필드 목록 */}
{section.fields.length > 0 && (
{section.fields.slice(0, 4).map((field) => (
{field.label}
))}
{section.fields.length > 4 && (
+{section.fields.length - 4}
)}
)}
{/* 레이아웃 설정 버튼 */}
))}
)}
{/* 저장 설정 모달 */}
{
onChange({
...config,
saveConfig: updates,
});
}}
tables={tables}
tableColumns={tableColumns}
onLoadTableColumns={loadTableColumns}
/>
{/* 섹션 레이아웃 모달 */}
{selectedSection && (
{
updateSection(selectedSection.id, updates);
setSectionLayoutModalOpen(false);
}}
onOpenFieldDetail={(field) => {
setSectionLayoutModalOpen(false);
setSelectedField(field);
setFieldDetailModalOpen(true);
}}
/>
)}
{/* 필드 상세 설정 모달 */}
{selectedSection && selectedField && (
{
setFieldDetailModalOpen(open);
if (!open) {
// 필드 상세 모달을 닫으면 섹션 레이아웃 모달을 다시 엽니다
setSectionLayoutModalOpen(true);
}
}}
field={selectedField}
onSave={(updates) => {
onChange({
...config,
sections: config.sections.map((s) =>
s.id === selectedSection.id
? {
...s,
fields: s.fields.map((f) => (f.id === selectedField.id ? { ...f, ...updates } : f)),
}
: s,
),
});
setFieldDetailModalOpen(false);
setSectionLayoutModalOpen(true);
}}
tables={tables}
tableColumns={tableColumns}
numberingRules={numberingRules}
onLoadTableColumns={loadTableColumns}
/>
)}
);
}