ERP-node/frontend/components/screen/ScreenGroupTreeView.tsx

233 lines
8.1 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect } from "react";
import { cn } from "@/lib/utils";
import { ChevronRight, ChevronDown, Monitor, FolderOpen, Folder } from "lucide-react";
import { ScreenDefinition } from "@/types/screen";
import { ScreenGroup, getScreenGroups } from "@/lib/api/screenGroup";
import { Badge } from "@/components/ui/badge";
interface ScreenGroupTreeViewProps {
screens: ScreenDefinition[];
selectedScreen: ScreenDefinition | null;
onScreenSelect: (screen: ScreenDefinition) => void;
onScreenDesign: (screen: ScreenDefinition) => void;
companyCode?: string;
}
interface TreeNode {
type: "group" | "screen";
id: string;
name: string;
data?: ScreenDefinition | ScreenGroup;
children?: TreeNode[];
expanded?: boolean;
}
export function ScreenGroupTreeView({
screens,
selectedScreen,
onScreenSelect,
onScreenDesign,
companyCode,
}: ScreenGroupTreeViewProps) {
const [groups, setGroups] = useState<ScreenGroup[]>([]);
const [loading, setLoading] = useState(true);
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
const [groupScreensMap, setGroupScreensMap] = useState<Map<number, number[]>>(new Map());
// 그룹 목록 로드
useEffect(() => {
const loadGroups = async () => {
try {
setLoading(true);
const response = await getScreenGroups({});
if (response.success && response.data) {
setGroups(response.data);
}
} catch (error) {
console.error("그룹 목록 로드 실패:", error);
} finally {
setLoading(false);
}
};
loadGroups();
}, [companyCode]);
// 그룹에 속한 화면 ID들을 가져오기
const getGroupedScreenIds = (): Set<number> => {
const ids = new Set<number>();
groupScreensMap.forEach((screenIds) => {
screenIds.forEach((id) => ids.add(id));
});
return ids;
};
// 미분류 화면들 (어떤 그룹에도 속하지 않은 화면)
const getUngroupedScreens = (): ScreenDefinition[] => {
const groupedIds = getGroupedScreenIds();
return screens.filter((screen) => !groupedIds.has(screen.screenId));
};
// 그룹에 속한 화면들
const getScreensInGroup = (groupId: number): ScreenDefinition[] => {
const screenIds = groupScreensMap.get(groupId) || [];
return screens.filter((screen) => screenIds.includes(screen.screenId));
};
const toggleGroup = (groupId: string) => {
const newExpanded = new Set(expandedGroups);
if (newExpanded.has(groupId)) {
newExpanded.delete(groupId);
} else {
newExpanded.add(groupId);
}
setExpandedGroups(newExpanded);
};
const handleScreenClick = (screen: ScreenDefinition) => {
onScreenSelect(screen);
};
const handleScreenDoubleClick = (screen: ScreenDefinition) => {
onScreenDesign(screen);
};
if (loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="text-sm text-muted-foreground"> ...</div>
</div>
);
}
const ungroupedScreens = getUngroupedScreens();
return (
<div className="h-full overflow-auto">
<div className="p-2">
{/* 그룹화된 화면들 */}
{groups.map((group) => {
const groupId = String(group.id);
const isExpanded = expandedGroups.has(groupId);
const groupScreens = getScreensInGroup(group.id);
return (
<div key={groupId} className="mb-1">
{/* 그룹 헤더 */}
<div
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer hover:bg-accent transition-colors",
"text-sm font-medium"
)}
onClick={() => toggleGroup(groupId)}
>
{isExpanded ? (
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
) : (
<ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
{isExpanded ? (
<FolderOpen className="h-4 w-4 shrink-0 text-amber-500" />
) : (
<Folder className="h-4 w-4 shrink-0 text-amber-500" />
)}
<span className="truncate flex-1">{group.groupName}</span>
<Badge variant="secondary" className="text-xs">
{groupScreens.length}
</Badge>
</div>
{/* 그룹 내 화면들 */}
{isExpanded && (
<div className="ml-4 mt-1 space-y-0.5">
{groupScreens.length === 0 ? (
<div className="pl-6 py-2 text-xs text-muted-foreground">
</div>
) : (
groupScreens.map((screen) => (
<div
key={screen.screenId}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer transition-colors",
"text-sm hover:bg-accent",
selectedScreen?.screenId === screen.screenId && "bg-accent"
)}
onClick={() => handleScreenClick(screen)}
onDoubleClick={() => handleScreenDoubleClick(screen)}
>
<Monitor className="h-4 w-4 shrink-0 text-blue-500" />
<span className="truncate flex-1">{screen.screenName}</span>
<span className="text-xs text-muted-foreground truncate max-w-[100px]">
{screen.screenCode}
</span>
</div>
))
)}
</div>
)}
</div>
);
})}
{/* 미분류 화면들 */}
{ungroupedScreens.length > 0 && (
<div className="mb-1">
<div
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer hover:bg-accent transition-colors",
"text-sm font-medium text-muted-foreground"
)}
onClick={() => toggleGroup("ungrouped")}
>
{expandedGroups.has("ungrouped") ? (
<ChevronDown className="h-4 w-4 shrink-0" />
) : (
<ChevronRight className="h-4 w-4 shrink-0" />
)}
<Folder className="h-4 w-4 shrink-0" />
<span className="truncate flex-1"></span>
<Badge variant="outline" className="text-xs">
{ungroupedScreens.length}
</Badge>
</div>
{expandedGroups.has("ungrouped") && (
<div className="ml-4 mt-1 space-y-0.5">
{ungroupedScreens.map((screen) => (
<div
key={screen.screenId}
className={cn(
"flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer transition-colors",
"text-sm hover:bg-accent",
selectedScreen?.screenId === screen.screenId && "bg-accent"
)}
onClick={() => handleScreenClick(screen)}
onDoubleClick={() => handleScreenDoubleClick(screen)}
>
<Monitor className="h-4 w-4 shrink-0 text-blue-500" />
<span className="truncate flex-1">{screen.screenName}</span>
<span className="text-xs text-muted-foreground truncate max-w-[100px]">
{screen.screenCode}
</span>
</div>
))}
</div>
)}
</div>
)}
{groups.length === 0 && ungroupedScreens.length === 0 && (
<div className="flex flex-col items-center justify-center py-8 text-center">
<Monitor className="h-12 w-12 text-muted-foreground/50 mb-2" />
<p className="text-sm text-muted-foreground"> </p>
</div>
)}
</div>
</div>
);
}