추가모달 상세설정 구현

This commit is contained in:
kjs
2025-09-03 17:12:27 +09:00
parent 941c6d9d84
commit b5edef274f
2 changed files with 877 additions and 44 deletions

View File

@@ -84,6 +84,12 @@ export const DataTableConfigPanel: React.FC<DataTableConfigPanelProps> = ({ comp
// 필터별 로컬 입력 상태
const [localFilterInputs, setLocalFilterInputs] = useState<Record<string, string>>({});
// 컬럼별 상세 설정 상태
const [localColumnDetailSettings, setLocalColumnDetailSettings] = useState<Record<string, any>>({});
// 컬럼별 상세 설정 확장/축소 상태
const [isColumnDetailOpen, setIsColumnDetailOpen] = useState<Record<string, boolean>>({});
// 모달 설정 확장/축소 상태
const [isModalConfigOpen, setIsModalConfigOpen] = useState<Record<string, boolean>>({});
@@ -379,6 +385,347 @@ export const DataTableConfigPanel: React.FC<DataTableConfigPanelProps> = ({ comp
[component.columns, onUpdateComponent],
);
// 컬럼 상세 설정 업데이트 (테이블 타입 관리에도 반영)
const updateColumnDetailSettings = useCallback(
async (columnId: string, webTypeConfig: any) => {
// 1. 먼저 화면 컴포넌트의 컬럼 설정 업데이트
const updatedColumns = component.columns.map((col) => (col.id === columnId ? { ...col, webTypeConfig } : col));
console.log("🔄 컬럼 상세 설정 업데이트:", { columnId, webTypeConfig, updatedColumns });
onUpdateComponent({ columns: updatedColumns });
// 2. 테이블 타입 관리에도 반영 (라디오 타입인 경우)
const targetColumn = component.columns.find((col) => col.id === columnId);
if (targetColumn && targetColumn.widgetType === "radio" && selectedTable) {
try {
// TODO: 테이블 타입 관리 API 호출하여 웹 타입과 상세 설정 업데이트
console.log("📡 테이블 타입 관리 업데이트 필요:", {
tableName: component.tableName,
columnName: targetColumn.columnName,
webType: "radio",
detailSettings: JSON.stringify(webTypeConfig),
});
} catch (error) {
console.error("테이블 타입 관리 업데이트 실패:", error);
}
}
},
[component.columns, component.tableName, selectedTable, onUpdateComponent],
);
// 컬럼의 현재 웹 타입 가져오기 (테이블 타입 관리에서 설정된 값)
const getColumnWebType = useCallback(
(column: DataTableColumn) => {
// 테이블 타입 관리에서 설정된 웹 타입 찾기
if (!selectedTable) return "text";
const tableColumn = selectedTable.columns.find((col) => col.columnName === column.columnName);
return (
tableColumn?.webType ||
getWidgetTypeFromColumn(tableColumn || { columnName: column.columnName, dataType: "text" })
);
},
[selectedTable],
);
// 컬럼의 현재 상세 설정 가져오기
const getColumnCurrentDetailSettings = useCallback((column: DataTableColumn) => {
return column.webTypeConfig || {};
}, []);
// 웹 타입별 상세 설정 렌더링
const renderColumnDetailSettings = useCallback(
(column: DataTableColumn) => {
const webType = getColumnWebType(column);
const currentSettings = getColumnCurrentDetailSettings(column);
const localSettings = localColumnDetailSettings[column.id] || currentSettings;
const updateSettings = (newSettings: any) => {
const merged = { ...localSettings, ...newSettings };
setLocalColumnDetailSettings((prev) => ({
...prev,
[column.id]: merged,
}));
updateColumnDetailSettings(column.id, merged);
};
switch (webType) {
case "select":
case "dropdown":
case "radio":
return (
<div className="space-y-3">
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<div className="space-y-2">
{(localSettings.options || []).map((option: any, index: number) => {
// 안전한 값 추출
const currentLabel =
typeof option === "object" && option !== null
? option.label || option.value || ""
: String(option || "");
return (
<div key={index} className="flex items-center space-x-2">
<Input
value={currentLabel}
onChange={(e) => {
const newOptions = [...(localSettings.options || [])];
newOptions[index] = { label: e.target.value, value: e.target.value };
updateSettings({ options: newOptions });
}}
placeholder="옵션명"
className="h-7 text-xs"
/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => {
const newOptions = (localSettings.options || []).filter((_: any, i: number) => i !== index);
updateSettings({ options: newOptions });
}}
className="h-7 w-7 p-0"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
);
})}
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
const newOption = { label: "", value: "" };
updateSettings({ options: [...(localSettings.options || []), newOption] });
}}
className="h-7 text-xs"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
{webType === "radio" ? (
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Select
value={localSettings.defaultValue || "__NONE__"}
onValueChange={(value) => updateSettings({ defaultValue: value === "__NONE__" ? "" : value })}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="기본값 선택..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="__NONE__"> </SelectItem>
{(localSettings.options || []).map((option: any, index: number) => {
// 안전한 문자열 변환
const getStringValue = (val: any): string => {
if (typeof val === "string") return val;
if (typeof val === "number") return String(val);
if (typeof val === "object" && val !== null) {
return val.label || val.value || val.name || JSON.stringify(val);
}
return String(val || "");
};
const optionValue = getStringValue(option.value || option.label || option) || `option-${index}`;
const optionLabel =
getStringValue(option.label || option.value || option) || `옵션 ${index + 1}`;
return (
<SelectItem key={index} value={optionValue}>
{optionLabel}
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
) : (
<div className="flex items-center space-x-2">
<Checkbox
checked={localSettings.multiple || false}
onCheckedChange={(checked) => updateSettings({ multiple: checked })}
/>
<Label className="text-xs"> </Label>
</div>
)}
</div>
);
case "number":
case "decimal":
return (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-xs"></Label>
<Input
type="number"
value={localSettings.min || ""}
onChange={(e) => updateSettings({ min: e.target.value ? Number(e.target.value) : undefined })}
placeholder="최소값"
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"></Label>
<Input
type="number"
value={localSettings.max || ""}
onChange={(e) => updateSettings({ max: e.target.value ? Number(e.target.value) : undefined })}
placeholder="최대값"
className="h-7 text-xs"
/>
</div>
</div>
{webType === "decimal" && (
<div className="space-y-1">
<Label className="text-xs"> (step)</Label>
<Input
type="number"
step="0.01"
value={localSettings.step || "0.01"}
onChange={(e) => updateSettings({ step: e.target.value })}
placeholder="0.01"
className="h-7 text-xs"
/>
</div>
)}
</div>
);
case "date":
case "datetime":
return (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
type="date"
value={localSettings.minDate || ""}
onChange={(e) => updateSettings({ minDate: e.target.value })}
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
type="date"
value={localSettings.maxDate || ""}
onChange={(e) => updateSettings({ maxDate: e.target.value })}
className="h-7 text-xs"
/>
</div>
</div>
{webType === "datetime" && (
<div className="flex items-center space-x-2">
<Checkbox
checked={localSettings.showSeconds || false}
onCheckedChange={(checked) => updateSettings({ showSeconds: checked })}
/>
<Label className="text-xs"> </Label>
</div>
)}
</div>
);
case "text":
case "email":
case "tel":
return (
<div className="space-y-3">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
type="number"
value={localSettings.maxLength || ""}
onChange={(e) => updateSettings({ maxLength: e.target.value ? Number(e.target.value) : undefined })}
placeholder="최대 문자 수"
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"></Label>
<Input
value={localSettings.placeholder || ""}
onChange={(e) => updateSettings({ placeholder: e.target.value })}
placeholder="입력 안내 텍스트"
className="h-7 text-xs"
/>
</div>
</div>
);
case "textarea":
return (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
type="number"
value={localSettings.rows || "3"}
onChange={(e) => updateSettings({ rows: Number(e.target.value) })}
placeholder="3"
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
type="number"
value={localSettings.maxLength || ""}
onChange={(e) => updateSettings({ maxLength: e.target.value ? Number(e.target.value) : undefined })}
placeholder="최대 문자 수"
className="h-7 text-xs"
/>
</div>
</div>
</div>
);
case "file":
return (
<div className="space-y-3">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={localSettings.accept || ""}
onChange={(e) => updateSettings({ accept: e.target.value })}
placeholder=".jpg,.png,.pdf"
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"> (MB)</Label>
<Input
type="number"
value={localSettings.maxSize ? localSettings.maxSize / 1024 / 1024 : "10"}
onChange={(e) => updateSettings({ maxSize: Number(e.target.value) * 1024 * 1024 })}
placeholder="10"
className="h-7 text-xs"
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
checked={localSettings.multiple || false}
onCheckedChange={(checked) => updateSettings({ multiple: checked })}
/>
<Label className="text-xs"> </Label>
</div>
</div>
);
default:
return <div className="py-2 text-xs text-gray-500"> ({webType}) .</div>;
}
},
[getColumnWebType, getColumnCurrentDetailSettings, localColumnDetailSettings, updateColumnDetailSettings],
);
// 컬럼 삭제
const removeColumn = useCallback(
(columnId: string) => {
@@ -1060,19 +1407,39 @@ export const DataTableConfigPanel: React.FC<DataTableConfigPanelProps> = ({ comp
<Badge variant="outline" className="text-xs">
{column.columnName}
</Badge>
<Badge variant="secondary" className="text-xs">
{getColumnWebType(column)}
</Badge>
</div>
<div className="flex items-center space-x-1">
<Button
type="button"
variant="ghost"
size="sm"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIsColumnDetailOpen((prev) => ({
...prev,
[column.id]: !prev[column.id],
}));
}}
>
<Settings className="h-3 w-3" />
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeColumn(column.id);
}}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeColumn(column.id);
}}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<div className="space-y-1">
@@ -1162,6 +1529,19 @@ export const DataTableConfigPanel: React.FC<DataTableConfigPanelProps> = ({ comp
</div>
</div>
{/* 웹 타입 상세 설정 */}
{isColumnDetailOpen[column.id] && (
<div className="border-t pt-2">
<div className="mb-2 flex items-center justify-between">
<Label className="text-xs font-medium"> </Label>
<Badge variant="outline" className="text-xs">
{getColumnWebType(column)}
</Badge>
</div>
<div className="pl-2">{renderColumnDetailSettings(column)}</div>
</div>
)}
{/* 모달 전용 설정 */}
{component.enableAdd && (
<div className="border-t pt-2">