오류 수정

This commit is contained in:
kjs
2025-11-26 14:44:49 +09:00
parent 611fe9f788
commit 13fe9c97fe
10 changed files with 712 additions and 243 deletions

View File

@@ -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)}>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -78,3 +78,4 @@ export const numberingRuleTemplate = {