ERP-node/frontend/components/screen/panels/ButtonConfigPanel.tsx

438 lines
17 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Badge } from "@/components/ui/badge";
import {
Save,
X,
Trash2,
Edit,
Plus,
RotateCcw,
Send,
ExternalLink,
MousePointer,
Settings,
AlertTriangle,
} from "lucide-react";
import { ButtonActionType, ButtonTypeConfig, WidgetComponent } from "@/types/screen";
interface ButtonConfigPanelProps {
component: WidgetComponent;
onUpdateComponent: (updates: Partial<WidgetComponent>) => void;
}
const actionTypeOptions: { value: ButtonActionType; label: string; icon: React.ReactNode; color: string }[] = [
{ value: "save", label: "저장", icon: <Save className="h-4 w-4" />, color: "#3b82f6" },
{ value: "cancel", label: "취소", icon: <X className="h-4 w-4" />, color: "#6b7280" },
{ value: "delete", label: "삭제", icon: <Trash2 className="h-4 w-4" />, color: "#ef4444" },
{ value: "edit", label: "수정", icon: <Edit className="h-4 w-4" />, color: "#f59e0b" },
{ value: "add", label: "추가", icon: <Plus className="h-4 w-4" />, color: "#10b981" },
{ value: "search", label: "검색", icon: <MousePointer className="h-4 w-4" />, color: "#8b5cf6" },
{ value: "reset", label: "초기화", icon: <RotateCcw className="h-4 w-4" />, color: "#6b7280" },
{ value: "submit", label: "제출", icon: <Send className="h-4 w-4" />, color: "#059669" },
{ value: "close", label: "닫기", icon: <X className="h-4 w-4" />, color: "#6b7280" },
{ value: "popup", label: "팝업 열기", icon: <ExternalLink className="h-4 w-4" />, color: "#8b5cf6" },
{ value: "navigate", label: "페이지 이동", icon: <ExternalLink className="h-4 w-4" />, color: "#0ea5e9" },
{ value: "custom", label: "사용자 정의", icon: <Settings className="h-4 w-4" />, color: "#64748b" },
];
export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({ component, onUpdateComponent }) => {
const config = (component.webTypeConfig as ButtonTypeConfig) || {};
// 로컬 상태 관리
const [localConfig, setLocalConfig] = useState<ButtonTypeConfig>({
actionType: "custom",
variant: "default",
size: "sm",
...config,
});
// 컴포넌트 변경 시 로컬 상태 동기화
useEffect(() => {
const newConfig = (component.webTypeConfig as ButtonTypeConfig) || {};
setLocalConfig({
actionType: "custom",
variant: "default",
size: "sm",
...newConfig,
});
}, [component.webTypeConfig]);
// 설정 업데이트 함수
const updateConfig = (updates: Partial<ButtonTypeConfig>) => {
const newConfig = { ...localConfig, ...updates };
setLocalConfig(newConfig);
// 스타일 업데이트도 함께 적용
const styleUpdates: any = {};
if (updates.backgroundColor) styleUpdates.backgroundColor = updates.backgroundColor;
if (updates.textColor) styleUpdates.color = updates.textColor;
if (updates.borderColor) styleUpdates.borderColor = updates.borderColor;
onUpdateComponent({
webTypeConfig: newConfig,
...(Object.keys(styleUpdates).length > 0 && {
style: { ...component.style, ...styleUpdates },
}),
});
};
// 액션 타입 변경 시 기본값 설정
const handleActionTypeChange = (actionType: ButtonActionType) => {
const actionOption = actionTypeOptions.find((opt) => opt.value === actionType);
const updates: Partial<ButtonTypeConfig> = { actionType };
// 액션 타입에 따른 기본 설정
switch (actionType) {
case "save":
updates.variant = "default";
updates.backgroundColor = "#3b82f6";
updates.textColor = "#ffffff";
// 버튼 라벨과 스타일도 업데이트
onUpdateComponent({
label: "저장",
style: { ...component.style, backgroundColor: "#3b82f6", color: "#ffffff" },
});
break;
case "cancel":
case "close":
updates.variant = "outline";
updates.backgroundColor = "transparent";
updates.textColor = "#6b7280";
onUpdateComponent({
label: actionType === "cancel" ? "취소" : "닫기",
style: { ...component.style, backgroundColor: "transparent", color: "#6b7280", border: "1px solid #d1d5db" },
});
break;
case "delete":
updates.variant = "destructive";
updates.backgroundColor = "#ef4444";
updates.textColor = "#ffffff";
updates.confirmMessage = "정말로 삭제하시겠습니까?";
onUpdateComponent({
label: "삭제",
style: { ...component.style, backgroundColor: "#ef4444", color: "#ffffff" },
});
break;
case "edit":
updates.backgroundColor = "#f59e0b";
updates.textColor = "#ffffff";
onUpdateComponent({
label: "수정",
style: { ...component.style, backgroundColor: "#f59e0b", color: "#ffffff" },
});
break;
case "add":
updates.backgroundColor = "#10b981";
updates.textColor = "#ffffff";
onUpdateComponent({
label: "추가",
style: { ...component.style, backgroundColor: "#10b981", color: "#ffffff" },
});
break;
case "search":
updates.backgroundColor = "#8b5cf6";
updates.textColor = "#ffffff";
onUpdateComponent({
label: "검색",
style: { ...component.style, backgroundColor: "#8b5cf6", color: "#ffffff" },
});
break;
case "reset":
updates.variant = "outline";
updates.backgroundColor = "transparent";
updates.textColor = "#6b7280";
onUpdateComponent({
label: "초기화",
style: { ...component.style, backgroundColor: "transparent", color: "#6b7280", border: "1px solid #d1d5db" },
});
break;
case "submit":
updates.backgroundColor = "#059669";
updates.textColor = "#ffffff";
onUpdateComponent({
label: "제출",
style: { ...component.style, backgroundColor: "#059669", color: "#ffffff" },
});
break;
case "popup":
updates.backgroundColor = "#8b5cf6";
updates.textColor = "#ffffff";
updates.popupTitle = "상세 정보";
updates.popupContent = "여기에 팝업 내용을 입력하세요.";
updates.popupSize = "md";
onUpdateComponent({
label: "상세보기",
style: { ...component.style, backgroundColor: "#8b5cf6", color: "#ffffff" },
});
break;
case "navigate":
updates.backgroundColor = "#0ea5e9";
updates.textColor = "#ffffff";
updates.navigateUrl = "/";
updates.navigateTarget = "_self";
onUpdateComponent({
label: "이동",
style: { ...component.style, backgroundColor: "#0ea5e9", color: "#ffffff" },
});
break;
case "custom":
updates.backgroundColor = "#64748b";
updates.textColor = "#ffffff";
onUpdateComponent({
label: "버튼",
style: { ...component.style, backgroundColor: "#64748b", color: "#ffffff" },
});
break;
}
updateConfig(updates);
};
const selectedActionOption = actionTypeOptions.find((opt) => opt.value === localConfig.actionType);
return (
<div className="space-y-4">
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-sm font-medium">
<Settings className="h-4 w-4" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* 액션 타입 선택 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Select value={localConfig.actionType} onValueChange={handleActionTypeChange}>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
{actionTypeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
<div className="flex items-center gap-2">
{option.icon}
<span>{option.label}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
{selectedActionOption && (
<div className="flex items-center gap-2 text-xs text-gray-500">
{selectedActionOption.icon}
<span>{selectedActionOption.label}</span>
<Badge
variant="outline"
style={{ backgroundColor: selectedActionOption.color + "20", color: selectedActionOption.color }}
>
{selectedActionOption.value}
</Badge>
</div>
)}
</div>
<Separator />
{/* 기본 설정 */}
<div className="space-y-3">
<Label className="text-xs font-medium"> </Label>
{/* 버튼 텍스트 */}
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={component.label || ""}
onChange={(e) => {
const newValue = e.target.value;
onUpdateComponent({ label: newValue });
}}
placeholder="버튼에 표시될 텍스트"
className="h-8 text-xs"
/>
</div>
{/* 버튼 스타일 */}
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-xs"></Label>
<Select value={localConfig.variant} onValueChange={(value) => updateConfig({ variant: value as any })}>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="default"></SelectItem>
<SelectItem value="destructive"></SelectItem>
<SelectItem value="outline"></SelectItem>
<SelectItem value="secondary"></SelectItem>
<SelectItem value="ghost"></SelectItem>
<SelectItem value="link"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs"></Label>
<Select value={localConfig.size} onValueChange={(value) => updateConfig({ size: value as any })}>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="sm"></SelectItem>
<SelectItem value="default"></SelectItem>
<SelectItem value="lg"></SelectItem>
<SelectItem value="icon"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* 아이콘 설정 */}
<div className="space-y-1">
<Label className="text-xs"> (Lucide )</Label>
<Input
value={localConfig.icon || ""}
onChange={(e) => updateConfig({ icon: e.target.value })}
placeholder="예: Save, Edit, Trash2"
className="h-8 text-xs"
/>
</div>
</div>
<Separator />
{/* 액션별 세부 설정 */}
{localConfig.actionType === "delete" && (
<div className="space-y-3">
<Label className="flex items-center gap-1 text-xs font-medium">
<AlertTriangle className="h-3 w-3 text-red-500" />
</Label>
<div className="space-y-2">
<Label className="text-xs"> </Label>
<Input
value={localConfig.confirmMessage || ""}
onChange={(e) => updateConfig({ confirmMessage: e.target.value })}
placeholder="정말로 삭제하시겠습니까?"
className="h-8 text-xs"
/>
</div>
</div>
)}
{localConfig.actionType === "popup" && (
<div className="space-y-3">
<Label className="flex items-center gap-1 text-xs font-medium">
<ExternalLink className="h-3 w-3 text-purple-500" />
</Label>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={localConfig.popupTitle || ""}
onChange={(e) => updateConfig({ popupTitle: e.target.value })}
placeholder="상세 정보"
className="h-8 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={localConfig.popupSize}
onValueChange={(value) => updateConfig({ popupSize: value as any })}
>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="sm"></SelectItem>
<SelectItem value="md"></SelectItem>
<SelectItem value="lg"></SelectItem>
<SelectItem value="xl"> </SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Textarea
value={localConfig.popupContent || ""}
onChange={(e) => updateConfig({ popupContent: e.target.value })}
placeholder="여기에 팝업 내용을 입력하세요."
className="h-16 resize-none text-xs"
/>
</div>
</div>
</div>
)}
{localConfig.actionType === "navigate" && (
<div className="space-y-3">
<Label className="flex items-center gap-1 text-xs font-medium">
<ExternalLink className="h-3 w-3 text-blue-500" />
</Label>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-xs"> URL</Label>
<Input
value={localConfig.navigateUrl || ""}
onChange={(e) => updateConfig({ navigateUrl: e.target.value })}
placeholder="/admin/users"
className="h-8 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={localConfig.navigateTarget}
onValueChange={(value) => updateConfig({ navigateTarget: value as any })}
>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="_self"> </SelectItem>
<SelectItem value="_blank"> </SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
)}
{localConfig.actionType === "custom" && (
<div className="space-y-3">
<Label className="flex items-center gap-1 text-xs font-medium">
<Settings className="h-3 w-3 text-gray-500" />
</Label>
<div className="space-y-2">
<Label className="text-xs">JavaScript </Label>
<Textarea
value={localConfig.customAction || ""}
onChange={(e) => updateConfig({ customAction: e.target.value })}
placeholder="alert('버튼이 클릭되었습니다!');"
className="h-16 resize-none font-mono text-xs"
/>
<div className="text-xs text-gray-500">
JavaScript . : alert(), console.log(),
</div>
</div>
</div>
)}
</CardContent>
</Card>
</div>
);
};