From cf8dac0c6490469dab88a560004b0c07dbd48612 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 18 Sep 2025 00:23:40 +0000 Subject: (대표님) rfq, 계약, 서명 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/itb/table/create-rfq-dialog.tsx | 380 ++++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 lib/itb/table/create-rfq-dialog.tsx (limited to 'lib/itb/table/create-rfq-dialog.tsx') diff --git a/lib/itb/table/create-rfq-dialog.tsx b/lib/itb/table/create-rfq-dialog.tsx new file mode 100644 index 00000000..57a4b9d4 --- /dev/null +++ b/lib/itb/table/create-rfq-dialog.tsx @@ -0,0 +1,380 @@ +// components/purchase-requests/create-rfq-dialog.tsx +"use client"; + +import * as React from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Badge } from "@/components/ui/badge"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { + FileText, + Package, + AlertCircle, + CheckCircle, + User, + ChevronsUpDown, + Check, + Loader2, + Info +} from "lucide-react"; +import { toast } from "sonner"; +import { cn } from "@/lib/utils"; +import type { PurchaseRequestView } from "@/db/schema"; +import { approvePurchaseRequestsAndCreateRfqs } from "../service"; +import { getPUsersForFilter } from "@/lib/rfq-last/service"; +import { useRouter } from "next/navigation"; + +interface CreateRfqDialogProps { + requests: PurchaseRequestView[]; + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess?: () => void; +} + +export function CreateRfqDialog({ + requests, + open, + onOpenChange, + onSuccess, +}: CreateRfqDialogProps) { + const [isLoading, setIsLoading] = React.useState(false); + const [userPopoverOpen, setUserPopoverOpen] = React.useState(false); + const [users, setUsers] = React.useState([]); + const [selectedUser, setSelectedUser] = React.useState(null); + const [isLoadingUsers, setIsLoadingUsers] = React.useState(false); + const [userSearchTerm, setUserSearchTerm] = React.useState(""); + const router = useRouter(); + + // 유저 목록 로드 + React.useEffect(() => { + const loadUsers = async () => { + setIsLoadingUsers(true); + try { + const userList = await getPUsersForFilter(); + setUsers(userList); + } catch (error) { + console.log("사용자 목록 로드 오류:", error); + toast.error("사용자 목록을 불러오는데 실패했습니다"); + } finally { + setIsLoadingUsers(false); + } + }; + + if (open) { + loadUsers(); + } + }, [open]); + + // 검색된 사용자 필터링 + const filteredUsers = React.useMemo(() => { + if (!userSearchTerm) return users; + + return users.filter(user => + user.name.toLowerCase().includes(userSearchTerm.toLowerCase()) || + user.userCode?.toLowerCase().includes(userSearchTerm.toLowerCase()) + ); + }, [users, userSearchTerm]); + + // 유효한 요청만 필터링 (이미 RFQ 생성된 것 제외) + const validRequests = requests.filter(r => r.status !== "RFQ생성완료"); + const invalidRequests = requests.filter(r => r.status === "RFQ생성완료"); + + const handleSelectUser = (user: any) => { + setSelectedUser(user); + setUserPopoverOpen(false); + }; + + const handleSubmit = async () => { + if (validRequests.length === 0) { + toast.error("RFQ를 생성할 수 있는 구매 요청이 없습니다"); + return; + } + + try { + setIsLoading(true); + + const requestIds = validRequests.map(r => r.id); + const results = await approvePurchaseRequestsAndCreateRfqs( + requestIds, + selectedUser?.id + ); + + const successCount = results.filter(r => r.success).length; + const skipCount = results.filter(r => r.skipped).length; + + if (successCount > 0) { + toast.success(`${successCount}개의 RFQ가 생성되었습니다`); + } + + if (skipCount > 0) { + toast.info(`${skipCount}개는 이미 RFQ가 생성되어 건너뛰었습니다`); + } + + onOpenChange(false); + onSuccess?.(); + router.refresh() + } catch (error) { + console.error("RFQ 생성 오류:", error); + toast.error("RFQ 생성 중 오류가 발생했습니다"); + } finally { + setIsLoading(false); + } + }; + + const handleClose = () => { + if (!isLoading) { + setSelectedUser(null); + setUserSearchTerm(""); + onOpenChange(false); + } + }; + + return ( + + + + + + RFQ 생성 + + + 선택한 구매 요청을 기반으로 RFQ를 생성합니다. + {invalidRequests.length > 0 && " 이미 RFQ가 생성된 항목은 제외됩니다."} + + + +
+ {/* 경고 메시지 */} + {invalidRequests.length > 0 && ( + + + + {invalidRequests.length}개 항목은 이미 RFQ가 생성되어 제외됩니다. + + + )} + + {/* 구매 담당자 선택 */} +
+ + + + + + + + + + 검색 결과가 없습니다 + + {filteredUsers.map((user) => ( + handleSelectUser(user)} + className="flex items-center justify-between" + > + + + {user.name} + {user.userCode && ( + + ({user.userCode}) + + )} + + + + ))} + + + + + +

+ 구매 담당자를 선택하지 않으면 나중에 지정할 수 있습니다 +

+
+ + {/* RFQ 생성 대상 목록 */} +
+ +
+ + + + 요청번호 + 요청제목 + 프로젝트 + 패키지 + 품목 + 첨부 + + + + {validRequests.length === 0 ? ( + + + RFQ를 생성할 수 있는 구매 요청이 없습니다 + + + ) : ( + validRequests.map((request) => ( + + + {request.requestCode} + + +
+ {request.requestTitle} +
+
+ +
+ {request.projectCode} +
+
+ +
+ {request.packageNo} +
+
+ + {request.itemCount > 0 && ( + + + {request.itemCount} + + )} + + + {request.attachmentCount > 0 && ( + + + {request.attachmentCount} + + )} + +
+ )) + )} +
+
+
+
+ + {/* 안내 메시지 */} + + + +
    +
  • RFQ 생성 시 구매 요청의 첨부파일이 자동으로 이관됩니다
  • +
  • 구매 요청 상태가 "RFQ생성완료"로 변경됩니다
  • +
  • 각 구매 요청별로 개별 RFQ가 생성됩니다
  • +
+
+
+
+ + + + + +
+
+ ); +} \ No newline at end of file -- cgit v1.2.3