ERP-node/frontend/components/screen/config-panels/CheckboxConfigPanel.tsx

449 lines
17 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { CheckSquare, Plus, Trash2 } from "lucide-react";
import { WebTypeConfigPanelProps } from "@/lib/registry/types";
import { WidgetComponent, CheckboxTypeConfig } from "@/types/screen";
interface CheckboxOption {
label: string;
value: string;
checked?: boolean;
disabled?: boolean;
}
export const CheckboxConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
component,
onUpdateComponent,
onUpdateProperty,
}) => {
const widget = component as WidgetComponent;
const config = (widget.webTypeConfig as CheckboxTypeConfig) || {};
// 로컬 상태
const [localConfig, setLocalConfig] = useState<CheckboxTypeConfig>({
// 단일 체크박스용
label: config.label || "",
checkedValue: config.checkedValue || "Y",
uncheckedValue: config.uncheckedValue || "N",
defaultChecked: config.defaultChecked || false,
// 다중 체크박스용 (체크박스 그룹)
options: config.options || [],
isGroup: config.isGroup || false,
groupLabel: config.groupLabel || "",
// 공통 설정
required: config.required || false,
readonly: config.readonly || false,
inline: config.inline !== false, // 기본값 true
});
// 새 옵션 추가용 상태
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(() => {
const currentConfig = (widget.webTypeConfig as CheckboxTypeConfig) || {};
setLocalConfig({
label: currentConfig.label || "",
checkedValue: currentConfig.checkedValue || "Y",
uncheckedValue: currentConfig.uncheckedValue || "N",
defaultChecked: currentConfig.defaultChecked || false,
options: currentConfig.options || [],
isGroup: currentConfig.isGroup || false,
groupLabel: currentConfig.groupLabel || "",
required: currentConfig.required || false,
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]);
// 설정 업데이트 핸들러
const updateConfig = (field: keyof CheckboxTypeConfig, value: any) => {
const newConfig = { ...localConfig, [field]: value };
setLocalConfig(newConfig);
onUpdateProperty("webTypeConfig", newConfig);
};
// 체크박스 유형 변경
const toggleCheckboxType = (isGroup: boolean) => {
if (isGroup && localConfig.options.length === 0) {
// 그룹으로 변경할 때 기본 옵션 추가
const defaultOptions: CheckboxOption[] = [
{ label: "옵션 1", value: "option1" },
{ label: "옵션 2", value: "option2" },
];
updateConfig("options", defaultOptions);
}
updateConfig("isGroup", isGroup);
};
// 옵션 추가
const addOption = () => {
if (!newOptionLabel.trim() || !newOptionValue.trim()) return;
const newOption: CheckboxOption = {
label: newOptionLabel.trim(),
value: newOptionValue.trim(),
checked: false,
};
const newOptions = [...localConfig.options, newOption];
updateConfig("options", newOptions);
setNewOptionLabel("");
setNewOptionValue("");
};
// 옵션 제거
const removeOption = (index: number) => {
const newOptions = localConfig.options.filter((_, i) => i !== index);
updateConfig("options", newOptions);
};
// 옵션 업데이트 (입력 필드용 - 로컬 상태만)
const updateOptionLocal = (index: number, field: keyof CheckboxOption, value: any) => {
const newOptions = [...localConfig.options];
newOptions[index] = { ...newOptions[index], [field]: value };
setLocalConfig({ ...localConfig, options: newOptions });
};
// 옵션 업데이트 완료 (onBlur)
const handleOptionBlur = () => {
onUpdateProperty("webTypeConfig", localConfig);
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-xs">
<CheckSquare className="h-4 w-4" />
</CardTitle>
<CardDescription className="text-xs"> , , .</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* 체크박스 유형 선택 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="grid grid-cols-2 gap-2">
<button
type="button"
onClick={() => toggleCheckboxType(false)}
className={`rounded border p-3 text-xs ${
!localConfig.isGroup ? "bg-primary text-primary-foreground" : "bg-background"
}`}
>
<div className="flex flex-col items-center gap-1">
<CheckSquare className="h-4 w-4" />
<span> </span>
</div>
</button>
<button
type="button"
onClick={() => toggleCheckboxType(true)}
className={`rounded border p-3 text-xs ${
localConfig.isGroup ? "bg-primary text-primary-foreground" : "bg-background"
}`}
>
<div className="flex flex-col items-center gap-1">
<div className="flex gap-1">
<CheckSquare className="h-3 w-3" />
<CheckSquare className="h-3 w-3" />
</div>
<span> </span>
</div>
</button>
</div>
</div>
{!localConfig.isGroup ? (
/* 단일 체크박스 설정 */
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label htmlFor="label" className="text-xs">
</Label>
<Input
id="label"
value={localInputs.label}
onChange={(e) => setLocalInputs({ ...localInputs, label: e.target.value })}
onBlur={() => updateConfig("label", localInputs.label)}
placeholder="체크박스 라벨"
className="text-xs"
/>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-2">
<Label htmlFor="checkedValue" className="text-xs">
</Label>
<Input
id="checkedValue"
value={localInputs.checkedValue}
onChange={(e) => setLocalInputs({ ...localInputs, checkedValue: e.target.value })}
onBlur={() => updateConfig("checkedValue", localInputs.checkedValue)}
placeholder="Y"
className="text-xs"
/>
</div>
<div className="space-y-2">
<Label htmlFor="uncheckedValue" className="text-xs">
</Label>
<Input
id="uncheckedValue"
value={localInputs.uncheckedValue}
onChange={(e) => setLocalInputs({ ...localInputs, uncheckedValue: e.target.value })}
onBlur={() => updateConfig("uncheckedValue", localInputs.uncheckedValue)}
placeholder="N"
className="text-xs"
/>
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="defaultChecked" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="defaultChecked"
checked={localConfig.defaultChecked || false}
onCheckedChange={(checked) => updateConfig("defaultChecked", checked)}
/>
</div>
</div>
) : (
/* 체크박스 그룹 설정 */
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="space-y-2">
<Label htmlFor="groupLabel" className="text-xs">
</Label>
<Input
id="groupLabel"
value={localInputs.groupLabel}
onChange={(e) => setLocalInputs({ ...localInputs, groupLabel: e.target.value })}
onBlur={() => updateConfig("groupLabel", localInputs.groupLabel)}
placeholder="체크박스 그룹 제목"
className="text-xs"
/>
</div>
{/* 옵션 추가 */}
<div className="space-y-2">
<Label className="text-xs"> </Label>
<div className="flex gap-2">
<Input
value={newOptionLabel}
onChange={(e) => setNewOptionLabel(e.target.value)}
placeholder="라벨"
className="flex-1 text-xs"
/>
<Input
value={newOptionValue}
onChange={(e) => setNewOptionValue(e.target.value)}
placeholder="값"
className="flex-1 text-xs"
/>
<Button
size="sm"
onClick={addOption}
disabled={!newOptionLabel.trim() || !newOptionValue.trim()}
className="text-xs"
>
<Plus className="h-3 w-3" />
</Button>
</div>
</div>
{/* 현재 옵션 목록 */}
<div className="space-y-2">
<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={`${option.value}-${index}`} className="flex items-center gap-2 rounded border p-2">
<Switch
checked={option.checked || false}
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) => updateOptionLocal(index, "label", e.target.value)}
onBlur={handleOptionBlur}
placeholder="라벨"
className="flex-1 text-xs"
/>
<Input
value={option.value}
onChange={(e) => updateOptionLocal(index, "value", e.target.value)}
onBlur={handleOptionBlur}
placeholder="값"
className="flex-1 text-xs"
/>
<Switch
checked={!option.disabled}
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" />
</Button>
</div>
))}
</div>
</div>
</div>
)}
{/* 공통 설정 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"> </h4>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="inline" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="inline"
checked={localConfig.inline !== false}
onCheckedChange={(checked) => updateConfig("inline", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="required" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs">
{localConfig.isGroup ? "최소 하나 이상 선택해야 합니다." : "체크박스가 선택되어야 합니다."}
</p>
</div>
<Switch
id="required"
checked={localConfig.required || false}
onCheckedChange={(checked) => updateConfig("required", checked)}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label htmlFor="readonly" className="text-xs">
</Label>
<p className="text-muted-foreground text-xs"> .</p>
</div>
<Switch
id="readonly"
checked={localConfig.readonly || false}
onCheckedChange={(checked) => updateConfig("readonly", checked)}
/>
</div>
</div>
{/* 미리보기 */}
<div className="space-y-3">
<h4 className="text-sm font-medium"></h4>
<div className="bg-muted/50 rounded-md border p-3">
{!localConfig.isGroup ? (
/* 단일 체크박스 미리보기 */
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="preview-single"
disabled={localConfig.readonly}
required={localConfig.required}
defaultChecked={localConfig.defaultChecked}
className="text-xs"
/>
<Label htmlFor="preview-single" className="text-xs">
{localConfig.label || "체크박스 라벨"}
</Label>
</div>
) : (
/* 체크박스 그룹 미리보기 */
<div className="space-y-2">
{localConfig.groupLabel && <Label className="text-xs font-medium">{localConfig.groupLabel}</Label>}
<div className={`space-y-1 ${localConfig.inline ? "flex gap-4" : ""}`}>
{localConfig.options.map((option, index) => (
<div key={index} className="flex items-center space-x-2">
<input
type="checkbox"
id={`preview-group-${index}`}
disabled={localConfig.readonly || option.disabled}
required={localConfig.required && index === 0} // 번째에만 required 표시
defaultChecked={option.checked}
className="text-xs"
/>
<Label htmlFor={`preview-group-${index}`} className="text-xs">
{option.label}
</Label>
</div>
))}
</div>
</div>
)}
<div className="text-muted-foreground mt-2 text-xs">
{localConfig.isGroup
? `${localConfig.options.length}개 옵션`
: `값: ${localConfig.checkedValue}/${localConfig.uncheckedValue}`}
{localConfig.inline && " • 가로 배열"}
{localConfig.required && " • 필수"}
</div>
</div>
</div>
</CardContent>
</Card>
);
};
CheckboxConfigPanel.displayName = "CheckboxConfigPanel";