"use client"; import React, { useState, useEffect, useCallback } from "react"; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent, } from "@dnd-kit/core"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { Plus, GripVertical, Settings, X, Check, ChevronsUpDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; import type { ActionButtonConfig, ModalParamMapping, ColumnConfig } from "./types"; interface ScreenInfo { screen_id: number; screen_name: string; screen_code: string; } // 정렬 가능한 버튼 아이템 const SortableButtonItem: React.FC<{ id: string; button: ActionButtonConfig; index: number; onSettingsClick: () => void; onRemove: () => void; }> = ({ id, button, index, onSettingsClick, onRemove }) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }); const style = { transform: CSS.Transform.toString(transform), transition, }; const getVariantColor = (variant?: string) => { switch (variant) { case "destructive": return "bg-destructive/10 text-destructive"; case "outline": return "bg-background border"; case "ghost": return "bg-muted/50"; case "secondary": return "bg-secondary text-secondary-foreground"; default: return "bg-primary/10 text-primary"; } }; const getActionLabel = (action?: string) => { switch (action) { case "add": return "추가"; case "edit": return "수정"; case "delete": return "삭제"; case "bulk-delete": return "일괄삭제"; case "api": return "API"; case "custom": return "커스텀"; default: return "추가"; } }; return (
{/* 드래그 핸들 */}
{/* 버튼 정보 */}
{button.label || `버튼 ${index + 1}`}
{getActionLabel(button.action)} {button.icon && ( {button.icon} )} {button.showCondition && button.showCondition !== "always" && ( {button.showCondition === "selected" ? "선택시만" : "미선택시만"} )}
{/* 액션 버튼들 */}
); }; interface ActionButtonConfigModalProps { open: boolean; onOpenChange: (open: boolean) => void; actionButtons: ActionButtonConfig[]; displayColumns?: ColumnConfig[]; // 모달 파라미터 매핑용 onSave: (buttons: ActionButtonConfig[]) => void; side: "left" | "right"; } export const ActionButtonConfigModal: React.FC = ({ open, onOpenChange, actionButtons: initialButtons, displayColumns = [], onSave, side, }) => { // 로컬 상태 const [buttons, setButtons] = useState([]); // 버튼 세부설정 모달 const [detailModalOpen, setDetailModalOpen] = useState(false); const [editingButtonIndex, setEditingButtonIndex] = useState(null); const [editingButton, setEditingButton] = useState(null); // 화면 목록 const [screens, setScreens] = useState([]); const [screensLoading, setScreensLoading] = useState(false); const [screenSelectOpen, setScreenSelectOpen] = useState(false); // 드래그 센서 const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); // 초기값 설정 useEffect(() => { if (open) { setButtons(initialButtons || []); } }, [open, initialButtons]); // 화면 목록 로드 const loadScreens = useCallback(async () => { setScreensLoading(true); try { const response = await apiClient.get("/screen-management/screens?size=1000"); let screenList: any[] = []; if (response.data?.success && Array.isArray(response.data?.data)) { screenList = response.data.data; } else if (Array.isArray(response.data?.data)) { screenList = response.data.data; } const transformedScreens = screenList.map((s: any) => ({ screen_id: s.screenId ?? s.screen_id ?? s.id, screen_name: s.screenName ?? s.screen_name ?? s.name ?? `화면 ${s.screenId || s.screen_id || s.id}`, screen_code: s.screenCode ?? s.screen_code ?? s.code ?? "", })); setScreens(transformedScreens); } catch (error) { console.error("화면 목록 로드 실패:", error); setScreens([]); } finally { setScreensLoading(false); } }, []); useEffect(() => { if (open) { loadScreens(); } }, [open, loadScreens]); // 드래그 종료 핸들러 const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { const oldIndex = buttons.findIndex((btn) => btn.id === active.id); const newIndex = buttons.findIndex((btn) => btn.id === over.id); if (oldIndex !== -1 && newIndex !== -1) { setButtons(arrayMove(buttons, oldIndex, newIndex)); } } }; // 버튼 추가 const handleAddButton = () => { const newButton: ActionButtonConfig = { id: `btn-${Date.now()}`, label: "새 버튼", variant: "default", action: "add", showCondition: "always", }; setButtons([...buttons, newButton]); }; // 버튼 삭제 const handleRemoveButton = (index: number) => { setButtons(buttons.filter((_, i) => i !== index)); }; // 버튼 업데이트 const handleUpdateButton = (index: number, updates: Partial) => { const newButtons = [...buttons]; newButtons[index] = { ...newButtons[index], ...updates }; setButtons(newButtons); }; // 버튼 세부설정 열기 const handleOpenDetailSettings = (index: number) => { setEditingButtonIndex(index); setEditingButton({ ...buttons[index] }); setDetailModalOpen(true); }; // 버튼 세부설정 저장 const handleSaveDetailSettings = () => { if (editingButtonIndex !== null && editingButton) { handleUpdateButton(editingButtonIndex, editingButton); } setDetailModalOpen(false); setEditingButtonIndex(null); setEditingButton(null); }; // 저장 const handleSave = () => { onSave(buttons); onOpenChange(false); }; // 선택된 화면 정보 const getScreenInfo = (screenId?: number) => { return screens.find((s) => s.screen_id === screenId); }; return ( <> {side === "left" ? "좌측" : "우측"} 패널 액션 버튼 설정 버튼을 추가하고 순서를 드래그로 변경할 수 있습니다.
{buttons.length === 0 ? (

액션 버튼이 없습니다

) : ( btn.id)} strategy={verticalListSortingStrategy} >
{buttons.map((btn, index) => ( handleOpenDetailSettings(index)} onRemove={() => handleRemoveButton(index)} /> ))}
)}
{/* 버튼 세부설정 모달 */} 버튼 세부설정 {editingButton?.label || "버튼"}의 동작을 설정합니다. {editingButton && (
{/* 기본 설정 */}

기본 설정

setEditingButton({ ...editingButton, label: e.target.value }) } placeholder="버튼 라벨" className="mt-1 h-9" />
{/* 동작 설정 */}

동작 설정

{/* 모달 설정 (add, edit 액션) */} {(editingButton.action === "add" || editingButton.action === "edit") && (
검색 결과가 없습니다 {screens.map((screen) => ( { setEditingButton({ ...editingButton, modalScreenId: screen.screen_id, }); setScreenSelectOpen(false); }} > {screen.screen_name} {screen.screen_code} ))}
)} {/* API 설정 */} {editingButton.action === "api" && ( <>
setEditingButton({ ...editingButton, apiEndpoint: e.target.value, }) } placeholder="/api/example" className="mt-1 h-9" />
)} {/* 확인 메시지 (삭제 계열) */} {(editingButton.action === "delete" || editingButton.action === "bulk-delete" || (editingButton.action === "api" && editingButton.apiMethod === "DELETE")) && (
setEditingButton({ ...editingButton, confirmMessage: e.target.value, }) } placeholder="정말 삭제하시겠습니까?" className="mt-1 h-9" />
)} {/* 커스텀 액션 ID */} {editingButton.action === "custom" && (
setEditingButton({ ...editingButton, customActionId: e.target.value, }) } placeholder="customAction1" className="mt-1 h-9" />

커스텀 이벤트 핸들러에서 이 ID로 버튼을 구분합니다

)}
)}
); }; export default ActionButtonConfigModal;