168 lines
6.2 KiB
TypeScript
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>
|
|
);
|
|
}
|