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

397 lines
15 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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]) : [],
// });
// 메뉴 선택
const handleMenuSelect = async (menuId: string) => {
// console.log("메뉴 선택:", menuId);
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);
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);
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);
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);
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);
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,
// })),
// });
if (!menuList || menuList.length === 0) {
// Radix UI Select v2.x: 빈 문자열 value="" 금지 → "__placeholder__" 사용
return [
<SelectItem key="no-menu" value="__placeholder__" 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 });
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">
URL: {selectedMenu.menu_url || selectedMenu.MENU_URL || (selectedMenu as any).menu_url || "없음"}
</p>
<p className="text-sm text-muted-foreground">
:{" "}
{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">
: {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"
>
<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"
}`}
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>
</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>
);
};