From 5b6313f16f508882a0ea67716b7dbaa1c6967f04 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 30 Jun 2025 08:28:13 +0000 Subject: (대표님) 20250630 16시 - 유저 도메인별 라우터 분리와 보안성검토 대응 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/menu-list/table/menu-list-table.tsx | 280 ++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 lib/menu-list/table/menu-list-table.tsx (limited to 'lib/menu-list/table/menu-list-table.tsx') diff --git a/lib/menu-list/table/menu-list-table.tsx b/lib/menu-list/table/menu-list-table.tsx new file mode 100644 index 00000000..097be082 --- /dev/null +++ b/lib/menu-list/table/menu-list-table.tsx @@ -0,0 +1,280 @@ +// app/evcp/menu-list/components/menu-list-table.tsx + +"use client"; + +import { useState, useMemo } from "react"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { Switch } from "@/components/ui/switch"; +import { Button } from "@/components/ui/button"; +import { Search, Filter, ExternalLink } from "lucide-react"; +import { toast } from "sonner"; +import { ManagerSelect } from "./manager-select"; +import { toggleMenuActive } from "../servcie"; + +interface MenuAssignment { + id: number; + menuPath: string; + menuTitle: string; + menuDescription?: string | null; + menuGroup?: string | null; + sectionTitle: string; + domain: string; + isActive: boolean; + createdAt: Date; + updatedAt: Date; + manager1Id?: number | null; + manager2Id?: number | null; + manager1Name?: string | null; + manager1Email?: string | null; + manager2Name?: string | null; + manager2Email?: string | null; +} + +interface User { + id: number; + name: string; + email: string; + domain: string; +} + +interface MenuListTableProps { + initialMenus: MenuAssignment[]; + initialUsers: User[]; +} + +export function MenuListTable({ initialMenus, initialUsers }: MenuListTableProps) { + const [searchQuery, setSearchQuery] = useState(""); + const [domainFilter, setDomainFilter] = useState("all"); + const [sectionFilter, setSectionFilter] = useState("all"); + const [statusFilter, setStatusFilter] = useState("all"); + + // 필터링된 메뉴 데이터 + const filteredMenus = useMemo(() => { + return initialMenus.filter((menu) => { + const matchesSearch = + menu.menuTitle.toLowerCase().includes(searchQuery.toLowerCase()) || + menu.menuPath.toLowerCase().includes(searchQuery.toLowerCase()) || + menu.sectionTitle.toLowerCase().includes(searchQuery.toLowerCase()) || + (menu.menuDescription?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false); + + const matchesDomain = domainFilter === "all" || menu.domain === domainFilter; + const matchesSection = sectionFilter === "all" || menu.sectionTitle === sectionFilter; + const matchesStatus = statusFilter === "all" || + (statusFilter === "active" && menu.isActive) || + (statusFilter === "inactive" && !menu.isActive); + + return matchesSearch && matchesDomain && matchesSection && matchesStatus; + }); + }, [initialMenus, searchQuery, domainFilter, sectionFilter, statusFilter]); + + // 섹션 리스트 추출 + const sections = useMemo(() => { + const sectionSet = new Set(initialMenus.map(menu => menu.sectionTitle)); + return Array.from(sectionSet).sort(); + }, [initialMenus]); + + // 도메인별 사용자 필터링 + const getFilteredUsers = (domain: string) => { + return initialUsers.filter(user => user.domain === domain); + }; + + // 메뉴 활성화/비활성화 토글 + const handleToggleActive = async (menuPath: string, isActive: boolean) => { + try { + const result = await toggleMenuActive(menuPath, isActive); + if (result.success) { + toast.success(result.message); + } else { + toast.error(result.message); + } + } catch (error) { + toast.error("메뉴 상태 변경 중 오류가 발생했습니다."); + } + }; + + return ( +
+ {/* 필터 영역 */} +
+
+
+ + setSearchQuery(e.target.value)} + className="pl-8" + /> +
+
+ +
+ + + + + +
+
+ + {/* 결과 요약 */} +
+ + 총 {filteredMenus.length}개의 메뉴 + {searchQuery && ` (${initialMenus.length}개 중 검색 결과)`} + +
+ + {/* 테이블 */} +
+ + + + 상태 + 메뉴 정보 + 도메인 + 담당자 1 + 담당자 2 + {/* 동작 */} + + + + {filteredMenus.length === 0 ? ( + + + 조건에 맞는 메뉴가 없습니다. + + + ) : ( + filteredMenus.map((menu) => { + const domainUsers = getFilteredUsers(menu.domain); + + return ( + + + handleToggleActive(menu.menuPath, checked)} + /> + + + +
+
+ {menu.menuTitle} + + {menu.sectionTitle} + + {menu.menuGroup && ( + + {menu.menuGroup} + + )} +
+
+ {menu.menuPath} +
+ {menu.menuDescription && ( +
+ {menu.menuDescription} +
+ )} +
+
+ + + + {menu.domain.toUpperCase()} + + + + + + + + + + + + {/* + + */} +
+ ); + }) + )} +
+
+
+
+ ); +} \ No newline at end of file -- cgit v1.2.3