"use client"; import React, { useEffect, useRef, useCallback, useMemo, useState } from "react"; import { Layers } from "lucide-react"; import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer"; import { ComponentDefinition, ComponentCategory, ComponentRendererProps } from "@/types/component"; import { RepeaterInput } from "@/components/webtypes/RepeaterInput"; import { RepeaterConfigPanel } from "@/components/webtypes/config/RepeaterConfigPanel"; import { useScreenContextOptional, DataReceivable } from "@/contexts/ScreenContext"; import { useSplitPanelContext } from "@/contexts/SplitPanelContext"; import { applyMappingRules } from "@/lib/utils/dataMapping"; import { toast } from "sonner"; import { apiClient } from "@/lib/api/client"; /** * Repeater Field Group ์ปดํฌ๋„ŒํŠธ */ const RepeaterFieldGroupComponent: React.FC = (props) => { const { component, value, onChange, readonly, disabled, formData, onFormDataChange, menuObjid } = props; const screenContext = useScreenContextOptional(); const splitPanelContext = useSplitPanelContext(); const receiverRef = useRef(null); // ๐Ÿ†• ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ์ƒํƒœ const [groupedData, setGroupedData] = useState(null); const [isLoadingGroupData, setIsLoadingGroupData] = useState(false); const groupDataLoadedRef = useRef(false); // ๐Ÿ†• ์›๋ณธ ๋ฐ์ดํ„ฐ ID ๋ชฉ๋ก (์‚ญ์ œ ์ถ”์ ์šฉ) const [originalItemIds, setOriginalItemIds] = useState([]); // ์ปดํฌ๋„ŒํŠธ์˜ ํ•„๋“œ๋ช… (formData ํ‚ค) const fieldName = (component as any).columnName || component.id; // repeaterConfig ๋˜๋Š” componentConfig์—์„œ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ const config = (component as any).repeaterConfig || component.componentConfig || { fields: [] }; // ๐Ÿ†• ๊ทธ๋ฃนํ™” ์„ค์ • (์˜ˆ: groupByColumn: "inbound_number") const groupByColumn = config.groupByColumn; const targetTable = config.targetTable; // formData์—์„œ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ (value prop๋ณด๋‹ค ์šฐ์„ ) const rawValue = formData?.[fieldName] ?? value; // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ ๊ฐ์ง€: formData์— id๊ฐ€ ์žˆ๊ณ , fieldName์œผ๋กœ ๊ฐ’์„ ์ฐพ์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ // formData ์ž์ฒด๋ฅผ ๋ฐฐ์—ด์˜ ์ฒซ ๋ฒˆ์งธ ํ•ญ๋ชฉ์œผ๋กœ ์‚ฌ์šฉ (๋‹จ์ผ ํ–‰ ์ˆ˜์ • ์‹œ) const isEditMode = formData?.id && !rawValue && !value; // ๐Ÿ†• ๋ฐ˜๋ณต ํ•„๋“œ ๊ทธ๋ฃน์˜ ํ•„๋“œ๋“ค์ด formData์— ์žˆ๋Š”์ง€ ํ™•์ธ const configFields = config.fields || []; const hasRepeaterFieldsInFormData = configFields.length > 0 && configFields.some((field: any) => formData?.[field.name] !== undefined); // ๐Ÿ†• formData์™€ config.fields์˜ ํ•„๋“œ ์ด๋ฆ„ ๋งค์นญ ํ™•์ธ const matchingFields = configFields.filter((field: any) => formData?.[field.name] !== undefined); // ๐Ÿ†• ๊ทธ๋ฃน ํ‚ค ๊ฐ’ (์˜ˆ: formData.inbound_number) const groupKeyValue = groupByColumn ? formData?.[groupByColumn] : null; console.log("๐Ÿ”„ [RepeaterFieldGroup] ๋ Œ๋”๋ง:", { fieldName, hasFormData: !!formData, formDataId: formData?.id, formDataValue: formData?.[fieldName], propsValue: value, rawValue, isEditMode, hasRepeaterFieldsInFormData, configFieldNames: configFields.map((f: any) => f.name), formDataKeys: formData ? Object.keys(formData) : [], matchingFieldNames: matchingFields.map((f: any) => f.name), groupByColumn, groupKeyValue, targetTable, hasGroupedData: groupedData !== null, groupedDataLength: groupedData?.length, }); // ๐Ÿ†• ์ˆ˜์ • ๋ชจ๋“œ์—์„œ ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ useEffect(() => { const loadGroupedData = async () => { // ์ด๋ฏธ ๋กœ๋“œํ–ˆ๊ฑฐ๋‚˜ ์กฐ๊ฑด์ด ๋งž์ง€ ์•Š์œผ๋ฉด ์Šคํ‚ต if (groupDataLoadedRef.current) return; if (!isEditMode || !groupByColumn || !groupKeyValue || !targetTable) return; console.log("๐Ÿ“ฅ [RepeaterFieldGroup] ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ์ž‘:", { groupByColumn, groupKeyValue, targetTable, }); setIsLoadingGroupData(true); groupDataLoadedRef.current = true; try { // API ํ˜ธ์ถœ: ๊ฐ™์€ ๊ทธ๋ฃน ํ‚ค๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์กฐํšŒ // search ํŒŒ๋ผ๋ฏธํ„ฐ ์‚ฌ์šฉ (filters๊ฐ€ ์•„๋‹Œ search) const response = await apiClient.post(`/table-management/tables/${targetTable}/data`, { page: 1, size: 100, // ์ถฉ๋ถ„ํžˆ ํฐ ๊ฐ’ search: { [groupByColumn]: groupKeyValue }, }); console.log("๐Ÿ” [RepeaterFieldGroup] API ์‘๋‹ต ๊ตฌ์กฐ:", { success: response.data?.success, hasData: !!response.data?.data, dataType: typeof response.data?.data, dataKeys: response.data?.data ? Object.keys(response.data.data) : [], }); // ์‘๋‹ต ๊ตฌ์กฐ: { success, data: { data: [...], total, page, totalPages } } if (response.data?.success && response.data?.data?.data) { const items = response.data.data.data; // ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด console.log("โœ… [RepeaterFieldGroup] ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ:", { count: items.length, groupByColumn, groupKeyValue, firstItem: items[0], }); setGroupedData(items); // ๐Ÿ†• ์›๋ณธ ๋ฐ์ดํ„ฐ ID ๋ชฉ๋ก ์ €์žฅ (์‚ญ์ œ ์ถ”์ ์šฉ) const itemIds = items.map((item: any) => String(item.id || item.po_item_id || item.item_id)).filter(Boolean); setOriginalItemIds(itemIds); console.log("๐Ÿ“‹ [RepeaterFieldGroup] ์›๋ณธ ๋ฐ์ดํ„ฐ ID ๋ชฉ๋ก ์ €์žฅ:", itemIds); // ๐Ÿ†• SplitPanelContext์— ๊ธฐ์กด ํ•ญ๋ชฉ ID ๋“ฑ๋ก (์ขŒ์ธก ํ…Œ์ด๋ธ” ํ•„ํ„ฐ๋ง์šฉ) if (splitPanelContext?.addItemIds && itemIds.length > 0) { splitPanelContext.addItemIds(itemIds); } // onChange ํ˜ธ์ถœํ•˜์—ฌ ๋ถ€๋ชจ์—๊ฒŒ ์•Œ๋ฆผ if (onChange && items.length > 0) { const dataWithMeta = items.map((item: any) => ({ ...item, _targetTable: targetTable, _originalItemIds: itemIds, // ๐Ÿ†• ์›๋ณธ ID ๋ชฉ๋ก๋„ ํ•จ๊ป˜ ์ „๋‹ฌ })); onChange(dataWithMeta); } } else { console.warn("โš ๏ธ [RepeaterFieldGroup] ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹คํŒจ:", response.data); setGroupedData([]); } } catch (error) { console.error("โŒ [RepeaterFieldGroup] ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์˜ค๋ฅ˜:", error); setGroupedData([]); } finally { setIsLoadingGroupData(false); } }; loadGroupedData(); }, [isEditMode, groupByColumn, groupKeyValue, targetTable, onChange]); // ๊ฐ’์ด JSON ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ ํŒŒ์‹ฑ let parsedValue: any[] = []; // ๐Ÿ†• ๊ทธ๋ฃนํ™”๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ if (groupedData !== null && groupedData.length > 0) { parsedValue = groupedData; } else if (isEditMode && hasRepeaterFieldsInFormData && !groupByColumn) { // ๊ทธ๋ฃนํ™” ์„ค์ •์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋‹จ์ผ ํ–‰ ์‚ฌ์šฉ console.log("๐Ÿ“ [RepeaterFieldGroup] ์ˆ˜์ • ๋ชจ๋“œ - formData๋ฅผ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉ", { formDataId: formData?.id, matchingFieldsCount: matchingFields.length, }); parsedValue = [{ ...formData }]; } else if (typeof rawValue === "string" && rawValue.trim() !== "") { // ๋นˆ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ JSON ํŒŒ์‹ฑ ์‹œ๋„ try { parsedValue = JSON.parse(rawValue); } catch { parsedValue = []; } } else if (Array.isArray(rawValue)) { parsedValue = rawValue; } // parsedValue๋ฅผ ref๋กœ ๊ด€๋ฆฌํ•˜์—ฌ ์ตœ์‹  ๊ฐ’ ์œ ์ง€ const parsedValueRef = useRef(parsedValue); parsedValueRef.current = parsedValue; // onChange๋ฅผ ref๋กœ ๊ด€๋ฆฌ const onChangeRef = useRef(onChange); onChangeRef.current = onChange; // onFormDataChange๋ฅผ ref๋กœ ๊ด€๋ฆฌ const onFormDataChangeRef = useRef(onFormDataChange); onFormDataChangeRef.current = onFormDataChange; // fieldName์„ ref๋กœ ๊ด€๋ฆฌ const fieldNameRef = useRef(fieldName); fieldNameRef.current = fieldName; // config๋ฅผ ref๋กœ ๊ด€๋ฆฌ const configRef = useRef(config); configRef.current = config; // ๋ฐ์ดํ„ฐ ์ˆ˜์‹  ํ•ธ๋“ค๋Ÿฌ const handleReceiveData = useCallback((data: any[], mappingRulesOrMode?: any[] | string) => { console.log("๐Ÿ“ฅ [RepeaterFieldGroup] ๋ฐ์ดํ„ฐ ์ˆ˜์‹ :", { data, mappingRulesOrMode }); if (!data || data.length === 0) { toast.warning("์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"); return; } // ๋งคํ•‘ ๊ทœ์น™์ด ๋ฐฐ์—ด์ธ ๊ฒฝ์šฐ์—๋งŒ ์ ์šฉ let processedData = data; if (Array.isArray(mappingRulesOrMode) && mappingRulesOrMode.length > 0) { processedData = applyMappingRules(data, mappingRulesOrMode); } // ๋ฐ์ดํ„ฐ ์ •๊ทœํ™”: ๊ฐ ํ•ญ๋ชฉ์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ ์ถ”์ถœ // ๋ฐ์ดํ„ฐ๊ฐ€ {0: {...}, inbound_type: "..."} ํ˜•ํƒœ์ธ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ const normalizedData = processedData.map((item: any) => { // item์ด {0: {...์‹ค์ œ๋ฐ์ดํ„ฐ...}, ์ถ”๊ฐ€ํ•„๋“œ: ๊ฐ’} ํ˜•ํƒœ์ธ ๊ฒฝ์šฐ if (item && typeof item === "object" && item[0] && typeof item[0] === "object") { // 0๋ฒˆ ์ธ๋ฑ์Šค์˜ ๋ฐ์ดํ„ฐ์™€ ๋‚˜๋จธ์ง€ ํ•„๋“œ๋ฅผ ๋ณ‘ํ•ฉ const { 0: originalData, ...additionalFields } = item; return { ...originalData, ...additionalFields }; } return item; }); // ๐Ÿ†• ์ •์˜๋œ ํ•„๋“œ๋งŒ ํ•„ํ„ฐ๋ง (๋ถˆํ•„์š”ํ•œ ํ•„๋“œ ์ œ๊ฑฐ) // ๋ฐ˜๋ณต ํ•„๋“œ ๊ทธ๋ฃน์— ์ •์˜๋œ ํ•„๋“œ + ์‹œ์Šคํ…œ ํ•„๋“œ๋งŒ ์œ ์ง€ const definedFields = configRef.current.fields || []; const definedFieldNames = new Set(definedFields.map((f: any) => f.name)); // ์‹œ์Šคํ…œ ํ•„๋“œ ๋ฐ ํ•„์ˆ˜ ํ•„๋“œ ์ถ”๊ฐ€ const systemFields = new Set(['id', '_targetTable', 'created_date', 'updated_date', 'writer', 'company_code']); const filteredData = normalizedData.map((item: any) => { const filteredItem: Record = {}; Object.keys(item).forEach(key => { // ์ •์˜๋œ ํ•„๋“œ์ด๊ฑฐ๋‚˜ ์‹œ์Šคํ…œ ํ•„๋“œ์ธ ๊ฒฝ์šฐ๋งŒ ํฌํ•จ if (definedFieldNames.has(key) || systemFields.has(key)) { filteredItem[key] = item[key]; } }); return filteredItem; }); console.log("๐Ÿ“ฅ [RepeaterFieldGroup] ์ •๊ทœํ™”๋œ ๋ฐ์ดํ„ฐ:", normalizedData); console.log("๐Ÿ“ฅ [RepeaterFieldGroup] ํ•„ํ„ฐ๋ง๋œ ๋ฐ์ดํ„ฐ:", filteredData); // ๊ธฐ์กด ๋ฐ์ดํ„ฐ์— ์ƒˆ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ (๊ธฐ๋ณธ ๋ชจ๋“œ: append) const currentValue = parsedValueRef.current; // mode๊ฐ€ "replace"์ธ ๊ฒฝ์šฐ ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋Œ€์ฒด, ๊ทธ ์™ธ์—๋Š” ์ถ”๊ฐ€ const mode = typeof mappingRulesOrMode === "string" ? mappingRulesOrMode : "append"; let newItems: any[]; let addedCount = 0; let duplicateCount = 0; if (mode === "replace") { newItems = filteredData; addedCount = filteredData.length; } else { // ๐Ÿ†• ์ค‘๋ณต ์ฒดํฌ: id ๋˜๋Š” ๊ณ ์œ  ์‹๋ณ„์ž๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํ•ญ๋ชฉ ์ œ์™ธ const existingIds = new Set( currentValue .map((item: any) => item.id || item.po_item_id || item.item_id) .filter(Boolean) ); const uniqueNewItems = filteredData.filter((item: any) => { const itemId = item.id || item.po_item_id || item.item_id; if (itemId && existingIds.has(itemId)) { duplicateCount++; return false; // ์ค‘๋ณต ํ•ญ๋ชฉ ์ œ์™ธ } return true; }); newItems = [...currentValue, ...uniqueNewItems]; addedCount = uniqueNewItems.length; } console.log("๐Ÿ“ฅ [RepeaterFieldGroup] ์ตœ์ข… ๋ฐ์ดํ„ฐ:", { currentValue, newItems, mode, addedCount, duplicateCount, }); // ๐Ÿ†• groupedData ์ƒํƒœ๋„ ์ง์ ‘ ์—…๋ฐ์ดํŠธ (UI ์ฆ‰์‹œ ๋ฐ˜์˜) setGroupedData(newItems); // ๐Ÿ†• SplitPanelContext์— ์ถ”๊ฐ€๋œ ํ•ญ๋ชฉ ID ๋“ฑ๋ก (์ขŒ์ธก ํ…Œ์ด๋ธ” ํ•„ํ„ฐ๋ง์šฉ) if (splitPanelContext?.addItemIds && addedCount > 0) { const newItemIds = newItems .map((item: any) => String(item.id || item.po_item_id || item.item_id)) .filter(Boolean); splitPanelContext.addItemIds(newItemIds); } // JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅ const jsonValue = JSON.stringify(newItems); console.log("๐Ÿ“ฅ [RepeaterFieldGroup] onChange/onFormDataChange ํ˜ธ์ถœ:", { jsonValue, hasOnChange: !!onChangeRef.current, hasOnFormDataChange: !!onFormDataChangeRef.current, fieldName: fieldNameRef.current, }); // onFormDataChange๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ (EmbeddedScreen์˜ formData ์ƒํƒœ ์—…๋ฐ์ดํŠธ) if (onFormDataChangeRef.current) { onFormDataChangeRef.current(fieldNameRef.current, jsonValue); } // ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด onChange ์‚ฌ์šฉ else if (onChangeRef.current) { onChangeRef.current(jsonValue); } // ๊ฒฐ๊ณผ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ if (addedCount > 0) { if (duplicateCount > 0) { toast.success(`${addedCount}๊ฐœ ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค (${duplicateCount}๊ฐœ ์ค‘๋ณต ์ œ์™ธ)`); } else { toast.success(`${addedCount}๊ฐœ ํ•ญ๋ชฉ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค`); } } else if (duplicateCount > 0) { toast.warning(`${duplicateCount}๊ฐœ ํ•ญ๋ชฉ์ด ์ด๋ฏธ ์ถ”๊ฐ€๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค`); } }, []); // DataReceivable ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ const dataReceiver = useMemo(() => ({ componentId: component.id, componentType: "repeater-field-group", receiveData: handleReceiveData, }), [component.id, handleReceiveData]); // ScreenContext์— ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ์ž๋กœ ๋“ฑ๋ก useEffect(() => { if (screenContext && component.id) { console.log("๐Ÿ“‹ [RepeaterFieldGroup] ScreenContext์— ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ์ž ๋“ฑ๋ก:", component.id); screenContext.registerDataReceiver(component.id, dataReceiver); return () => { screenContext.unregisterDataReceiver(component.id); }; } }, [screenContext, component.id, dataReceiver]); // SplitPanelContext์— ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ์ž๋กœ ๋“ฑ๋ก (๋ถ„ํ•  ํŒจ๋„ ๋‚ด์—์„œ๋งŒ) useEffect(() => { const splitPanelPosition = screenContext?.splitPanelPosition; if (splitPanelContext?.isInSplitPanel && splitPanelPosition && component.id) { console.log("๐Ÿ”— [RepeaterFieldGroup] SplitPanelContext์— ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ์ž ๋“ฑ๋ก:", { componentId: component.id, position: splitPanelPosition, }); splitPanelContext.registerReceiver(splitPanelPosition, component.id, dataReceiver); receiverRef.current = dataReceiver; return () => { console.log("๐Ÿ”— [RepeaterFieldGroup] SplitPanelContext์—์„œ ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ์ž ํ•ด์ œ:", component.id); splitPanelContext.unregisterReceiver(splitPanelPosition, component.id); receiverRef.current = null; }; } }, [splitPanelContext, screenContext?.splitPanelPosition, component.id, dataReceiver]); // ๐Ÿ†• ์ „์—ญ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ (splitPanelDataTransfer) useEffect(() => { const handleSplitPanelDataTransfer = (event: CustomEvent) => { const { data, mode, mappingRules } = event.detail; console.log("๐Ÿ“ฅ [RepeaterFieldGroup] splitPanelDataTransfer ์ด๋ฒคํŠธ ์ˆ˜์‹ :", { dataCount: data?.length, mode, componentId: component.id, }); // ์šฐ์ธก ํŒจ๋„์˜ ๋ฆฌํ”ผํ„ฐ ํ•„๋“œ ๊ทธ๋ฃน๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹  const splitPanelPosition = screenContext?.splitPanelPosition; if (splitPanelPosition === "right" && data && data.length > 0) { handleReceiveData(data, mappingRules || mode || "append"); } }; window.addEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer as EventListener); return () => { window.removeEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer as EventListener); }; }, [screenContext?.splitPanelPosition, handleReceiveData, component.id]); // ๐Ÿ†• RepeaterInput์—์„œ ํ•ญ๋ชฉ ๋ณ€๊ฒฝ ์‹œ SplitPanelContext์˜ addedItemIds ๋™๊ธฐํ™” const handleRepeaterChange = useCallback((newValue: any[]) => { // ๋ฐฐ์—ด์„ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅ const jsonValue = JSON.stringify(newValue); onChange?.(jsonValue); // ๐Ÿ†• groupedData ์ƒํƒœ๋„ ์—…๋ฐ์ดํŠธ setGroupedData(newValue); // ๐Ÿ†• SplitPanelContext์˜ addedItemIds ๋™๊ธฐํ™” if (splitPanelContext?.isInSplitPanel && screenContext?.splitPanelPosition === "right") { // ํ˜„์žฌ ํ•ญ๋ชฉ๋“ค์˜ ID ๋ชฉ๋ก const currentIds = newValue .map((item: any) => String(item.id || item.po_item_id || item.item_id)) .filter(Boolean); // ๊ธฐ์กด addedItemIds์™€ ๋น„๊ตํ•˜์—ฌ ์‚ญ์ œ๋œ ID ์ฐพ๊ธฐ const addedIds = splitPanelContext.addedItemIds; const removedIds = Array.from(addedIds).filter(id => !currentIds.includes(id)); if (removedIds.length > 0) { console.log("๐Ÿ—‘๏ธ [RepeaterFieldGroup] ์‚ญ์ œ๋œ ํ•ญ๋ชฉ ID ์ œ๊ฑฐ:", removedIds); splitPanelContext.removeItemIds(removedIds); } // ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ID๊ฐ€ ์žˆ์œผ๋ฉด ๋“ฑ๋ก const newIds = currentIds.filter((id: string) => !addedIds.has(id)); if (newIds.length > 0) { console.log("โž• [RepeaterFieldGroup] ์ƒˆ ํ•ญ๋ชฉ ID ์ถ”๊ฐ€:", newIds); splitPanelContext.addItemIds(newIds); } } }, [onChange, splitPanelContext, screenContext?.splitPanelPosition]); return ( ); }; /** * Repeater Field Group ๋ Œ๋”๋Ÿฌ * ์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ ํ•ญ๋ชฉ๋“ค์„ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€/์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ */ export class RepeaterFieldGroupRenderer extends AutoRegisteringComponentRenderer { /** * ์ปดํฌ๋„ŒํŠธ ์ •์˜ */ static componentDefinition: ComponentDefinition = { id: "repeater-field-group", name: "๋ฐ˜๋ณต ํ•„๋“œ ๊ทธ๋ฃน", nameEng: "Repeater Field Group", description: "์—ฌ๋Ÿฌ ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ ํ•ญ๋ชฉ๋“ค์„ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€/์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•œ ํ•„๋“œ ๊ทธ๋ฃน", category: ComponentCategory.INPUT, webType: "array", // ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃธ icon: Layers, component: RepeaterFieldGroupRenderer, configPanel: RepeaterConfigPanel, defaultSize: { width: 600, height: 200, // ๊ธฐ๋ณธ ๋†’์ด ์กฐ์ • }, defaultConfig: { fields: [], // ๋นˆ ๋ฐฐ์—ด๋กœ ์‹œ์ž‘ - ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ํ•„๋“œ ์ถ”๊ฐ€ minItems: 1, // ๊ธฐ๋ณธ 1๊ฐœ ํ•ญ๋ชฉ maxItems: 20, addButtonText: "ํ•ญ๋ชฉ ์ถ”๊ฐ€", allowReorder: true, showIndex: true, collapsible: false, layout: "grid", showDivider: true, emptyMessage: "ํ•„๋“œ๋ฅผ ๋จผ์ € ์ •์˜ํ•˜์„ธ์š”.", }, tags: ["repeater", "fieldgroup", "dynamic", "multi", "form", "array", "fields"], author: "System", version: "1.0.0", }; /** * ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง */ render(): React.ReactElement { return ; } } // ์ปดํฌ๋„ŒํŠธ ์ž๋™ ๋“ฑ๋ก RepeaterFieldGroupRenderer.registerSelf(); // Hot Reload ์ง€์› (๊ฐœ๋ฐœ ๋ชจ๋“œ) if (process.env.NODE_ENV === "development") { RepeaterFieldGroupRenderer.enableHotReload(); }