diff --git a/frontend/components/screen/FloatingPanel.tsx b/frontend/components/screen/FloatingPanel.tsx index cddb053b..d6efd8b1 100644 --- a/frontend/components/screen/FloatingPanel.tsx +++ b/frontend/components/screen/FloatingPanel.tsx @@ -267,7 +267,7 @@ export const FloatingPanel: React.FC = ({ {/* 컨텐츠 */}
= ({ }; return ( -
+

{definition.name} 설정

@@ -998,7 +998,7 @@ export const DetailSettingsPanel: React.FC = ({
{/* 설정 패널 영역 */} -
{renderComponentConfigPanel()}
+
{renderComponentConfigPanel()}
); } @@ -1156,8 +1156,8 @@ export const DetailSettingsPanel: React.FC = ({
{/* 컴포넌트 설정 패널 */} -
-
+
+
{/* DynamicComponentConfigPanel */} = ({
{/* 상세 설정 영역 */} -
-
+
+
{console.log("🔍 [DetailSettingsPanel] widget 타입:", selectedComponent?.type, "autoFill:", widget.autoFill)} {/* 🆕 자동 입력 섹션 */}
diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx index 48542342..656f3f1a 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx @@ -4,13 +4,9 @@ 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 { Switch } from "@/components/ui/switch"; -import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; -import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Checkbox } from "@/components/ui/checkbox"; -import { ScrollArea } from "@/components/ui/scroll-area"; +import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { Plus, @@ -21,37 +17,27 @@ import { Settings, Database, Layout, - Hash, - Check, - ChevronsUpDown, } from "lucide-react"; -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; -import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; import { getNumberingRules } from "@/lib/api/numberingRule"; - import { UniversalFormModalConfig, UniversalFormModalConfigPanelProps, FormSectionConfig, FormFieldConfig, - LinkedFieldMapping, - FIELD_TYPE_OPTIONS, MODAL_SIZE_OPTIONS, - SELECT_OPTION_TYPE_OPTIONS, - LINKED_FIELD_DISPLAY_FORMAT_OPTIONS, } from "./types"; import { - defaultFieldConfig, defaultSectionConfig, - defaultNumberingRuleConfig, - defaultSelectOptionsConfig, generateSectionId, - generateFieldId, } 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}

@@ -67,16 +53,15 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor // 채번규칙 목록 const [numberingRules, setNumberingRules] = useState<{ id: string; name: string }[]>([]); - // 선택된 섹션/필드 - const [selectedSectionId, setSelectedSectionId] = useState(null); - const [selectedFieldId, setSelectedFieldId] = useState(null); - - // 테이블 선택 Combobox 상태 - const [tableSelectOpen, setTableSelectOpen] = useState(false); + // 모달 상태 + 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(() => { - console.log("[UniversalFormModal ConfigPanel] 초기화 - 테이블 및 채번규칙 로드"); loadTables(); loadNumberingRules(); }, []); @@ -86,47 +71,8 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor if (config.saveConfig.tableName) { loadTableColumns(config.saveConfig.tableName); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [config.saveConfig.tableName]); - // 다중 컬럼 저장의 소스 테이블 컬럼 로드 - useEffect(() => { - const allSourceTables = new Set(); - config.sections.forEach((section) => { - // 필드 레벨의 linkedFieldGroup 확인 - section.fields.forEach((field) => { - if (field.linkedFieldGroup?.sourceTable) { - allSourceTables.add(field.linkedFieldGroup.sourceTable); - } - }); - }); - allSourceTables.forEach((tableName) => { - if (!tableColumns[tableName]) { - loadTableColumns(tableName); - } - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.sections]); - - // 다중 테이블 저장 설정의 메인/서브 테이블 컬럼 로드 - useEffect(() => { - const customApiSave = config.saveConfig.customApiSave; - if (customApiSave?.enabled && customApiSave?.multiTable) { - // 메인 테이블 컬럼 로드 - const mainTableName = customApiSave.multiTable.mainTable?.tableName; - if (mainTableName && !tableColumns[mainTableName]) { - loadTableColumns(mainTableName); - } - // 서브 테이블들 컬럼 로드 - customApiSave.multiTable.subTables?.forEach((subTable) => { - if (subTable.tableName && !tableColumns[subTable.tableName]) { - loadTableColumns(subTable.tableName); - } - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [config.saveConfig.customApiSave]); - const loadTables = async () => { try { const response = await apiClient.get("/table-management/tables"); @@ -145,18 +91,13 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor }; const loadTableColumns = async (tableName: string) => { - console.log(`[UniversalFormModal] 테이블 컬럼 로드 시도: ${tableName}`); if (!tableName || (tableColumns[tableName] && tableColumns[tableName].length > 0)) return; try { const response = await apiClient.get(`/table-management/tables/${tableName}/columns`); - console.log("[UniversalFormModal] 테이블 컬럼 응답:", response.data); - - // API 응답 구조: { success: true, data: { columns: [...] } } - const data = response.data?.data?.columns || response.data?.data; + const data = response.data?.data; if (response.data?.success && Array.isArray(data)) { - console.log(`[UniversalFormModal] 파싱된 컬럼 ${data.length}개:`, data); setTableColumns((prev) => ({ ...prev, [tableName]: data.map( @@ -165,36 +106,27 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor column_name?: string; dataType?: string; data_type?: string; - columnLabel?: string; - column_label?: string; - name?: string; + columnComment?: string; + column_comment?: string; }) => ({ - name: c.columnName || c.column_name || c.name || "", - type: c.dataType || c.data_type || "", - label: c.columnLabel || c.column_label || c.columnName || c.column_name || c.name || "", + name: c.columnName || c.column_name || "", + type: c.dataType || c.data_type || "text", + label: c.columnComment || c.column_comment || c.columnName || c.column_name || "", }), ), })); - } else { - console.warn("[UniversalFormModal] 컬럼 데이터 없음 또는 형식 오류:", response.data); - setTableColumns((prev) => ({ ...prev, [tableName]: [] })); } } catch (error) { console.error(`테이블 컬럼 로드 실패 (${tableName}):`, error); - setTableColumns((prev) => ({ ...prev, [tableName]: [] })); } }; const loadNumberingRules = async () => { try { - console.log("[UniversalFormModal] 채번규칙 로드 시도"); const response = await getNumberingRules(); - console.log("[UniversalFormModal] 채번규칙 응답:", response); + const data = response?.data; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const data: any = response.data; - - if (response.success && Array.isArray(data) && data.length > 0) { + if (response?.success && Array.isArray(data)) { const rules = data.map( (r: { id?: string | number; @@ -208,13 +140,10 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor name: r.name || r.ruleName || r.rule_name || "", }), ); - console.log("[UniversalFormModal] 파싱된 채번규칙:", rules); setNumberingRules(rules); - } else { - console.warn("[UniversalFormModal] 채번규칙 데이터 없음:", data); } } catch (error) { - console.error("[UniversalFormModal] 채번규칙 목록 로드 실패:", error); + console.error("채번규칙 목록 로드 실패:", error); } }; @@ -229,16 +158,6 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor [config, onChange], ); - const updateSaveConfig = useCallback( - (updates: Partial) => { - onChange({ - ...config, - saveConfig: { ...config.saveConfig, ...updates }, - }); - }, - [config, onChange], - ); - // 섹션 관리 const addSection = useCallback(() => { const newSection: FormSectionConfig = { @@ -250,7 +169,6 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor ...config, sections: [...config.sections, newSection], }); - setSelectedSectionId(newSection.id); }, [config, onChange]); const updateSection = useCallback( @@ -269,12 +187,8 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor ...config, sections: config.sections.filter((s) => s.id !== sectionId), }); - if (selectedSectionId === sectionId) { - setSelectedSectionId(null); - setSelectedFieldId(null); - } }, - [config, onChange, selectedSectionId], + [config, onChange], ); const moveSectionUp = useCallback( @@ -297,99 +211,71 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor [config, onChange], ); - // 필드 관리 - const addField = useCallback( - (sectionId: string) => { - const newField: FormFieldConfig = { - ...defaultFieldConfig, - id: generateFieldId(), - label: "새 필드", - numberingRule: { ...defaultNumberingRuleConfig }, - selectOptions: { ...defaultSelectOptionsConfig }, - }; - onChange({ - ...config, - sections: config.sections.map((s) => - s.id === sectionId ? { ...s, fields: [...s.fields, newField] } : s, - ), - }); - setSelectedSectionId(sectionId); - setSelectedFieldId(newField.id); - }, - [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 updateField = useCallback( - (sectionId: string, fieldId: string, updates: Partial) => { - onChange({ - ...config, - sections: config.sections.map((s) => - s.id === sectionId - ? { - ...s, - fields: s.fields.map((f) => (f.id === fieldId ? { ...f, ...updates } : f)), - } - : s, - ), - }); - }, - [config, onChange], - ); + // 섹션 레이아웃 모달 열기 + const handleOpenSectionLayout = (section: FormSectionConfig) => { + setSelectedSection(section); + setSectionLayoutModalOpen(true); + }; - const removeField = useCallback( - (sectionId: string, fieldId: string) => { - onChange({ - ...config, - sections: config.sections.map((s) => - s.id === sectionId ? { ...s, fields: s.fields.filter((f) => f.id !== fieldId) } : s, - ), - }); - if (selectedFieldId === fieldId) { - setSelectedFieldId(null); - } - }, - [config, onChange, selectedFieldId], - ); - - // 선택된 섹션/필드 가져오기 - const selectedSection = config.sections.find((s) => s.id === selectedSectionId); - const selectedField = selectedSection?.fields.find((f) => f.id === selectedFieldId); - - // 현재 테이블의 컬럼 목록 - const currentColumns = tableColumns[config.saveConfig.tableName] || []; + // 필드 상세 설정 모달 열기 + const handleOpenFieldDetail = (section: FormSectionConfig, field: FormFieldConfig) => { + setSelectedSection(section); + setSelectedField(field); + setFieldDetailModalOpen(true); + }; return ( - -
+
+
+
{/* 모달 기본 설정 */} - - - -
- - 모달 기본 설정 + + + +
+ + 모달 기본 설정
- -
- + +
+ updateModalConfig({ title: e.target.value })} - placeholder="모달 제목 입력" - className="h-7 text-xs mt-1" + className="h-9 text-sm w-full max-w-full" /> + 모달 상단에 표시될 제목입니다
-
- - updateModalConfig({ size: value })}> + @@ -400,1906 +286,263 @@ export function UniversalFormModalConfigPanel({ config, onChange }: UniversalFor ))} + 모달 창의 크기를 선택하세요
-
-
- 저장 버튼 표시 - updateModalConfig({ showSaveButton: checked })} +
+
+ + updateModalConfig({ saveButtonText: e.target.value })} + className="h-9 text-sm w-full max-w-full" + /> +
+
+ + updateModalConfig({ cancelButtonText: e.target.value })} + className="h-9 text-sm w-full max-w-full" />
- ButtonPrimary 컴포넌트로 저장 버튼을 별도 구성할 경우 끄세요 - - {config.modal.showSaveButton !== false && ( -
- - updateModalConfig({ saveButtonText: e.target.value })} - className="h-7 text-xs mt-1" - /> -
- )}
{/* 저장 설정 */} - - - -
- - 저장 설정 + + + +
+ + 저장 설정
- - {/* 저장 테이블 - Combobox */} -
- - {config.saveConfig.customApiSave?.enabled ? ( -
- 전용 API 저장 모드에서는 API가 테이블 저장을 처리합니다. - {config.saveConfig.customApiSave?.apiType === "user-with-dept" && ( - 대상 테이블: user_info + user_dept - )} -
- ) : ( - <> - - - - - - - - - 테이블을 찾을 수 없습니다 - - {tables.map((t) => ( - { - updateSaveConfig({ tableName: t.name }); - setTableSelectOpen(false); - }} - className="text-xs" - > - - {t.name} - {t.label !== t.name && ( - ({t.label}) - )} - - ))} - - - - - - {config.saveConfig.tableName && ( -

- 컬럼 {currentColumns.length}개 로드됨 + +

+
+ +

+ {config.saveConfig.tableName || "(미설정)"}

- )} - - )} -
- - {/* 다중 행 저장 설정 - 전용 API 모드에서는 숨김 */} - {!config.saveConfig.customApiSave?.enabled && ( -
-
- 다중 행 저장 - - updateSaveConfig({ - multiRowSave: { ...config.saveConfig.multiRowSave, enabled: checked }, - }) - } - /> -
- 겸직처럼 하나의 폼에서 여러 행을 저장할 때 사용합니다. - - {config.saveConfig.multiRowSave?.enabled && ( -
- {/* 공통 필드 선택 */} -
- -
- {config.sections - .filter((s) => !s.repeatable) - .flatMap((s) => s.fields) - .map((field) => ( - - ))} -
- 메인 행과 겸직 행 모두에 저장될 필드 -
- - {/* 메인 섹션 필드 선택 */} -
- -
- {config.sections - .filter((s) => !s.repeatable) - .flatMap((s) => s.fields) - .filter((field) => !config.saveConfig.multiRowSave?.commonFields?.includes(field.columnName)) - .map((field) => ( - - ))} -
- 메인 행에만 저장될 필드 (공통 필드 제외) -
- - {/* 반복 섹션 선택 */} -
- - - 겸직 등 반복 데이터가 있는 섹션 -
-
- )} -
- )} - - {/* 다중 테이블 저장 설정 (범용) */} -
-
- 다중 테이블 저장 - - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - enabled: checked, - apiType: "multi-table", - multiTable: checked ? { - enabled: true, - mainTable: { tableName: config.saveConfig.tableName || "", primaryKeyColumn: "" }, - subTables: [], - } : undefined, - }, - }) - } - /> -
- - 메인 테이블 + 서브 테이블(반복 섹션)에 트랜잭션으로 저장합니다. -
예: 사원+부서, 주문+주문상세, 프로젝트+멤버 등 -
- - {config.saveConfig.customApiSave?.enabled && ( -
- {/* API 타입 선택 */} -
- - -
- - {/* 다중 테이블 저장 설정 */} - {config.saveConfig.customApiSave?.apiType === "multi-table" && ( -
- {/* 메인 테이블 설정 */} -
- - 비반복 섹션의 데이터가 저장될 메인 테이블입니다. - -
- - - - - - - - - - 테이블을 찾을 수 없습니다 - - {tables.map((table) => ( - { - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: { - ...config.saveConfig.customApiSave?.multiTable?.mainTable, - tableName: table.name, - }, - subTables: config.saveConfig.customApiSave?.multiTable?.subTables || [], - }, - }, - }); - // 테이블 컬럼 로드 - if (!tableColumns[table.name]) { - loadTableColumns(table.name); - } - }} - className="text-[10px]" - > - -
- {table.label || table.name} - {table.label && {table.name}} -
-
- ))} -
-
-
-
-
-
- -
- - - 서브 테이블과 연결할 때 사용할 PK 컬럼 -
-
- - {/* 서브 테이블 설정 */} -
-
- - -
- 반복 섹션의 데이터가 저장될 서브 테이블을 설정합니다. - - {(config.saveConfig.customApiSave?.multiTable?.subTables || []).map((subTable, subIndex) => ( -
-
- 서브 테이블 #{subIndex + 1} - -
- - {/* 서브 테이블명 */} -
- - - - - - - - - - 테이블을 찾을 수 없습니다 - - {tables.map((table) => ( - { - const newSubTables = [...(config.saveConfig.customApiSave?.multiTable?.subTables || [])]; - newSubTables[subIndex] = { ...newSubTables[subIndex], tableName: table.name }; - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: config.saveConfig.customApiSave?.multiTable?.mainTable || { tableName: "", primaryKeyColumn: "" }, - subTables: newSubTables, - }, - }, - }); - // 테이블 컬럼 로드 - if (!tableColumns[table.name]) { - loadTableColumns(table.name); - } - }} - className="text-[9px]" - > - -
- {table.label || table.name} - {table.label && {table.name}} -
-
- ))} -
-
-
-
-
-
- - {/* 반복 섹션 선택 */} -
- - 서브 테이블에 저장할 데이터가 있는 반복 섹션 - -
- - {/* 연결 컬럼 설정 */} -
- - 메인 테이블의 PK와 서브 테이블의 FK를 연결 -
- {/* 메인 테이블 컬럼 선택 (PK 컬럼 기준) */} - -
- {/* 서브 테이블 컬럼 선택 (FK 컬럼) */} - -
- 메인 테이블과 서브 테이블을 연결할 컬럼 -
- - {/* 필드 매핑 */} - {subTable.repeatSectionId && subTable.tableName && ( -
-
- - -
- - {(subTable.fieldMappings || []).map((mapping, mapIndex) => { - const repeatSection = config.sections.find((s) => s.id === subTable.repeatSectionId); - const sectionFields = repeatSection?.fields || []; - - return ( -
-
- 매핑 #{mapIndex + 1} - -
- -
- -
- ); - })} -
- )} - - {/* 추가 옵션 */} -
- -
- { - const newSubTables = [...(config.saveConfig.customApiSave?.multiTable?.subTables || [])]; - newSubTables[subIndex] = { - ...newSubTables[subIndex], - options: { ...newSubTables[subIndex].options, saveMainAsFirst: !!checked }, - }; - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: config.saveConfig.customApiSave?.multiTable?.mainTable || { tableName: "", primaryKeyColumn: "" }, - subTables: newSubTables, - }, - }, - }); - }} - className="shrink-0" - /> - -
- - {subTable.options?.saveMainAsFirst && ( -
- - - 메인/서브 구분용 컬럼 (예: is_primary) -
- )} - -
- { - const newSubTables = [...(config.saveConfig.customApiSave?.multiTable?.subTables || [])]; - newSubTables[subIndex] = { - ...newSubTables[subIndex], - options: { ...newSubTables[subIndex].options, deleteExistingBefore: !!checked }, - }; - updateSaveConfig({ - customApiSave: { - ...config.saveConfig.customApiSave, - multiTable: { - ...config.saveConfig.customApiSave?.multiTable, - enabled: true, - mainTable: config.saveConfig.customApiSave?.multiTable?.mainTable || { tableName: "", primaryKeyColumn: "" }, - subTables: newSubTables, - }, - }, - }); - }} - className="shrink-0" - /> - -
-
-
- ))} - - {(config.saveConfig.customApiSave?.multiTable?.subTables || []).length === 0 && ( -

- 서브 테이블을 추가하세요 -

- )} -
-
- )} - - {/* 커스텀 API 설정 */} - {config.saveConfig.customApiSave?.apiType === "custom" && ( -
-
- - - updateSaveConfig({ - customApiSave: { ...config.saveConfig.customApiSave, customEndpoint: e.target.value }, - }) - } - placeholder="/api/custom/endpoint" - className="h-6 text-[10px] mt-1" - /> -
-
- - -
-
- )} -
- )} -
- - {/* 저장 후 동작 */} -
- -
- 모달 닫기 - - updateSaveConfig({ - afterSave: { ...config.saveConfig.afterSave, closeModal: checked }, - }) - } - /> -
-
- 부모 화면 새로고침 - - updateSaveConfig({ - afterSave: { ...config.saveConfig.afterSave, refreshParent: checked }, - }) - } - /> -
-
- 토스트 메시지 표시 - - updateSaveConfig({ - afterSave: { ...config.saveConfig.afterSave, showToast: checked }, - }) - } - /> -
+ {config.saveConfig.customApiSave?.enabled && config.saveConfig.customApiSave?.multiTable?.enabled && ( + + 다중 테이블 모드 + + )} +
+
+ + 데이터를 저장할 테이블과 방식을 설정합니다. +
+ "저장 설정 열기"를 클릭하여 상세 설정을 변경하세요. +
- {/* 섹션 관리 */} - - - -
- - 섹션 관리 ({config.sections.length}) + {/* 섹션 구성 */} + + + +
+ + 섹션 구성 + + {config.sections.length}개 +
- - + + 폼을 여러 섹션으로 나누어 구성할 수 있습니다. +
+ 예: 기본 정보, 배송 정보, 결제 정보 +
- {config.sections.map((section, sectionIndex) => ( - { - setSelectedSectionId(section.id); - setSelectedFieldId(null); - }} - > - -
-
- - {section.title} - {section.repeatable && ( - 반복 - )} -
-
+ {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} + + )} +
+ )} + + {/* 레이아웃 설정 버튼 */} +
- 필드 {section.fields.length}개 - - - ))} + ))} +
+ )} - - {/* 선택된 섹션 설정 */} - {selectedSection && ( - - - -
- - 섹션: {selectedSection.title} -
-
- -
- - updateSection(selectedSection.id, { title: e.target.value })} - className="h-7 text-xs mt-1" - /> -
- -
- -