233 lines
8.1 KiB
TypeScript
233 lines
8.1 KiB
TypeScript
"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>
|
|
);
|
|
}
|
|
|