- DB: menu_info(9000000) parent_obj_id를 0 → -395553955 (사용자) 로 변경 → 상단 [사용자] 탭 안에 '모모유통' 그룹으로 표시 - /api/admin/menus/delete: cascade=true 시 자손까지 재귀 삭제 (rel_menu_auth 동시 정리) - admin-panel 메뉴 관리: 하위 메뉴가 있으면 사용자에게 confirm 후 일괄 삭제
This commit is contained in:
@@ -522,13 +522,30 @@ function MenuManagement() {
|
||||
if (!selected) { Swal.fire("알림", "삭제할 메뉴를 선택하세요.", "warning"); return; }
|
||||
const r = await Swal.fire({ title: "삭제 확인", text: `"${selected.MENU_NAME_KOR}" 메뉴를 삭제하시겠습니까?`, icon: "warning", showCancelButton: true, confirmButtonText: "삭제", cancelButtonText: "취소" });
|
||||
if (!r.isConfirmed) return;
|
||||
const res = await fetch("/api/admin/menus/delete", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ objid: selected.OBJID }),
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
const callDelete = async (cascade: boolean) => {
|
||||
const res = await fetch("/api/admin/menus/delete", {
|
||||
method: "POST", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ objid: selected.OBJID, cascade }),
|
||||
});
|
||||
return res.json();
|
||||
};
|
||||
|
||||
let data = await callDelete(false);
|
||||
if (!data.success && data.needsCascade) {
|
||||
const r2 = await Swal.fire({
|
||||
icon: "warning",
|
||||
title: `하위 메뉴 ${data.childCount}개 함께 삭제`,
|
||||
text: "선택한 메뉴와 모든 하위 메뉴를 함께 삭제합니다. 계속하시겠습니까?",
|
||||
showCancelButton: true, confirmButtonText: "함께 삭제", cancelButtonText: "취소",
|
||||
confirmButtonColor: "#dc2626",
|
||||
});
|
||||
if (!r2.isConfirmed) return;
|
||||
data = await callDelete(true);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
Swal.fire({ icon: "success", title: "삭제되었습니다.", timer: 1200, showConfirmButton: false });
|
||||
Swal.fire({ icon: "success", title: `${data.deleted ?? 1}개 메뉴 삭제됨`, timer: 1500, showConfirmButton: false });
|
||||
setSelected(null); setEditForm({}); setShowForm(false);
|
||||
fetchData();
|
||||
} else { Swal.fire("오류", data.message, "error"); }
|
||||
|
||||
@@ -1,29 +1,55 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { execute } from "@/lib/db";
|
||||
import { pool } from "@/lib/db";
|
||||
import { getSession } from "@/lib/session";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const user = await getSession();
|
||||
if (!user) return NextResponse.json({ success: false }, { status: 401 });
|
||||
const body = await request.json();
|
||||
const { objid } = body;
|
||||
const { objid, cascade } = body as { objid?: string | number; cascade?: boolean };
|
||||
if (!objid) return NextResponse.json({ success: false, message: "삭제할 메뉴를 선택하세요." });
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
// 하위 메뉴가 있으면 삭제 불가
|
||||
const { queryOne } = await import("@/lib/db");
|
||||
const child = await queryOne<{ cnt: string }>(
|
||||
`SELECT COUNT(*) AS cnt FROM menu_info WHERE parent_obj_id = $1`,
|
||||
[parseInt(objid, 10)]
|
||||
await client.query("BEGIN");
|
||||
const target = Number(objid);
|
||||
|
||||
// 자손 모두 수집 (재귀)
|
||||
const descendants = await client.query<{ objid: number }>(
|
||||
`WITH RECURSIVE tree AS (
|
||||
SELECT objid FROM menu_info WHERE objid = $1
|
||||
UNION ALL
|
||||
SELECT m.objid FROM menu_info m JOIN tree t ON m.parent_obj_id = t.objid
|
||||
)
|
||||
SELECT objid FROM tree`,
|
||||
[target]
|
||||
);
|
||||
if (Number(child?.cnt || 0) > 0) {
|
||||
return NextResponse.json({ success: false, message: "하위 메뉴가 있어 삭제할 수 없습니다. 하위 메뉴를 먼저 삭제하세요." });
|
||||
|
||||
const ids = descendants.rows.map((r) => r.objid);
|
||||
const childCount = ids.length - 1;
|
||||
|
||||
// cascade 미지정인데 하위 있으면 거부
|
||||
if (childCount > 0 && !cascade) {
|
||||
await client.query("ROLLBACK");
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: `하위 메뉴 ${childCount}개가 있습니다. 함께 삭제하려면 다시 시도하세요.`,
|
||||
childCount,
|
||||
needsCascade: true,
|
||||
});
|
||||
}
|
||||
|
||||
await execute(`DELETE FROM menu_info WHERE objid = $1`, [parseInt(objid, 10)]);
|
||||
return NextResponse.json({ success: true, message: "삭제되었습니다." });
|
||||
// 권한-메뉴 매핑 정리 후 메뉴 삭제
|
||||
await client.query(`DELETE FROM rel_menu_auth WHERE menu_obj_id = ANY($1::bigint[])`, [ids]);
|
||||
const r = await client.query(`DELETE FROM menu_info WHERE objid = ANY($1::bigint[])`, [ids]);
|
||||
await client.query("COMMIT");
|
||||
return NextResponse.json({ success: true, deleted: r.rowCount, ids });
|
||||
} catch (e) {
|
||||
await client.query("ROLLBACK");
|
||||
console.error("Menu delete:", e);
|
||||
return NextResponse.json({ success: false, message: "삭제 중 오류가 발생했습니다." });
|
||||
const msg = e instanceof Error ? e.message : "삭제 중 오류";
|
||||
return NextResponse.json({ success: false, message: msg }, { status: 500 });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user