diff options
Diffstat (limited to 'lib/vendor-regular-registrations/table')
| -rw-r--r-- | lib/vendor-regular-registrations/table/major-items-update-dialog.tsx | 245 | ||||
| -rw-r--r-- | lib/vendor-regular-registrations/table/safety-qualification-update-dialog.tsx (renamed from lib/vendor-regular-registrations/table/safety-qualification-update-sheet.tsx) | 36 | ||||
| -rw-r--r-- | lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx | 22 |
3 files changed, 271 insertions, 32 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> + ) +} diff --git a/lib/vendor-regular-registrations/table/safety-qualification-update-sheet.tsx b/lib/vendor-regular-registrations/table/safety-qualification-update-dialog.tsx index c2aeba70..80084732 100644 --- a/lib/vendor-regular-registrations/table/safety-qualification-update-sheet.tsx +++ b/lib/vendor-regular-registrations/table/safety-qualification-update-dialog.tsx @@ -7,12 +7,12 @@ import { z } from "zod" import { toast } from "sonner" import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet" + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" import { Form, FormControl, @@ -29,7 +29,7 @@ const formSchema = z.object({ safetyQualificationContent: z.string().min(1, "안전적격성 평가 내용을 입력해주세요."), }) -interface SafetyQualificationUpdateSheetProps { +interface SafetyQualificationUpdateDialogProps { open: boolean onOpenChange: (open: boolean) => void registrationId?: number @@ -38,14 +38,14 @@ interface SafetyQualificationUpdateSheetProps { onSuccess?: () => void } -export function SafetyQualificationUpdateSheet({ +export function SafetyQualificationUpdateDialog({ open, onOpenChange, registrationId, vendorName, currentContent, onSuccess, -}: SafetyQualificationUpdateSheetProps) { +}: SafetyQualificationUpdateDialogProps) { const [isLoading, setIsLoading] = React.useState(false) const form = useForm<z.infer<typeof formSchema>>({ @@ -93,14 +93,14 @@ export function SafetyQualificationUpdateSheet({ } return ( - <Sheet open={open} onOpenChange={onOpenChange}> - <SheetContent className="w-[400px] sm:w-[540px]"> - <SheetHeader> - <SheetTitle>안전적격성 평가 입력</SheetTitle> - <SheetDescription> + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="w-[400px] sm:w-[540px] max-h-[90vh] overflow-y-auto"> + <DialogHeader> + <DialogTitle>안전적격성 평가 입력</DialogTitle> + <DialogDescription> {vendorName && `${vendorName}의 `}안전적격성 평가 내용을 입력해주세요. - </SheetDescription> - </SheetHeader> + </DialogDescription> + </DialogHeader> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 mt-6"> @@ -137,7 +137,7 @@ export function SafetyQualificationUpdateSheet({ </div> </form> </Form> - </SheetContent> - </Sheet> + </DialogContent> + </Dialog> ) } diff --git a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx index 765b0279..7446716b 100644 --- a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx +++ b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-columns.tsx @@ -13,29 +13,23 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge import { Eye, FileText, Ellipsis, Shield, Package } from "lucide-react"
import { toast } from "sonner"
import { useState } from "react"
-import { SafetyQualificationUpdateSheet } from "./safety-qualification-update-sheet"
-import { MajorItemsUpdateSheet } from "../major-items-update-sheet"
+import { SafetyQualificationUpdateDialog } from "./safety-qualification-update-dialog"
+import { MajorItemsUpdateDialog } from "./major-items-update-dialog"
const statusLabels = {
- audit_pass: "실사통과",
- cp_submitted: "CP등록",
- cp_review: "CP검토",
- cp_finished: "CP완료",
+ under_review: "검토중",
approval_ready: "조건충족",
- registration_requested: "등록요청됨",
in_review: "정규등록검토",
+ completed: "등록완료",
pending_approval: "장기미등록",
}
const statusColors = {
- audit_pass: "bg-blue-100 text-blue-800",
- cp_submitted: "bg-green-100 text-green-800",
- cp_review: "bg-yellow-100 text-yellow-800",
- cp_finished: "bg-purple-100 text-purple-800",
+ under_review: "bg-blue-100 text-blue-800",
approval_ready: "bg-emerald-100 text-emerald-800",
- registration_requested: "bg-indigo-100 text-indigo-800",
in_review: "bg-orange-100 text-orange-800",
+ completed: "bg-green-100 text-green-800",
pending_approval: "bg-red-100 text-red-800",
}
@@ -295,7 +289,7 @@ export function getColumns(): ColumnDef<VendorRegularRegistration>[] { </DropdownMenuContent>
</DropdownMenu>
- <SafetyQualificationUpdateSheet
+ <SafetyQualificationUpdateDialog
open={safetyQualificationSheetOpen}
onOpenChange={setSafetyQualificationSheetOpen}
registrationId={registration.id}
@@ -306,7 +300,7 @@ export function getColumns(): ColumnDef<VendorRegularRegistration>[] { window.location.reload()
}}
/>
- <MajorItemsUpdateSheet
+ <MajorItemsUpdateDialog
open={majorItemsSheetOpen}
onOpenChange={setMajorItemsSheetOpen}
registrationId={registration.id}
|
