80cd2b2d07
Build & Deploy to K8s / build-and-deploy (push) Failing after 3m8s
- 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>
122 lines
4.1 KiB
TypeScript
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>
|
|
);
|
|
}
|