ERP-node/frontend/components/admin/ScreenAssignmentTab.tsx

396 lines
15 KiB
TypeScript
Raw Normal View History

2025-09-01 18:42:59 +09:00
"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { toast } from "sonner";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Search, Plus, X, Monitor, Settings } from "lucide-react";
import { menuScreenApi, screenApi } from "@/lib/api/screen";
import { ScreenDefinition } from "@/types/screen";
import type { MenuItem } from "@/lib/api/menu";
interface ScreenAssignmentTabProps {
menus: MenuItem[];
}
export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus }) => {
const [selectedMenuId, setSelectedMenuId] = useState<string>("");
const [selectedMenu, setSelectedMenu] = useState<MenuItem | null>(null);
const [assignedScreens, setAssignedScreens] = useState<ScreenDefinition[]>([]);
const [availableScreens, setAvailableScreens] = useState<ScreenDefinition[]>([]);
const [loading, setLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const [showAssignDialog, setShowAssignDialog] = useState(false);
const [showUnassignDialog, setShowUnassignDialog] = useState(false);
const [selectedScreen, setSelectedScreen] = useState<ScreenDefinition | null>(null);
// 디버그: 전달받은 메뉴 데이터 확인
// console.log("ScreenAssignmentTab - 전달받은 메뉴 데이터:", {
// total: menus.length,
// sample: menus.slice(0, 3),
// keys: menus.length > 0 ? Object.keys(menus[0]) : [],
// });
2025-09-01 18:42:59 +09:00
// 메뉴 선택
const handleMenuSelect = async (menuId: string) => {
// console.log("메뉴 선택:", menuId);
2025-09-01 18:42:59 +09:00
setSelectedMenuId(menuId);
// 다양한 형식의 objid 대응
const menu = menus.find((m) => {
const objid = m.objid || m.OBJID || (m as any).objid;
return objid?.toString() === menuId;
});
// console.log("선택된 메뉴:", menu);
2025-09-01 18:42:59 +09:00
setSelectedMenu(menu || null);
if (menu) {
const objid = menu.objid || menu.OBJID || (menu as any).objid;
if (objid) {
await loadAssignedScreens(parseInt(objid.toString()));
}
}
};
// 할당된 화면 목록 로드
const loadAssignedScreens = async (menuObjid: number) => {
try {
setLoading(true);
const screens = await menuScreenApi.getScreensByMenu(menuObjid);
setAssignedScreens(screens);
} catch (error) {
// console.error("할당된 화면 로드 실패:", error);
2025-09-01 18:42:59 +09:00
toast.error("할당된 화면 목록을 불러오는데 실패했습니다.");
} finally {
setLoading(false);
}
};
// 할당 가능한 화면 목록 로드
const loadAvailableScreens = async () => {
try {
const response = await screenApi.getScreens({});
const allScreens = response.data;
// 이미 할당된 화면 제외
const assignedScreenIds = assignedScreens.map((screen) => screen.screenId);
const available = allScreens.filter((screen) => !assignedScreenIds.includes(screen.screenId));
setAvailableScreens(available);
} catch (error) {
// console.error("사용 가능한 화면 로드 실패:", error);
2025-09-01 18:42:59 +09:00
toast.error("사용 가능한 화면 목록을 불러오는데 실패했습니다.");
}
};
// 화면 할당
const handleAssignScreen = async () => {
if (!selectedScreen || !selectedMenu) return;
try {
const objid = selectedMenu.objid || selectedMenu.OBJID || (selectedMenu as any).objid;
if (!objid) {
toast.error("메뉴 ID를 찾을 수 없습니다.");
return;
}
await menuScreenApi.assignScreenToMenu(selectedScreen.screenId, parseInt(objid.toString()));
toast.success("화면이 메뉴에 성공적으로 할당되었습니다.");
// 목록 새로고침
await loadAssignedScreens(parseInt(objid.toString()));
setShowAssignDialog(false);
setSelectedScreen(null);
} catch (error) {
// console.error("화면 할당 실패:", error);
2025-09-01 18:42:59 +09:00
toast.error("화면 할당에 실패했습니다.");
}
};
// 화면 할당 해제
const handleUnassignScreen = async () => {
if (!selectedScreen || !selectedMenu) return;
try {
const objid = selectedMenu.objid || selectedMenu.OBJID || (selectedMenu as any).objid;
if (!objid) {
toast.error("메뉴 ID를 찾을 수 없습니다.");
return;
}
await menuScreenApi.unassignScreenFromMenu(selectedScreen.screenId, parseInt(objid.toString()));
toast.success("화면-메뉴 할당이 해제되었습니다.");
// 목록 새로고침
await loadAssignedScreens(parseInt(objid.toString()));
setShowUnassignDialog(false);
setSelectedScreen(null);
} catch (error) {
// console.error("화면 할당 해제 실패:", error);
2025-09-01 18:42:59 +09:00
toast.error("화면 할당 해제에 실패했습니다.");
}
};
// 화면 할당 대화상자 열기
const openAssignDialog = async () => {
await loadAvailableScreens();
setShowAssignDialog(true);
};
// 필터된 사용 가능한 화면
const filteredAvailableScreens = availableScreens.filter(
(screen) =>
screen.screenName.toLowerCase().includes(searchTerm.toLowerCase()) ||
screen.screenCode.toLowerCase().includes(searchTerm.toLowerCase()),
);
// 단순화된 메뉴 옵션 생성 (모든 메뉴를 평면적으로 표시)
const getMenuOptions = (menuList: MenuItem[]): JSX.Element[] => {
// console.log("메뉴 옵션 생성:", {
// total: menuList.length,
// sample: menuList.slice(0, 3).map((m) => ({
// objid: m.objid || m.OBJID || (m as any).objid,
// name: m.menu_name_kor || m.MENU_NAME_KOR || (m as any).menu_name_kor,
// parent: m.parent_obj_id || m.PARENT_OBJ_ID || (m as any).parent_obj_id,
// })),
// });
2025-09-01 18:42:59 +09:00
if (!menuList || menuList.length === 0) {
return [
<SelectItem key="no-menu" value="" disabled>
</SelectItem>,
];
}
return menuList.map((menu, index) => {
// 현재 메뉴의 ID와 이름 추출 (다양한 형식 대응)
const menuObjid = menu.objid || menu.OBJID || (menu as any).objid;
const menuName = menu.menu_name_kor || menu.MENU_NAME_KOR || (menu as any).menu_name_kor;
const lev = menu.lev || menu.LEV || (menu as any).lev || 0;
// 들여쓰기 (레벨에 따라)
const indent = " ".repeat(Math.max(0, lev));
// console.log("메뉴 항목:", { index, menuObjid, menuName, lev });
2025-09-01 18:42:59 +09:00
return (
<SelectItem key={menuObjid?.toString() || `menu-${index}`} value={menuObjid?.toString() || `menu-${index}`}>
{indent}
{menuName || `메뉴 ${index + 1}`}
</SelectItem>
);
});
};
return (
<div className="space-y-6">
{/* 메뉴 선택 */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<Label htmlFor="menu-select"> </Label>
<Select value={selectedMenuId} onValueChange={handleMenuSelect}>
<SelectTrigger>
<SelectValue placeholder="화면을 할당할 메뉴를 선택하세요" />
</SelectTrigger>
<SelectContent>{getMenuOptions(menus)}</SelectContent>
</Select>
</div>
{selectedMenu && (
<div className="rounded-lg border bg-gray-50 p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-medium">
{selectedMenu.menu_name_kor ||
selectedMenu.MENU_NAME_KOR ||
(selectedMenu as any).menu_name_kor ||
"메뉴"}
</h3>
<p className="text-sm text-muted-foreground">
2025-09-01 18:42:59 +09:00
URL: {selectedMenu.menu_url || selectedMenu.MENU_URL || (selectedMenu as any).menu_url || "없음"}
</p>
<p className="text-sm text-muted-foreground">
2025-09-01 18:42:59 +09:00
:{" "}
{selectedMenu.menu_desc || selectedMenu.MENU_DESC || (selectedMenu as any).menu_desc || "없음"}
</p>
</div>
<Badge
variant={
(selectedMenu.status || selectedMenu.STATUS || (selectedMenu as any).status) === "active"
? "default"
: "secondary"
}
>
{(selectedMenu.status || selectedMenu.STATUS || (selectedMenu as any).status) === "active"
? "활성"
: "비활성"}
</Badge>
</div>
</div>
)}
</div>
</CardContent>
</Card>
{/* 할당된 화면 목록 */}
{selectedMenu && (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Monitor className="h-5 w-5" />
({assignedScreens.length})
</CardTitle>
<Button onClick={openAssignDialog} className="bg-blue-600 hover:bg-blue-700">
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent>
{loading ? (
<div className="py-8 text-center text-gray-500"> ...</div>
) : assignedScreens.length === 0 ? (
<div className="py-8 text-center text-gray-500"> . .</div>
) : (
<div className="space-y-3">
{assignedScreens.map((screen) => (
<div
key={screen.screenId}
className="flex items-center justify-between rounded-lg border p-4 hover:bg-gray-50"
>
<div className="flex-1">
<div className="flex items-center gap-3">
<h4 className="font-medium">{screen.screenName}</h4>
<Badge variant="outline" className="font-mono">
{screen.screenCode}
</Badge>
<Badge variant={screen.isActive === "Y" ? "default" : "secondary"}>
{screen.isActive === "Y" ? "활성" : "비활성"}
</Badge>
</div>
<p className="mt-1 text-sm text-muted-foreground">
2025-09-01 18:42:59 +09:00
: {screen.tableName} | : {screen.createdDate.toLocaleDateString()}
</p>
{screen.description && <p className="mt-1 text-sm text-gray-500">{screen.description}</p>}
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
setSelectedScreen(screen);
setShowUnassignDialog(true);
}}
className="text-destructive hover:text-red-700"
2025-09-01 18:42:59 +09:00
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
)}
</CardContent>
</Card>
)}
{/* 화면 할당 대화상자 */}
<AlertDialog open={showAssignDialog} onOpenChange={setShowAssignDialog}>
<AlertDialogContent className="max-w-2xl">
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>{selectedMenu?.menu_name_kor} .</AlertDialogDescription>
</AlertDialogHeader>
<div className="space-y-4">
{/* 검색 */}
<div className="relative">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
<Input
placeholder="화면명 또는 코드로 검색..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
{/* 화면 목록 */}
<div className="max-h-64 space-y-2 overflow-y-auto">
{filteredAvailableScreens.length === 0 ? (
<div className="py-4 text-center text-gray-500"> .</div>
) : (
filteredAvailableScreens.map((screen) => (
<div
key={screen.screenId}
className={`cursor-pointer rounded-lg border p-3 transition-colors ${
selectedScreen?.screenId === screen.screenId ? "border-primary bg-accent" : "hover:bg-gray-50"
2025-09-01 18:42:59 +09:00
}`}
onClick={() => setSelectedScreen(screen)}
>
<div className="flex items-center gap-3">
<h4 className="font-medium">{screen.screenName}</h4>
<Badge variant="outline" className="font-mono text-xs">
{screen.screenCode}
</Badge>
</div>
<p className="mt-1 text-sm text-muted-foreground">: {screen.tableName}</p>
2025-09-01 18:42:59 +09:00
</div>
))
)}
</div>
</div>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setSelectedScreen(null)}></AlertDialogCancel>
<AlertDialogAction onClick={handleAssignScreen} disabled={!selectedScreen}>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 화면 할당 해제 대화상자 */}
<AlertDialog open={showUnassignDialog} onOpenChange={setShowUnassignDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
"{selectedScreen?.screenName}" ?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setSelectedScreen(null)}></AlertDialogCancel>
<AlertDialogAction onClick={handleUnassignScreen} className="bg-red-600 hover:bg-red-700">
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};