diff options
Diffstat (limited to 'lib/vendor-investigation')
| -rw-r--r-- | lib/vendor-investigation/service.ts | 85 | ||||
| -rw-r--r-- | lib/vendor-investigation/table/contract-dialog.tsx | 85 | ||||
| -rw-r--r-- | lib/vendor-investigation/table/investigation-table.tsx | 56 | ||||
| -rw-r--r-- | lib/vendor-investigation/table/items-dialog.tsx | 73 |
4 files changed, 272 insertions, 27 deletions
diff --git a/lib/vendor-investigation/service.ts b/lib/vendor-investigation/service.ts index b731a95c..e3d03cd4 100644 --- a/lib/vendor-investigation/service.ts +++ b/lib/vendor-investigation/service.ts @@ -12,6 +12,7 @@ import { sendEmail } from "../mail/sendEmail"; import fs from "fs" import path from "path" import { v4 as uuid } from "uuid" +import { vendorsLogs } from "@/db/schema"; export async function getVendorsInvestigation(input: GetVendorsInvestigationSchema) { return unstable_cache( @@ -44,7 +45,7 @@ export async function getVendorsInvestigation(input: GetVendorsInvestigationSche const finalWhere = and( advancedWhere, globalWhere, - eq(vendorInvestigationsView.vendorStatus, "PQ_SUBMITTED") + // eq(vendorInvestigationsView.vendorStatus, "PQ_APPROVED") ) @@ -82,6 +83,8 @@ export async function getVendorsInvestigation(input: GetVendorsInvestigationSche // 7) Calculate pageCount const pageCount = Math.ceil(total / input.perPage) + console.log(data,"data") + // Now 'data' already contains JSON arrays of contacts & items // thanks to the subqueries in the view definition! return { data, pageCount } @@ -100,50 +103,84 @@ export async function getVendorsInvestigation(input: GetVendorsInvestigationSche } +/** + * Get existing investigations for a list of vendor IDs + * + * @param vendorIds Array of vendor IDs to check for existing investigations + * @returns Array of investigation data + */ +export async function getExistingInvestigationsForVendors(vendorIds: number[]) { + if (!vendorIds.length) return [] + + try { + // Query the vendorInvestigationsView using the vendorIds + const investigations = await db.query.vendorInvestigations.findMany({ + where: inArray(vendorInvestigationsView.vendorId, vendorIds), + orderBy: [desc(vendorInvestigationsView.investigationCreatedAt)], + }) + + return investigations + } catch (error) { + console.error("Error fetching existing investigations:", error) + return [] + } +} + interface RequestInvestigateVendorsInput { ids: number[] } export async function requestInvestigateVendors({ - ids, -}: RequestInvestigateVendorsInput) { + ids, userId // userId를 추가 +}: RequestInvestigateVendorsInput & { userId: number }) { try { if (!ids || ids.length === 0) { return { error: "No vendor IDs provided." } } - // 1. Create a new investigation row for each vendor - // You could also check if an investigation already exists for each vendor - // before inserting. For now, we’ll assume we always insert new ones. - const newRecords = await db - .insert(vendorInvestigations) - .values( - ids.map((vendorId) => ({ - vendorId - })) - ) - .returning() - - // 2. Optionally, send an email notification - // Adjust recipient, subject, and body as needed. + const result = await db.transaction(async (tx) => { + // 1. Create a new investigation row for each vendor + const newRecords = await tx + .insert(vendorInvestigations) + .values( + ids.map((vendorId) => ({ + vendorId + })) + ) + .returning(); + + // 2. 각 벤더에 대해 로그 기록 + await Promise.all( + ids.map(async (vendorId) => { + await tx.insert(vendorsLogs).values({ + vendorId: vendorId, + userId: userId, + action: "investigation_requested", + comment: "Investigation requested for this vendor", + }); + }) + ); + + return newRecords; + }); + + // 3. 이메일 발송 (트랜잭션 외부에서 실행) await sendEmail({ to: "dujin.kim@dtsolution.io", subject: "New Vendor Investigation(s) Requested", - // This template name could match a Handlebars file like: `investigation-request.hbs` template: "investigation-request", context: { - // For example, if you're translating in Korean: language: "ko", - // Add any data you want to use within the template vendorIds: ids, notes: "Please initiate the planned investigations soon." }, - }) + }); - // 3. Optionally, revalidate any pages that might show updated data - // revalidatePath("/your-vendors-page") // or wherever you list the vendors + // 4. 캐시 무효화 + revalidateTag("vendors"); + revalidateTag("vendor-investigations"); - return { data: newRecords, error: null } + return { data: result, error: null } } catch (err: unknown) { const errorMessage = err instanceof Error ? err.message : String(err) return { error: errorMessage } diff --git a/lib/vendor-investigation/table/contract-dialog.tsx b/lib/vendor-investigation/table/contract-dialog.tsx new file mode 100644 index 00000000..28e6963b --- /dev/null +++ b/lib/vendor-investigation/table/contract-dialog.tsx @@ -0,0 +1,85 @@ +"use client" + +import * as React from "react" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Avatar } from "@/components/ui/avatar" +import { ScrollArea } from "@/components/ui/scroll-area" +import { ContactItem } from "@/config/vendorInvestigationsColumnsConfig" + +interface ContactsDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + investigationId: number | null + contacts: ContactItem[] +} + +export function ContactsDialog({ + open, + onOpenChange, + investigationId, + contacts, +}: ContactsDialogProps) { + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="sm:max-w-md"> + <DialogHeader> + <DialogTitle>Vendor Contacts</DialogTitle> + <DialogDescription> + {contacts.length > 0 + ? `Showing ${contacts.length} contacts for investigation #${investigationId}` + : `No contacts found for investigation #${investigationId}`} + </DialogDescription> + </DialogHeader> + <ScrollArea className="max-h-[60vh] pr-4"> + {contacts.length > 0 ? ( + <div className="space-y-4"> + {contacts.map((contact, index) => ( + <div + key={index} + className="flex items-start gap-4 p-3 rounded-lg border" + > + <Avatar className="w-10 h-10"> + <span>{contact.contactName?.charAt(0) || "C"}</span> + </Avatar> + <div className="flex-1 space-y-1"> + <p className="font-medium">{contact.contactName || "Unnamed"}</p> + {contact.contactEmail && ( + <p className="text-sm text-muted-foreground"> + {contact.contactEmail} + </p> + )} + {contact.contactPhone && ( + <p className="text-sm text-muted-foreground"> + {contact.contactPhone} + </p> + )} + {contact.contactPosition && ( + <p className="text-sm text-muted-foreground"> + Position: {contact.contactPosition} + </p> + )} + </div> + </div> + ))} + </div> + ) : ( + <div className="text-center py-6 text-muted-foreground"> + No contacts available + </div> + )} + </ScrollArea> + <DialogFooter> + <Button onClick={() => onOpenChange(false)}>Close</Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) +}
\ No newline at end of file diff --git a/lib/vendor-investigation/table/investigation-table.tsx b/lib/vendor-investigation/table/investigation-table.tsx index fa4e2ab8..56aa7962 100644 --- a/lib/vendor-investigation/table/investigation-table.tsx +++ b/lib/vendor-investigation/table/investigation-table.tsx @@ -21,6 +21,8 @@ import { PossibleItem } from "@/config/vendorInvestigationsColumnsConfig" import { UpdateVendorInvestigationSheet } from "./update-investigation-sheet" +import { ItemsDrawer } from "./items-dialog" +import { ContactsDialog } from "./contract-dialog" interface VendorsTableProps { promises: Promise< @@ -71,18 +73,48 @@ export function VendorsInvestigationTable({ promises }: VendorsTableProps) { } as VendorInvestigationsViewWithContacts }) }, [rawResponse.data]) + + console.log(transformedData) const pageCount = rawResponse.pageCount + // Add state for row actions const [rowAction, setRowAction] = React.useState<DataTableRowAction<VendorInvestigationsViewWithContacts> | null>(null) + // Add state for contacts dialog + const [contactsDialogOpen, setContactsDialogOpen] = React.useState(false) + const [selectedContacts, setSelectedContacts] = React.useState<ContactItem[]>([]) + const [selectedContactInvestigationId, setSelectedContactInvestigationId] = React.useState<number | null>(null) + + // Add state for items drawer + const [itemsDrawerOpen, setItemsDrawerOpen] = React.useState(false) + const [selectedItems, setSelectedItems] = React.useState<PossibleItem[]>([]) + const [selectedItemInvestigationId, setSelectedItemInvestigationId] = React.useState<number | null>(null) + + // Create handlers for opening the contacts dialog and items drawer + const openContactsModal = React.useCallback((investigationId: number, contacts: ContactItem[]) => { + setSelectedContactInvestigationId(investigationId) + setSelectedContacts(contacts || []) + setContactsDialogOpen(true) + }, []) + + const openItemsDrawer = React.useCallback((investigationId: number, items: PossibleItem[]) => { + setSelectedItemInvestigationId(investigationId) + setSelectedItems(items || []) + setItemsDrawerOpen(true) + }, []) + // Get router const router = useRouter() - // Call getColumns() with router injection + // Call getColumns() with all required functions const columns = React.useMemo( - () => getColumns({ setRowAction }), - [setRowAction, router] + () => getColumns({ + setRowAction, + openContactsModal, + openItemsDrawer + }), + [setRowAction, openContactsModal, openItemsDrawer] ) const filterFields: DataTableFilterField<VendorInvestigationsViewWithContacts>[] = [ @@ -123,11 +155,29 @@ export function VendorsInvestigationTable({ promises }: VendorsTableProps) { <VendorsTableToolbarActions table={table} /> </DataTableAdvancedToolbar> </DataTable> + + {/* Update Investigation Sheet */} <UpdateVendorInvestigationSheet open={rowAction?.type === "update"} onOpenChange={() => setRowAction(null)} investigation={rowAction?.row.original ?? null} /> + + {/* Contacts Dialog */} + <ContactsDialog + open={contactsDialogOpen} + onOpenChange={setContactsDialogOpen} + investigationId={selectedContactInvestigationId} + contacts={selectedContacts} + /> + + {/* Items Drawer */} + <ItemsDrawer + open={itemsDrawerOpen} + onOpenChange={setItemsDrawerOpen} + investigationId={selectedItemInvestigationId} + items={selectedItems} + /> </> ) }
\ No newline at end of file diff --git a/lib/vendor-investigation/table/items-dialog.tsx b/lib/vendor-investigation/table/items-dialog.tsx new file mode 100644 index 00000000..5d010ff4 --- /dev/null +++ b/lib/vendor-investigation/table/items-dialog.tsx @@ -0,0 +1,73 @@ +"use client" + +import * as React from "react" +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetFooter, +} from "@/components/ui/sheet" +import { Button } from "@/components/ui/button" +import { ScrollArea } from "@/components/ui/scroll-area" +import { PossibleItem } from "@/config/vendorInvestigationsColumnsConfig" + +interface ItemsDrawerProps { + open: boolean + onOpenChange: (open: boolean) => void + investigationId: number | null + items: PossibleItem[] +} + +export function ItemsDrawer({ + open, + onOpenChange, + investigationId, + items, +}: ItemsDrawerProps) { + return ( + <Sheet open={open} onOpenChange={onOpenChange}> + <SheetContent className="sm:max-w-md"> + <SheetHeader> + <SheetTitle>Possible Items</SheetTitle> + <SheetDescription> + {items.length > 0 + ? `Showing ${items.length} items for investigation #${investigationId}` + : `No items found for investigation #${investigationId}`} + </SheetDescription> + </SheetHeader> + <ScrollArea className="max-h-[70vh] mt-6 pr-4"> + {items.length > 0 ? ( + <div className="space-y-4"> + {items.map((item, index) => ( + <div + key={index} + className="flex flex-col gap-2 p-3 rounded-lg border" + > + <div className="flex justify-between items-start"> + <h4 className="font-medium">{item.itemName || "Unknown Item"}</h4> + {item.itemName && ( + <span className="text-xs bg-muted px-2 py-1 rounded"> + {item.itemCode} + </span> + )} + </div> + + + </div> + ))} + </div> + ) : ( + <div className="text-center py-6 text-muted-foreground"> + No items available + </div> + )} + </ScrollArea> + <SheetFooter className="mt-4"> + <Button onClick={() => onOpenChange(false)}>Close</Button> + </SheetFooter> + </SheetContent> + </Sheet> + ) +}
\ No newline at end of file |
