오류 수정
This commit is contained in:
@@ -47,6 +47,7 @@ import dynamic from "next/dynamic";
|
||||
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
||||
import { DynamicWebTypeRenderer } from "@/lib/registry";
|
||||
import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeUtils";
|
||||
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
|
||||
|
||||
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
|
||||
const InteractiveScreenViewer = dynamic(
|
||||
@@ -1315,15 +1316,16 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
<DialogHeader>
|
||||
<DialogTitle>화면 미리보기 - {screenToPreview?.screenName}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-1 items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-slate-100 p-6">
|
||||
{isLoadingPreview ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="mb-2 text-lg font-medium">레이아웃 로딩 중...</div>
|
||||
<div className="text-muted-foreground text-sm">화면 정보를 불러오고 있습니다.</div>
|
||||
<TableOptionsProvider>
|
||||
<div className="flex flex-1 items-center justify-center overflow-hidden bg-gradient-to-br from-gray-50 to-slate-100 p-6">
|
||||
{isLoadingPreview ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="mb-2 text-lg font-medium">레이아웃 로딩 중...</div>
|
||||
<div className="text-muted-foreground text-sm">화면 정보를 불러오고 있습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : previewLayout && previewLayout.components ? (
|
||||
) : previewLayout && previewLayout.components ? (
|
||||
(() => {
|
||||
const screenWidth = previewLayout.screenResolution?.width || 1200;
|
||||
const screenHeight = previewLayout.screenResolution?.height || 800;
|
||||
@@ -1536,7 +1538,8 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TableOptionsProvider>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setPreviewDialogOpen(false)}>
|
||||
닫기
|
||||
|
||||
@@ -47,6 +47,14 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
// 새 옵션 추가용 상태
|
||||
const [newOptionLabel, setNewOptionLabel] = useState("");
|
||||
const [newOptionValue, setNewOptionValue] = useState("");
|
||||
|
||||
// 입력 필드용 로컬 상태
|
||||
const [localInputs, setLocalInputs] = useState({
|
||||
label: config.label || "",
|
||||
checkedValue: config.checkedValue || "Y",
|
||||
uncheckedValue: config.uncheckedValue || "N",
|
||||
groupLabel: config.groupLabel || "",
|
||||
});
|
||||
|
||||
// 컴포넌트 변경 시 로컬 상태 동기화
|
||||
useEffect(() => {
|
||||
@@ -63,6 +71,14 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
readonly: currentConfig.readonly || false,
|
||||
inline: currentConfig.inline !== false,
|
||||
});
|
||||
|
||||
// 입력 필드 로컬 상태도 동기화
|
||||
setLocalInputs({
|
||||
label: currentConfig.label || "",
|
||||
checkedValue: currentConfig.checkedValue || "Y",
|
||||
uncheckedValue: currentConfig.uncheckedValue || "N",
|
||||
groupLabel: currentConfig.groupLabel || "",
|
||||
});
|
||||
}, [widget.webTypeConfig]);
|
||||
|
||||
// 설정 업데이트 핸들러
|
||||
@@ -107,11 +123,16 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
updateConfig("options", newOptions);
|
||||
};
|
||||
|
||||
// 옵션 업데이트
|
||||
const updateOption = (index: number, field: keyof CheckboxOption, value: any) => {
|
||||
// 옵션 업데이트 (입력 필드용 - 로컬 상태만)
|
||||
const updateOptionLocal = (index: number, field: keyof CheckboxOption, value: any) => {
|
||||
const newOptions = [...localConfig.options];
|
||||
newOptions[index] = { ...newOptions[index], [field]: value };
|
||||
updateConfig("options", newOptions);
|
||||
setLocalConfig({ ...localConfig, options: newOptions });
|
||||
};
|
||||
|
||||
// 옵션 업데이트 완료 (onBlur)
|
||||
const handleOptionBlur = () => {
|
||||
onUpdateProperty("webTypeConfig", localConfig);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -170,8 +191,9 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="label"
|
||||
value={localConfig.label || ""}
|
||||
onChange={(e) => updateConfig("label", e.target.value)}
|
||||
value={localInputs.label}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, label: e.target.value })}
|
||||
onBlur={() => updateConfig("label", localInputs.label)}
|
||||
placeholder="체크박스 라벨"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -184,8 +206,9 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="checkedValue"
|
||||
value={localConfig.checkedValue || ""}
|
||||
onChange={(e) => updateConfig("checkedValue", e.target.value)}
|
||||
value={localInputs.checkedValue}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, checkedValue: e.target.value })}
|
||||
onBlur={() => updateConfig("checkedValue", localInputs.checkedValue)}
|
||||
placeholder="Y"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -196,8 +219,9 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="uncheckedValue"
|
||||
value={localConfig.uncheckedValue || ""}
|
||||
onChange={(e) => updateConfig("uncheckedValue", e.target.value)}
|
||||
value={localInputs.uncheckedValue}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, uncheckedValue: e.target.value })}
|
||||
onBlur={() => updateConfig("uncheckedValue", localInputs.uncheckedValue)}
|
||||
placeholder="N"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -229,8 +253,9 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="groupLabel"
|
||||
value={localConfig.groupLabel || ""}
|
||||
onChange={(e) => updateConfig("groupLabel", e.target.value)}
|
||||
value={localInputs.groupLabel}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, groupLabel: e.target.value })}
|
||||
onBlur={() => updateConfig("groupLabel", localInputs.groupLabel)}
|
||||
placeholder="체크박스 그룹 제목"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -268,26 +293,40 @@ export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
<Label className="text-xs">옵션 목록 ({localConfig.options.length}개)</Label>
|
||||
<div className="max-h-40 space-y-2 overflow-y-auto">
|
||||
{localConfig.options.map((option, index) => (
|
||||
<div key={index} className="flex items-center gap-2 rounded border p-2">
|
||||
<div key={`${option.value}-${index}`} className="flex items-center gap-2 rounded border p-2">
|
||||
<Switch
|
||||
checked={option.checked || false}
|
||||
onCheckedChange={(checked) => updateOption(index, "checked", checked)}
|
||||
onCheckedChange={(checked) => {
|
||||
const newOptions = [...localConfig.options];
|
||||
newOptions[index] = { ...newOptions[index], checked };
|
||||
const newConfig = { ...localConfig, options: newOptions };
|
||||
setLocalConfig(newConfig);
|
||||
onUpdateProperty("webTypeConfig", newConfig);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
value={option.label}
|
||||
onChange={(e) => updateOption(index, "label", e.target.value)}
|
||||
onChange={(e) => updateOptionLocal(index, "label", e.target.value)}
|
||||
onBlur={handleOptionBlur}
|
||||
placeholder="라벨"
|
||||
className="flex-1 text-xs"
|
||||
/>
|
||||
<Input
|
||||
value={option.value}
|
||||
onChange={(e) => updateOption(index, "value", e.target.value)}
|
||||
onChange={(e) => updateOptionLocal(index, "value", e.target.value)}
|
||||
onBlur={handleOptionBlur}
|
||||
placeholder="값"
|
||||
className="flex-1 text-xs"
|
||||
/>
|
||||
<Switch
|
||||
checked={!option.disabled}
|
||||
onCheckedChange={(checked) => updateOption(index, "disabled", !checked)}
|
||||
onCheckedChange={(checked) => {
|
||||
const newOptions = [...localConfig.options];
|
||||
newOptions[index] = { ...newOptions[index], disabled: !checked };
|
||||
const newConfig = { ...localConfig, options: newOptions };
|
||||
setLocalConfig(newConfig);
|
||||
onUpdateProperty("webTypeConfig", newConfig);
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="destructive" onClick={() => removeOption(index)} className="p-1 text-xs">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
|
||||
@@ -51,32 +51,29 @@ export const EntityConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
const [newFieldName, setNewFieldName] = useState("");
|
||||
const [newFieldLabel, setNewFieldLabel] = useState("");
|
||||
const [newFieldType, setNewFieldType] = useState("string");
|
||||
const [isUserEditing, setIsUserEditing] = useState(false);
|
||||
|
||||
// 컴포넌트 변경 시 로컬 상태 동기화 (사용자가 입력 중이 아닐 때만)
|
||||
// 컴포넌트 변경 시 로컬 상태 동기화
|
||||
useEffect(() => {
|
||||
if (!isUserEditing) {
|
||||
const currentConfig = (widget.webTypeConfig as EntityTypeConfig) || {};
|
||||
setLocalConfig({
|
||||
entityType: currentConfig.entityType || "",
|
||||
displayFields: currentConfig.displayFields || [],
|
||||
searchFields: currentConfig.searchFields || [],
|
||||
valueField: currentConfig.valueField || "id",
|
||||
labelField: currentConfig.labelField || "name",
|
||||
multiple: currentConfig.multiple || false,
|
||||
searchable: currentConfig.searchable !== false,
|
||||
placeholder: currentConfig.placeholder || "엔티티를 선택하세요",
|
||||
emptyMessage: currentConfig.emptyMessage || "검색 결과가 없습니다",
|
||||
pageSize: currentConfig.pageSize || 20,
|
||||
minSearchLength: currentConfig.minSearchLength || 1,
|
||||
defaultValue: currentConfig.defaultValue || "",
|
||||
required: currentConfig.required || false,
|
||||
readonly: currentConfig.readonly || false,
|
||||
apiEndpoint: currentConfig.apiEndpoint || "",
|
||||
filters: currentConfig.filters || {},
|
||||
});
|
||||
}
|
||||
}, [widget.webTypeConfig, isUserEditing]);
|
||||
const currentConfig = (widget.webTypeConfig as EntityTypeConfig) || {};
|
||||
setLocalConfig({
|
||||
entityType: currentConfig.entityType || "",
|
||||
displayFields: currentConfig.displayFields || [],
|
||||
searchFields: currentConfig.searchFields || [],
|
||||
valueField: currentConfig.valueField || "id",
|
||||
labelField: currentConfig.labelField || "name",
|
||||
multiple: currentConfig.multiple || false,
|
||||
searchable: currentConfig.searchable !== false,
|
||||
placeholder: currentConfig.placeholder || "엔티티를 선택하세요",
|
||||
emptyMessage: currentConfig.emptyMessage || "검색 결과가 없습니다",
|
||||
pageSize: currentConfig.pageSize || 20,
|
||||
minSearchLength: currentConfig.minSearchLength || 1,
|
||||
defaultValue: currentConfig.defaultValue || "",
|
||||
required: currentConfig.required || false,
|
||||
readonly: currentConfig.readonly || false,
|
||||
apiEndpoint: currentConfig.apiEndpoint || "",
|
||||
filters: currentConfig.filters || {},
|
||||
});
|
||||
}, [widget.webTypeConfig]);
|
||||
|
||||
// 설정 업데이트 핸들러 (즉시 부모에게 전달 - 드롭다운, 체크박스 등)
|
||||
const updateConfig = (field: keyof EntityTypeConfig, value: any) => {
|
||||
@@ -87,13 +84,11 @@ export const EntityConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
|
||||
// 입력 필드용 업데이트 (로컬 상태만)
|
||||
const updateConfigLocal = (field: keyof EntityTypeConfig, value: any) => {
|
||||
setIsUserEditing(true);
|
||||
setLocalConfig({ ...localConfig, [field]: value });
|
||||
};
|
||||
|
||||
// 입력 완료 시 부모에게 전달
|
||||
const handleInputBlur = () => {
|
||||
setIsUserEditing(false);
|
||||
onUpdateProperty("webTypeConfig", localConfig);
|
||||
};
|
||||
|
||||
@@ -121,17 +116,15 @@ export const EntityConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
updateConfig("displayFields", newFields);
|
||||
};
|
||||
|
||||
// 필드 업데이트 (입력 중)
|
||||
// 필드 업데이트 (입력 중) - 로컬 상태만 업데이트
|
||||
const updateDisplayField = (index: number, field: keyof EntityField, value: any) => {
|
||||
setIsUserEditing(true);
|
||||
const newFields = [...localConfig.displayFields];
|
||||
newFields[index] = { ...newFields[index], [field]: value };
|
||||
setLocalConfig({ ...localConfig, displayFields: newFields });
|
||||
};
|
||||
|
||||
// 필드 업데이트 완료 (onBlur)
|
||||
// 필드 업데이트 완료 (onBlur) - 부모에게 전달
|
||||
const handleFieldBlur = () => {
|
||||
setIsUserEditing(false);
|
||||
onUpdateProperty("webTypeConfig", localConfig);
|
||||
};
|
||||
|
||||
@@ -325,12 +318,15 @@ export const EntityConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
<Label className="text-xs">표시 필드 ({localConfig.displayFields.length}개)</Label>
|
||||
<div className="max-h-40 space-y-2 overflow-y-auto">
|
||||
{localConfig.displayFields.map((field, index) => (
|
||||
<div key={index} className="flex items-center gap-2 rounded border p-2">
|
||||
<div key={`${field.name}-${index}`} className="flex items-center gap-2 rounded border p-2">
|
||||
<Switch
|
||||
checked={field.visible}
|
||||
onCheckedChange={(checked) => {
|
||||
updateDisplayField(index, "visible", checked);
|
||||
handleFieldBlur();
|
||||
const newFields = [...localConfig.displayFields];
|
||||
newFields[index] = { ...newFields[index], visible: checked };
|
||||
const newConfig = { ...localConfig, displayFields: newFields };
|
||||
setLocalConfig(newConfig);
|
||||
onUpdateProperty("webTypeConfig", newConfig);
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
@@ -347,7 +343,16 @@ export const EntityConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
placeholder="라벨"
|
||||
className="flex-1 text-xs"
|
||||
/>
|
||||
<Select value={field.type} onValueChange={(value) => updateDisplayField(index, "type", value)}>
|
||||
<Select
|
||||
value={field.type}
|
||||
onValueChange={(value) => {
|
||||
const newFields = [...localConfig.displayFields];
|
||||
newFields[index] = { ...newFields[index], type: value };
|
||||
const newConfig = { ...localConfig, displayFields: newFields };
|
||||
setLocalConfig(newConfig);
|
||||
onUpdateProperty("webTypeConfig", newConfig);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-24 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -43,6 +43,12 @@ export const RadioConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
const [newOptionLabel, setNewOptionLabel] = useState("");
|
||||
const [newOptionValue, setNewOptionValue] = useState("");
|
||||
const [bulkOptions, setBulkOptions] = useState("");
|
||||
|
||||
// 입력 필드용 로컬 상태
|
||||
const [localInputs, setLocalInputs] = useState({
|
||||
groupLabel: config.groupLabel || "",
|
||||
groupName: config.groupName || "",
|
||||
});
|
||||
|
||||
// 컴포넌트 변경 시 로컬 상태 동기화
|
||||
useEffect(() => {
|
||||
@@ -59,6 +65,12 @@ export const RadioConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
inline: currentConfig.inline !== false,
|
||||
groupLabel: currentConfig.groupLabel || "",
|
||||
});
|
||||
|
||||
// 입력 필드 로컬 상태도 동기화
|
||||
setLocalInputs({
|
||||
groupLabel: currentConfig.groupLabel || "",
|
||||
groupName: currentConfig.groupName || "",
|
||||
});
|
||||
}, [widget.webTypeConfig]);
|
||||
|
||||
// 설정 업데이트 핸들러
|
||||
@@ -95,17 +107,24 @@ export const RadioConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 옵션 업데이트
|
||||
const updateOption = (index: number, field: keyof RadioOption, value: any) => {
|
||||
// 옵션 업데이트 (입력 필드용 - 로컬 상태만)
|
||||
const updateOptionLocal = (index: number, field: keyof RadioOption, value: any) => {
|
||||
const newOptions = [...localConfig.options];
|
||||
const oldValue = newOptions[index].value;
|
||||
newOptions[index] = { ...newOptions[index], [field]: value };
|
||||
updateConfig("options", newOptions);
|
||||
|
||||
|
||||
// 값이 변경되고 해당 값이 기본값이었다면 기본값도 업데이트
|
||||
const newConfig = { ...localConfig, options: newOptions };
|
||||
if (field === "value" && localConfig.defaultValue === oldValue) {
|
||||
updateConfig("defaultValue", value);
|
||||
newConfig.defaultValue = value;
|
||||
}
|
||||
|
||||
setLocalConfig(newConfig);
|
||||
};
|
||||
|
||||
// 옵션 업데이트 완료 (onBlur)
|
||||
const handleOptionBlur = () => {
|
||||
onUpdateProperty("webTypeConfig", localConfig);
|
||||
};
|
||||
|
||||
// 벌크 옵션 추가
|
||||
@@ -185,8 +204,9 @@ export const RadioConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="groupLabel"
|
||||
value={localConfig.groupLabel || ""}
|
||||
onChange={(e) => updateConfig("groupLabel", e.target.value)}
|
||||
value={localInputs.groupLabel}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, groupLabel: e.target.value })}
|
||||
onBlur={() => updateConfig("groupLabel", localInputs.groupLabel)}
|
||||
placeholder="라디오버튼 그룹 제목"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -198,8 +218,9 @@ export const RadioConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="groupName"
|
||||
value={localConfig.groupName || ""}
|
||||
onChange={(e) => updateConfig("groupName", e.target.value)}
|
||||
value={localInputs.groupName}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, groupName: e.target.value })}
|
||||
onBlur={() => updateConfig("groupName", localInputs.groupName)}
|
||||
placeholder="자동 생성 (필드명 기반)"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -290,22 +311,30 @@ export const RadioConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
<Label className="text-xs">현재 옵션 ({localConfig.options.length}개)</Label>
|
||||
<div className="max-h-40 space-y-2 overflow-y-auto">
|
||||
{localConfig.options.map((option, index) => (
|
||||
<div key={index} className="flex items-center gap-2 rounded border p-2">
|
||||
<div key={`${option.value}-${index}`} className="flex items-center gap-2 rounded border p-2">
|
||||
<Input
|
||||
value={option.label}
|
||||
onChange={(e) => updateOption(index, "label", e.target.value)}
|
||||
onChange={(e) => updateOptionLocal(index, "label", e.target.value)}
|
||||
onBlur={handleOptionBlur}
|
||||
placeholder="라벨"
|
||||
className="flex-1 text-xs"
|
||||
/>
|
||||
<Input
|
||||
value={option.value}
|
||||
onChange={(e) => updateOption(index, "value", e.target.value)}
|
||||
onChange={(e) => updateOptionLocal(index, "value", e.target.value)}
|
||||
onBlur={handleOptionBlur}
|
||||
placeholder="값"
|
||||
className="flex-1 text-xs"
|
||||
/>
|
||||
<Switch
|
||||
checked={!option.disabled}
|
||||
onCheckedChange={(checked) => updateOption(index, "disabled", !checked)}
|
||||
onCheckedChange={(checked) => {
|
||||
const newOptions = [...localConfig.options];
|
||||
newOptions[index] = { ...newOptions[index], disabled: !checked };
|
||||
const newConfig = { ...localConfig, options: newOptions };
|
||||
setLocalConfig(newConfig);
|
||||
onUpdateProperty("webTypeConfig", newConfig);
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="destructive" onClick={() => removeOption(index)} className="p-1 text-xs">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
|
||||
@@ -44,6 +44,12 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
const [newOptionLabel, setNewOptionLabel] = useState("");
|
||||
const [newOptionValue, setNewOptionValue] = useState("");
|
||||
const [bulkOptions, setBulkOptions] = useState("");
|
||||
|
||||
// 입력 필드용 로컬 상태
|
||||
const [localInputs, setLocalInputs] = useState({
|
||||
placeholder: config.placeholder || "",
|
||||
emptyMessage: config.emptyMessage || "",
|
||||
});
|
||||
|
||||
// 컴포넌트 변경 시 로컬 상태 동기화
|
||||
useEffect(() => {
|
||||
@@ -61,6 +67,12 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
readonly: currentConfig.readonly || false,
|
||||
emptyMessage: currentConfig.emptyMessage || "선택 가능한 옵션이 없습니다",
|
||||
});
|
||||
|
||||
// 입력 필드 로컬 상태도 동기화
|
||||
setLocalInputs({
|
||||
placeholder: currentConfig.placeholder || "",
|
||||
emptyMessage: currentConfig.emptyMessage || "",
|
||||
});
|
||||
}, [widget.webTypeConfig]);
|
||||
|
||||
// 설정 업데이트 핸들러
|
||||
@@ -91,11 +103,16 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
updateConfig("options", newOptions);
|
||||
};
|
||||
|
||||
// 옵션 업데이트
|
||||
const updateOption = (index: number, field: keyof SelectOption, value: any) => {
|
||||
// 옵션 업데이트 (입력 필드용 - 로컬 상태만)
|
||||
const updateOptionLocal = (index: number, field: keyof SelectOption, value: any) => {
|
||||
const newOptions = [...localConfig.options];
|
||||
newOptions[index] = { ...newOptions[index], [field]: value };
|
||||
updateConfig("options", newOptions);
|
||||
setLocalConfig({ ...localConfig, options: newOptions });
|
||||
};
|
||||
|
||||
// 옵션 업데이트 완료 (onBlur)
|
||||
const handleOptionBlur = () => {
|
||||
onUpdateProperty("webTypeConfig", localConfig);
|
||||
};
|
||||
|
||||
// 벌크 옵션 추가
|
||||
@@ -170,8 +187,9 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="placeholder"
|
||||
value={localConfig.placeholder || ""}
|
||||
onChange={(e) => updateConfig("placeholder", e.target.value)}
|
||||
value={localInputs.placeholder}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, placeholder: e.target.value })}
|
||||
onBlur={() => updateConfig("placeholder", localInputs.placeholder)}
|
||||
placeholder="선택하세요"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -183,8 +201,9 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
</Label>
|
||||
<Input
|
||||
id="emptyMessage"
|
||||
value={localConfig.emptyMessage || ""}
|
||||
onChange={(e) => updateConfig("emptyMessage", e.target.value)}
|
||||
value={localInputs.emptyMessage}
|
||||
onChange={(e) => setLocalInputs({ ...localInputs, emptyMessage: e.target.value })}
|
||||
onBlur={() => updateConfig("emptyMessage", localInputs.emptyMessage)}
|
||||
placeholder="선택 가능한 옵션이 없습니다"
|
||||
className="text-xs"
|
||||
/>
|
||||
@@ -285,22 +304,30 @@ export const SelectConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
||||
<Label className="text-xs">현재 옵션 ({localConfig.options.length}개)</Label>
|
||||
<div className="max-h-40 space-y-2 overflow-y-auto">
|
||||
{localConfig.options.map((option, index) => (
|
||||
<div key={index} className="flex items-center gap-2 rounded border p-2">
|
||||
<div key={`${option.value}-${index}`} className="flex items-center gap-2 rounded border p-2">
|
||||
<Input
|
||||
value={option.label}
|
||||
onChange={(e) => updateOption(index, "label", e.target.value)}
|
||||
onChange={(e) => updateOptionLocal(index, "label", e.target.value)}
|
||||
onBlur={handleOptionBlur}
|
||||
placeholder="라벨"
|
||||
className="flex-1 text-xs"
|
||||
/>
|
||||
<Input
|
||||
value={option.value}
|
||||
onChange={(e) => updateOption(index, "value", e.target.value)}
|
||||
onChange={(e) => updateOptionLocal(index, "value", e.target.value)}
|
||||
onBlur={handleOptionBlur}
|
||||
placeholder="값"
|
||||
className="flex-1 text-xs"
|
||||
/>
|
||||
<Switch
|
||||
checked={!option.disabled}
|
||||
onCheckedChange={(checked) => updateOption(index, "disabled", !checked)}
|
||||
onCheckedChange={(checked) => {
|
||||
const newOptions = [...localConfig.options];
|
||||
newOptions[index] = { ...newOptions[index], disabled: !checked };
|
||||
const newConfig = { ...localConfig, options: newOptions };
|
||||
setLocalConfig(newConfig);
|
||||
onUpdateProperty("webTypeConfig", newConfig);
|
||||
}}
|
||||
/>
|
||||
<Button size="sm" variant="destructive" onClick={() => removeOption(index)} className="p-1 text-xs">
|
||||
<Trash2 className="h-3 w-3" />
|
||||
|
||||
@@ -78,3 +78,4 @@ export const numberingRuleTemplate = {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user