summaryrefslogtreecommitdiff
path: root/components/signup/tech-vendor-item-selector-dialog.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-21 07:54:26 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-21 07:54:26 +0000
commit14f61e24947fb92dd71ec0a7196a6e815f8e66da (patch)
tree317c501d64662d05914330628f867467fba78132 /components/signup/tech-vendor-item-selector-dialog.tsx
parent194bd4bd7e6144d5c09c5e3f5476d254234dce72 (diff)
(최겸)기술영업 RFQ 담당자 초대, 요구사항 반영
Diffstat (limited to 'components/signup/tech-vendor-item-selector-dialog.tsx')
-rw-r--r--components/signup/tech-vendor-item-selector-dialog.tsx254
1 files changed, 254 insertions, 0 deletions
diff --git a/components/signup/tech-vendor-item-selector-dialog.tsx b/components/signup/tech-vendor-item-selector-dialog.tsx
new file mode 100644
index 00000000..a69dec5d
--- /dev/null
+++ b/components/signup/tech-vendor-item-selector-dialog.tsx
@@ -0,0 +1,254 @@
+"use client"
+
+import * as React from "react"
+import { useState, useEffect } from "react"
+import { Search, X } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Input } from "@/components/ui/input"
+import { Badge } from "@/components/ui/badge"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { Checkbox } from "@/components/ui/checkbox"
+
+interface Item {
+ itemCode: string
+ itemList: string
+ subItemList?: string
+ workType?: string
+ shipTypes?: string
+}
+
+interface TechVendorItemSelectorDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ vendorType: string | string[]
+ onItemsSelected: (selectedItems: string[]) => void
+}
+
+export function TechVendorItemSelectorDialog({
+ open,
+ onOpenChange,
+ vendorType,
+ onItemsSelected,
+}: TechVendorItemSelectorDialogProps) {
+ const [items, setItems] = useState<Item[]>([])
+ const [filteredItems, setFilteredItems] = useState<Item[]>([])
+ const [searchTerm, setSearchTerm] = useState("")
+ const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set())
+ const [isLoading, setIsLoading] = useState(false)
+
+ // 벤더 타입에 따른 아이템 조회
+ useEffect(() => {
+ if (open && vendorType) {
+ loadItemsByVendorType()
+ }
+ }, [open, vendorType])
+
+ // 검색 필터링
+ useEffect(() => {
+ if (searchTerm.trim() === "") {
+ setFilteredItems(items)
+ } else {
+ const filtered = items.filter(
+ (item) =>
+ item.itemList.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (item.subItemList && item.subItemList.toLowerCase().includes(searchTerm.toLowerCase())) ||
+ item.itemCode.toLowerCase().includes(searchTerm.toLowerCase())
+ )
+ setFilteredItems(filtered)
+ }
+ }, [searchTerm, items])
+
+ const loadItemsByVendorType = async () => {
+ setIsLoading(true)
+ try {
+ // 서버 액션으로 아이템 조회
+ const { getItemsByVendorType } = await import("@/lib/tech-vendors/service")
+
+ let allItems: any[] = []
+
+ // 여러 벤더 타입인 경우 각각 조회하여 합치기
+ if (Array.isArray(vendorType)) {
+ for (const type of vendorType) {
+ const result = await getItemsByVendorType(type, "")
+ if (result && result.data && result.data.length > 0) {
+ allItems = [...allItems, ...result.data]
+ }
+ }
+ } else {
+ // 단일 벤더 타입인 경우
+ const result = await getItemsByVendorType(vendorType, "")
+ if (result && result.data && result.data.length > 0) {
+ allItems = result.data
+ }
+ }
+
+ if (allItems.length > 0) {
+ // 중복 제거 (itemCode 기준)
+ const uniqueItems = allItems.filter((item, index, self) =>
+ index === self.findIndex(t => t.itemCode === item.itemCode)
+ )
+
+ const itemsData = uniqueItems.map((item: any) => ({
+ itemCode: item.itemCode || "",
+ itemList: item.itemList || "",
+ subItemList: item.subItemList || "",
+ workType: item.workType || "",
+ shipTypes: item.shipTypes || "",
+ }))
+ setItems(itemsData)
+ setFilteredItems(itemsData)
+ } else {
+ setItems([])
+ setFilteredItems([])
+ }
+ } catch (error) {
+ console.error("아이템 조회 실패:", error)
+ setItems([])
+ setFilteredItems([])
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const handleItemToggle = (itemCode: string) => {
+ const newSelected = new Set(selectedItems)
+ if (newSelected.has(itemCode)) {
+ newSelected.delete(itemCode)
+ } else {
+ newSelected.add(itemCode)
+ }
+ setSelectedItems(newSelected)
+ }
+
+ const handleConfirm = () => {
+ const selectedItemCodes = Array.from(selectedItems)
+ onItemsSelected(selectedItemCodes)
+ onOpenChange(false)
+ // 상태 초기화
+ setSelectedItems(new Set())
+ setSearchTerm("")
+ }
+
+ const handleCancel = () => {
+ onOpenChange(false)
+ // 상태 초기화
+ setSelectedItems(new Set())
+ setSearchTerm("")
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-4xl max-h-[80vh] flex flex-col">
+ <DialogHeader>
+ <DialogTitle>공급가능품목 선택</DialogTitle>
+ <DialogDescription>
+ {Array.isArray(vendorType) ? vendorType.join(", ") : vendorType} 관련 아이템 중에서 공급 가능한 품목을 선택해주세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="flex-1 space-y-4">
+ {/* 검색바 */}
+ <div className="relative">
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
+ <Input
+ placeholder="아이템명, 서브아이템명, 아이템코드로 검색..."
+ value={searchTerm}
+ onChange={(e) => setSearchTerm(e.target.value)}
+ className="pl-10"
+ />
+ </div>
+
+ {/* 선택된 아이템 표시 */}
+ {selectedItems.size > 0 && (
+ <div className="space-y-2">
+ <div className="text-sm font-medium">선택된 아이템 ({selectedItems.size}개)</div>
+ <div className="flex flex-wrap gap-2">
+ {Array.from(selectedItems).map((itemCode) => {
+ const item = items.find((i) => i.itemCode === itemCode)
+ return (
+ <Badge key={itemCode} variant="secondary" className="gap-1">
+ {item?.itemList || itemCode}
+ <button
+ onClick={() => handleItemToggle(itemCode)}
+ className="ml-1 hover:bg-muted rounded-full p-0.5"
+ >
+ <X className="h-3 w-3" />
+ </button>
+ </Badge>
+ )
+ })}
+ </div>
+ </div>
+ )}
+
+ {/* 아이템 목록 */}
+ <div className="border rounded-md">
+ <ScrollArea className="h-96">
+ {isLoading ? (
+ <div className="p-4 text-center text-muted-foreground">로딩 중...</div>
+ ) : filteredItems.length === 0 ? (
+ <div className="p-4 text-center text-muted-foreground">
+ {searchTerm ? "검색 결과가 없습니다." : "아이템이 없습니다."}
+ </div>
+ ) : (
+ <div className="p-4 space-y-2">
+ {filteredItems.map((item) => (
+ <div
+ key={item.itemCode}
+ className="flex items-start space-x-3 p-3 border rounded-lg hover:bg-muted/50"
+ >
+ <Checkbox
+ checked={selectedItems.has(item.itemCode)}
+ onCheckedChange={() => handleItemToggle(item.itemCode)}
+ className="mt-1"
+ />
+ <div className="flex-1 space-y-1">
+ <div className="flex items-center space-x-2">
+ <span className="font-medium">{item.itemList}</span>
+ <Badge variant="outline" className="text-xs">
+ {item.itemCode}
+ </Badge>
+ </div>
+ {item.subItemList && (
+ <div className="text-sm text-muted-foreground">
+ {item.subItemList}
+ </div>
+ )}
+ <div className="flex space-x-2 text-xs text-muted-foreground">
+ {item.workType && (
+ <span>공종: {item.workType}</span>
+ )}
+ {item.shipTypes && (
+ <span>선종: {item.shipTypes}</span>
+ )}
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+ </ScrollArea>
+ </div>
+ </div>
+
+ <DialogFooter>
+ <Button variant="outline" onClick={handleCancel}>
+ 취소
+ </Button>
+ <Button onClick={handleConfirm} disabled={selectedItems.size === 0}>
+ 선택 완료 ({selectedItems.size}개)
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+} \ No newline at end of file