'use client'; import type { ReactNode } from 'react'; import { Pencil, Trash2 } from 'lucide-react'; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from '@/components/ui/context-menu'; import { useDashboardStore } from '@/stores/dashboardStore'; import { useMenu } from '@/contexts/MenuContext'; import { getDashboardList, deleteDashboard } from '@/lib/api/dashMenu'; import { toast } from 'sonner'; interface DashboardTarget { id: string; name: string; icon: string; is_personal: boolean; } interface MenuItemActionsProps { /** 메뉴 URL (예: "/123") — 대시보드 매핑 키. `/^\/\d+$/` 패턴이 아니면 그냥 children 만 렌더. */ menuUrl?: string; /** 표시용 메뉴/대시보드 이름 — confirm/prompt 안내 + rename 기본값 */ menuName: string; /** 우클릭 대상 — 메뉴 아이템 element. ContextMenuTrigger asChild 로 감싸짐. */ children: ReactNode; } const DASHBOARD_URL_RE = /^\/\d+$/; /** * 사이드바 / TopNav 메뉴 항목 우클릭 시 [이름 변경 / 삭제] 컨텍스트 메뉴. * 메뉴 url(/{seq}) 가 대시보드 패턴이 아니면 그냥 children 만 렌더. * * dashboardStore.dashboards 에서 menu_url 매칭으로 objid 도출. AppLayout 마운트 시 * store 가 채워지지만, race 시점엔 액션 시점에 lazy fetch fallback. */ export function MenuItemActions({ menuUrl, menuName, children }: MenuItemActionsProps) { const removeDashboard = useDashboardStore((s) => s.removeDashboard); const setDashboardsList = useDashboardStore((s) => s.setDashboards); const openDashEdit = useDashboardStore((s) => s.openEdit); const { refreshMenus } = useMenu(); // 대시보드 패턴 아니면 컨텍스트 메뉴 자체 비활성 — 일반 우클릭 동작 유지 if (!menuUrl || !DASHBOARD_URL_RE.test(menuUrl)) { return <>{children}; } const resolveDashboardTarget = async (): Promise => { let list = useDashboardStore.getState().dashboards; if (list.length === 0) { try { const result = await getDashboardList(); list = result?.list ?? []; setDashboardsList(list); } catch (err) { console.warn('[MenuItemActions] dashboard list fetch 실패', err); return null; } } const match = list.find((d) => (d.menu_url ?? d.MENU_URL) === menuUrl); if (!match) return null; const id = match.objid ?? match.OBJID ?? match.dashboard_id ?? match.DASHBOARD_ID; if (id == null) return null; return { id: String(id), name: String(match.name ?? match.NAME ?? menuName), icon: String(match.icon ?? match.ICON ?? 'ClipboardList'), is_personal: Boolean(match.user_id ?? match.USER_ID), }; }; const handleEdit = async () => { const target = await resolveDashboardTarget(); if (!target) { toast.error('대시보드 정보를 찾지 못했습니다'); return; } openDashEdit(target); }; const handleDelete = async () => { if (!confirm(`"${menuName}" 대시보드를 삭제합니다.\n이 작업은 되돌릴 수 없습니다.`)) return; const target = await resolveDashboardTarget(); if (!target) { toast.error('대시보드 정보를 찾지 못했습니다'); return; } try { await deleteDashboard(target.id); removeDashboard(target.id); try { await refreshMenus(); } catch { /* refresh 실패 무시 */ } toast.success('대시보드 삭제됨'); } catch (err) { console.error('[MenuItemActions] delete 실패', err); toast.error('삭제 실패'); } }; return ( {children} 수정 삭제 ); }