ERP-node/frontend/lib/registry/components/v2-process-work-standard/components/ItemProcessSelector.tsx

168 lines
6.2 KiB
TypeScript

"use client";
import React, { useEffect, useState } from "react";
import { Search, ChevronDown, ChevronRight, Package, GitBranch, Settings2, Star } from "lucide-react";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { ItemData, RoutingVersion, SelectionState } from "../types";
interface ItemProcessSelectorProps {
title: string;
items: ItemData[];
routings: RoutingVersion[];
selection: SelectionState;
onSearch: (keyword: string) => void;
onSelectItem: (itemCode: string, itemName: string) => void;
onSelectProcess: (
routingDetailId: string,
processName: string,
routingVersionId: string,
routingVersionName: string
) => void;
onInit: () => void;
}
export function ItemProcessSelector({
title,
items,
routings,
selection,
onSearch,
onSelectItem,
onSelectProcess,
onInit,
}: ItemProcessSelectorProps) {
const [searchKeyword, setSearchKeyword] = useState("");
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
useEffect(() => {
onInit();
}, [onInit]);
const handleSearch = (value: string) => {
setSearchKeyword(value);
onSearch(value);
};
const toggleItem = (itemCode: string, itemName: string) => {
const next = new Set(expandedItems);
if (next.has(itemCode)) {
next.delete(itemCode);
} else {
next.add(itemCode);
onSelectItem(itemCode, itemName);
}
setExpandedItems(next);
};
const isItemExpanded = (itemCode: string) => expandedItems.has(itemCode);
return (
<div className="flex h-full flex-col border-r bg-background">
{/* 헤더 */}
<div className="border-b p-3">
<div className="mb-2 flex items-center gap-2">
<Package className="h-4 w-4 text-primary" />
<span className="text-sm font-semibold">{title}</span>
</div>
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-3.5 w-3.5 text-muted-foreground" />
<Input
placeholder="품목 또는 공정 검색"
value={searchKeyword}
onChange={(e) => handleSearch(e.target.value)}
className="h-8 pl-8 text-xs"
/>
</div>
</div>
{/* 트리 목록 */}
<div className="flex-1 overflow-y-auto p-2">
{items.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-center">
<Package className="mb-2 h-8 w-8 text-muted-foreground/50" />
<p className="text-xs text-muted-foreground">
</p>
</div>
) : (
items.map((item) => (
<div key={item.item_code} className="mb-1">
{/* 품목 헤더 */}
<button
onClick={() => toggleItem(item.item_code, item.item_name)}
className={cn(
"flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-xs transition-colors",
"hover:bg-accent",
selection.itemCode === item.item_code && "bg-accent"
)}
>
{isItemExpanded(item.item_code) ? (
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
) : (
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
)}
<Package className="h-3.5 w-3.5 shrink-0 text-primary" />
<span className="truncate font-medium">
{item.item_name} ({item.item_code})
</span>
</button>
{/* 라우팅 + 공정 */}
{isItemExpanded(item.item_code) &&
selection.itemCode === item.item_code && (
<div className="ml-4 mt-0.5 border-l pl-2">
{routings.length === 0 ? (
<p className="py-2 text-[10px] text-muted-foreground">
</p>
) : (
routings.map((routing) => (
<div key={routing.id} className="mb-1">
{/* 라우팅 버전 */}
<div className="flex items-center gap-1.5 px-1 py-1">
<Star className="h-3 w-3 text-amber-500" />
<span className="text-[11px] font-medium text-muted-foreground">
{routing.version_name || "기본 라우팅"}
</span>
</div>
{/* 공정 목록 */}
{routing.processes.map((proc) => (
<button
key={proc.routing_detail_id}
onClick={() =>
onSelectProcess(
proc.routing_detail_id,
proc.process_name,
routing.id,
routing.version_name || "기본 라우팅"
)
}
className={cn(
"ml-3 flex w-[calc(100%-12px)] items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-xs transition-colors",
"hover:bg-primary/10",
selection.routingDetailId ===
proc.routing_detail_id &&
"bg-primary/15 font-semibold text-primary"
)}
>
<Settings2 className="h-3 w-3 shrink-0" />
<span className="truncate">
{proc.process_name || proc.process_code}
</span>
</button>
))}
</div>
))
)}
</div>
)}
</div>
))
)}
</div>
</div>
);
}