summaryrefslogtreecommitdiff
path: root/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-regular-registrations/table/major-items-update-dialog.tsx')
-rw-r--r--lib/vendor-regular-registrations/table/major-items-update-dialog.tsx245
1 files changed, 245 insertions, 0 deletions
diff --git a/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx b/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx
new file mode 100644
index 00000000..26741a1b
--- /dev/null
+++ b/lib/vendor-regular-registrations/table/major-items-update-dialog.tsx
@@ -0,0 +1,245 @@
+"use client"
+
+import * as React from "react"
+import { useState } from "react"
+import { toast } from "sonner"
+import { X, Plus, Search } from "lucide-react"
+
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Badge } from "@/components/ui/badge"
+import { Label } from "@/components/ui/label"
+import { searchItemsForPQ } from "@/lib/items/service"
+import { updateMajorItems } from "../service"
+
+// PQ 대상 품목 타입 정의
+interface PQItem {
+ itemCode: string
+ itemName: string
+}
+
+interface MajorItemsUpdateDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ registrationId?: number
+ vendorName?: string
+ currentItems?: string | null
+ onSuccess?: () => void
+}
+
+export function MajorItemsUpdateDialog({
+ open,
+ onOpenChange,
+ registrationId,
+ vendorName,
+ currentItems,
+ onSuccess,
+}: MajorItemsUpdateDialogProps) {
+ const [isLoading, setIsLoading] = useState(false)
+ const [selectedItems, setSelectedItems] = useState<PQItem[]>([])
+
+ // 아이템 검색 관련 상태
+ const [itemSearchQuery, setItemSearchQuery] = useState<string>("")
+ const [filteredItems, setFilteredItems] = useState<PQItem[]>([])
+ const [showItemDropdown, setShowItemDropdown] = useState<boolean>(false)
+
+ // 기존 아이템들 파싱 및 초기화
+ React.useEffect(() => {
+ if (open && currentItems) {
+ try {
+ const parsedItems = JSON.parse(currentItems)
+ if (Array.isArray(parsedItems)) {
+ setSelectedItems(parsedItems)
+ }
+ } catch (error) {
+ console.error("기존 주요품목 파싱 오류:", error)
+ setSelectedItems([])
+ }
+ } else if (open) {
+ setSelectedItems([])
+ }
+ }, [open, currentItems])
+
+ // 아이템 검색 필터링
+ React.useEffect(() => {
+ if (itemSearchQuery.trim() === "") {
+ setFilteredItems([])
+ setShowItemDropdown(false)
+ return
+ }
+
+ const searchItems = async () => {
+ try {
+ const results = await searchItemsForPQ(itemSearchQuery)
+ setFilteredItems(results)
+ setShowItemDropdown(true)
+ } catch (error) {
+ console.error("아이템 검색 오류:", error)
+ toast.error("아이템 검색 중 오류가 발생했습니다.")
+ setFilteredItems([])
+ setShowItemDropdown(false)
+ }
+ }
+
+ // 디바운싱: 300ms 후에 검색 실행
+ const timeoutId = setTimeout(searchItems, 300)
+ return () => clearTimeout(timeoutId)
+ }, [itemSearchQuery])
+
+ // 아이템 선택 함수
+ const handleSelectItem = (item: PQItem) => {
+ // 이미 선택된 아이템인지 확인
+ const isAlreadySelected = selectedItems.some(selectedItem =>
+ selectedItem.itemCode === item.itemCode
+ )
+
+ if (!isAlreadySelected) {
+ setSelectedItems(prev => [...prev, item])
+ }
+
+ // 검색 초기화
+ setItemSearchQuery("")
+ setFilteredItems([])
+ setShowItemDropdown(false)
+ }
+
+ // 아이템 제거 함수
+ const handleRemoveItem = (itemCode: string) => {
+ setSelectedItems(prev => prev.filter(item => item.itemCode !== itemCode))
+ }
+
+ const handleSave = async () => {
+ if (!registrationId) {
+ toast.error("등록 ID가 없습니다.")
+ return
+ }
+
+ setIsLoading(true)
+ try {
+ const result = await updateMajorItems(
+ registrationId,
+ JSON.stringify(selectedItems)
+ )
+
+ if (result.success) {
+ toast.success("주요품목이 업데이트되었습니다.")
+ onOpenChange(false)
+ onSuccess?.()
+ } else {
+ toast.error(result.error || "주요품목 업데이트에 실패했습니다.")
+ }
+ } catch (error) {
+ console.error("주요품목 업데이트 오류:", error)
+ toast.error("주요품목 업데이트 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="w-[400px] sm:w-[540px] max-h-[90vh] overflow-y-auto">
+ <DialogHeader>
+ <DialogTitle>주요품목 등록</DialogTitle>
+ <DialogDescription>
+ {vendorName && `${vendorName}의 `}주요품목을 등록해주세요.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-6 mt-6">
+ {/* 선택된 아이템들 표시 */}
+ {selectedItems.length > 0 && (
+ <div className="space-y-2">
+ <Label>선택된 주요품목 ({selectedItems.length}개)</Label>
+ <div className="flex flex-wrap gap-2 max-h-40 overflow-y-auto border rounded-md p-3">
+ {selectedItems.map((item) => (
+ <Badge key={item.itemCode} variant="secondary" className="flex items-center gap-1">
+ <span className="text-xs">
+ {item.itemCode} - {item.itemName}
+ </span>
+ <Button
+ type="button"
+ variant="ghost"
+ size="sm"
+ className="h-4 w-4 p-0 hover:bg-destructive hover:text-destructive-foreground"
+ onClick={() => handleRemoveItem(item.itemCode)}
+ >
+ <X className="h-3 w-3" />
+ </Button>
+ </Badge>
+ ))}
+ </div>
+ </div>
+ )}
+
+ {/* 검색 입력 */}
+ <div className="space-y-2">
+ <Label>품목 검색 및 추가</Label>
+ <div className="relative">
+ <div className="relative">
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+ <Input
+ placeholder="아이템 코드 또는 이름으로 검색하세요"
+ value={itemSearchQuery}
+ onChange={(e) => setItemSearchQuery(e.target.value)}
+ className="pl-9"
+ />
+ </div>
+
+ {/* 검색 결과 드롭다운 */}
+ {showItemDropdown && (
+ <div className="absolute top-full left-0 right-0 z-50 mt-1 max-h-48 overflow-y-auto bg-background border rounded-md shadow-lg">
+ {filteredItems.length > 0 ? (
+ filteredItems.map((item) => (
+ <button
+ key={item.itemCode}
+ type="button"
+ className="w-full px-3 py-2 text-left text-sm hover:bg-muted focus:bg-muted focus:outline-none"
+ onClick={() => handleSelectItem(item)}
+ >
+ <div className="font-medium">{item.itemCode}</div>
+ <div className="text-muted-foreground text-xs">{item.itemName}</div>
+ </button>
+ ))
+ ) : (
+ <div className="px-3 py-2 text-sm text-muted-foreground">
+ 검색 결과가 없습니다.
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+
+ <div className="text-xs text-muted-foreground">
+ 아이템 코드나 이름을 입력하여 검색하고 선택하세요.
+ </div>
+ </div>
+ </div>
+
+ <div className="flex justify-end space-x-2 mt-6">
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => onOpenChange(false)}
+ disabled={isLoading}
+ >
+ 취소
+ </Button>
+ <Button
+ onClick={handleSave}
+ disabled={isLoading}
+ >
+ {isLoading ? "저장 중..." : "저장"}
+ </Button>
+ </div>
+ </DialogContent>
+ </Dialog>
+ )
+}