Files
gbpark 80cd2b2d07
Build & Deploy to K8s / build-and-deploy (push) Failing after 3m8s
fix: e70267f73 에서 누락된 untracked 파일 추가
- frontend/components/layout/MenuItemActions.tsx — AppLayout/TopNavBar 가 import 하는데 빠져서 webpack 빌드 fail
- backend-spring db migration V001 (varchar_migration) + V002 (create_missing_tables) 같이 누락분 정리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 06:22:02 +09:00

122 lines
4.1 KiB
TypeScript

'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<DashboardTarget | null> => {
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 (
<ContextMenu>
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
<ContextMenuContent className="w-40">
<ContextMenuItem onSelect={handleEdit}>
<Pencil className="mr-2 h-3.5 w-3.5" />
<span></span>
</ContextMenuItem>
<ContextMenuItem
onSelect={handleDelete}
className="text-red-600 focus:text-red-600"
>
<Trash2 className="mr-2 h-3.5 w-3.5" />
<span></span>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
}