diff options
| author | joonhoekim <26rote@gmail.com> | 2025-12-01 19:52:06 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-12-01 19:52:06 +0900 |
| commit | 44b74ff4170090673b6eeacd8c528e0abf47b7aa (patch) | |
| tree | 3f3824b4e2cb24536c1677188b4cae5b8909d3da /lib/vendor-rfq-response/vendor-rfq-table | |
| parent | 4953e770929b82ef77da074f77071ebd0f428529 (diff) | |
(김준회) deprecated code 정리
Diffstat (limited to 'lib/vendor-rfq-response/vendor-rfq-table')
7 files changed, 0 insertions, 1414 deletions
diff --git a/lib/vendor-rfq-response/vendor-rfq-table/ItemsDialog.tsx b/lib/vendor-rfq-response/vendor-rfq-table/ItemsDialog.tsx deleted file mode 100644 index 504fc177..00000000 --- a/lib/vendor-rfq-response/vendor-rfq-table/ItemsDialog.tsx +++ /dev/null @@ -1,125 +0,0 @@ -"use client" - -import * as React from "react" -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, - DialogFooter, -} from "@/components/ui/dialog" -import { Button } from "@/components/ui/button" -import { - Table, - TableBody, - TableCaption, - TableCell, - TableFooter, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table" -import { RfqWithAll } from "../types" -/** - * 아이템 구조 예시 - * - API 응답에서 quantity가 "string" 형태이므로, - * 숫자로 사용하실 거라면 parse 과정이 필요할 수 있습니다. - */ -export interface RfqItem { - id: number - itemCode: string - itemName: string - quantity: string - description: string - uom: string -} - -/** - * 첨부파일 구조 예시 - */ -export interface RfqAttachment { - id: number - fileName: string - filePath: string - vendorId: number | null - evaluationId: number | null -} - - -/** - * 다이얼로그 내에서만 사용할 단순 아이템 구조 (예: 임시/기본값 표출용) - */ -export interface DefaultItem { - id?: number - itemCode: string - description?: string | null - quantity?: number | null - uom?: string | null -} - -/** - * RfqsItemsDialog 컴포넌트 Prop 타입 - */ -export interface RfqsItemsDialogProps { - open: boolean - onOpenChange: (open: boolean) => void - rfq: RfqWithAll - defaultItems?: DefaultItem[] -} - -export function RfqsItemsDialog({ - open, - onOpenChange, - rfq, -}: RfqsItemsDialogProps) { - return ( - <Dialog open={open} onOpenChange={onOpenChange}> - <DialogContent className="max-w-none w-[1200px]"> - <DialogHeader> - <DialogTitle>Items for RFQ {rfq?.rfqCode}</DialogTitle> - <DialogDescription> - Below is the list of items for this RFQ. - </DialogDescription> - </DialogHeader> - - <div className="overflow-x-auto w-full space-y-4"> - {rfq && rfq.items.length === 0 && ( - <p className="text-sm text-muted-foreground">No items found.</p> - )} - {rfq && rfq.items.length > 0 && ( - <Table> - {/* 필요에 따라 TableCaption 등을 추가해도 좋습니다. */} - <TableHeader> - <TableRow> - <TableHead>Item Code</TableHead> - <TableHead>Item Code</TableHead> - <TableHead>Description</TableHead> - <TableHead>Qty</TableHead> - <TableHead>UoM</TableHead> - </TableRow> - </TableHeader> - <TableBody> - {rfq.items.map((it, idx) => ( - <TableRow key={it.id ?? idx}> - <TableCell>{it.itemCode || "No Code"}</TableCell> - <TableCell>{it.itemName || "No Name"}</TableCell> - <TableCell>{it.description || "-"}</TableCell> - <TableCell>{it.quantity ?? 1}</TableCell> - <TableCell>{it.uom ?? "each"}</TableCell> - </TableRow> - ))} - </TableBody> - </Table> - )} - </div> - - <DialogFooter className="mt-4"> - <Button type="button" variant="outline" onClick={() => onOpenChange(false)}> - Close - </Button> - </DialogFooter> - </DialogContent> - </Dialog> - ) -}
\ No newline at end of file diff --git a/lib/vendor-rfq-response/vendor-rfq-table/attachment-rfq-sheet.tsx b/lib/vendor-rfq-response/vendor-rfq-table/attachment-rfq-sheet.tsx deleted file mode 100644 index 6c51c12c..00000000 --- a/lib/vendor-rfq-response/vendor-rfq-table/attachment-rfq-sheet.tsx +++ /dev/null @@ -1,106 +0,0 @@ -"use client" - -import * as React from "react" -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, - SheetDescription, - SheetFooter, - SheetClose, -} from "@/components/ui/sheet" -import { Button } from "@/components/ui/button" -import { Download } from "lucide-react" -import { formatDate } from "@/lib/utils" - -// 첨부파일 구조 -interface RfqAttachment { - id: number - fileName: string - filePath: string - createdAt?: Date // or Date - vendorId?: number | null - size?: number -} - -// 컴포넌트 Prop -interface RfqAttachmentsSheetProps extends React.ComponentPropsWithRef<typeof Sheet> { - rfqId: number - attachments?: RfqAttachment[] -} - -/** - * RfqAttachmentsSheet: - * - 단순히 첨부파일 리스트 + 다운로드 버튼만 - */ -export function RfqAttachmentsSheet({ - rfqId, - attachments = [], - ...props -}: RfqAttachmentsSheetProps) { - return ( - <Sheet {...props}> - <SheetContent className="flex flex-col gap-6 sm:max-w-sm"> - <SheetHeader> - <SheetTitle>Attachments</SheetTitle> - <SheetDescription>RFQ #{rfqId}에 대한 첨부파일 목록</SheetDescription> - </SheetHeader> - - <div className="space-y-2"> - {/* 첨부파일이 없을 경우 */} - {attachments.length === 0 && ( - <p className="text-sm text-muted-foreground"> - No attachments - </p> - )} - - {/* 첨부파일 목록 */} - {attachments.map((att) => ( - <div - key={att.id} - className="flex items-center justify-between rounded border p-2" - > - <div className="flex flex-col text-sm"> - <span className="font-medium">{att.fileName}</span> - {att.size && ( - <span className="text-xs text-muted-foreground"> - {Math.round(att.size / 1024)} KB - </span> - )} - {att.createdAt && ( - <span className="text-xs text-muted-foreground"> - Created at {formatDate(att.createdAt)} - </span> - )} - </div> - {/* 파일 다운로드 버튼 */} - {att.filePath && ( - <a - href={att.filePath} - download - target="_blank" - rel="noreferrer" - className="text-sm" - > - <Button variant="ghost" size="icon" type="button"> - <Download className="h-4 w-4" /> - </Button> - </a> - )} - </div> - ))} - </div> - - <SheetFooter className="gap-2 pt-2"> - {/* 닫기 버튼 */} - <SheetClose asChild> - <Button type="button" variant="outline"> - Close - </Button> - </SheetClose> - </SheetFooter> - </SheetContent> - </Sheet> - ) -}
\ No newline at end of file diff --git a/lib/vendor-rfq-response/vendor-rfq-table/comments-sheet.tsx b/lib/vendor-rfq-response/vendor-rfq-table/comments-sheet.tsx deleted file mode 100644 index 5bb8a16a..00000000 --- a/lib/vendor-rfq-response/vendor-rfq-table/comments-sheet.tsx +++ /dev/null @@ -1,320 +0,0 @@ -"use client" - -import * as React from "react" -import { useForm, useFieldArray } from "react-hook-form" -import { z } from "zod" -import { zodResolver } from "@hookform/resolvers/zod" -import { Download, X, Loader2 } from "lucide-react" -import prettyBytes from "pretty-bytes" -import { toast } from "sonner" - -import { - Sheet, - SheetClose, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet" -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Textarea } from "@/components/ui/textarea" -import { - Dropzone, - DropzoneZone, - DropzoneUploadIcon, - DropzoneTitle, - DropzoneDescription, - DropzoneInput, -} from "@/components/ui/dropzone" -import { - Table, - TableHeader, - TableRow, - TableHead, - TableBody, - TableCell, -} from "@/components/ui/table" - -import { formatDate } from "@/lib/utils" -import { createRfqCommentWithAttachments } from "@/lib/rfqs/service" - - -export interface MatchedVendorComment { - id: number - commentText: string - commentedBy?: number - commentedByEmail?: string - createdAt?: Date - attachments?: { - id: number - fileName: string - filePath: string - }[] -} - -// 1) props 정의 -interface CommentSheetProps extends React.ComponentPropsWithRef<typeof Sheet> { - initialComments?: MatchedVendorComment[] - currentUserId: number - rfqId: number - vendorId: number - onCommentsUpdated?: (comments: MatchedVendorComment[]) => void - isLoading?: boolean // New prop -} - -// 2) 폼 스키마 -const commentFormSchema = z.object({ - commentText: z.string().min(1, "댓글을 입력하세요."), - newFiles: z.array(z.any()).optional(), // File[] -}) -type CommentFormValues = z.infer<typeof commentFormSchema> - -const MAX_FILE_SIZE = 30e6 // 30MB - -export function CommentSheet({ - rfqId, - vendorId, - initialComments = [], - currentUserId, - onCommentsUpdated, - isLoading = false, // Default to false - ...props -}: CommentSheetProps) { - - console.log(initialComments) - - const [comments, setComments] = React.useState<MatchedVendorComment[]>(initialComments) - const [isPending, startTransition] = React.useTransition() - - React.useEffect(() => { - setComments(initialComments) - }, [initialComments]) - - const form = useForm<CommentFormValues>({ - resolver: zodResolver(commentFormSchema), - defaultValues: { - commentText: "", - newFiles: [], - }, - }) - - const { fields: newFileFields, append, remove } = useFieldArray({ - control: form.control, - name: "newFiles", - }) - - // (A) 기존 코멘트 렌더링 - function renderExistingComments() { - - if (isLoading) { - return ( - <div className="flex justify-center items-center h-32"> - <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" /> - <span className="ml-2 text-sm text-muted-foreground">Loading comments...</span> - </div> - ) - } - - if (comments.length === 0) { - return <p className="text-sm text-muted-foreground">No comments yet</p> - } - return ( - <Table> - <TableHeader> - <TableRow> - <TableHead className="w-1/2">Comment</TableHead> - <TableHead>Attachments</TableHead> - <TableHead>Created At</TableHead> - <TableHead>Created By</TableHead> - </TableRow> - </TableHeader> - <TableBody> - {comments.map((c) => ( - <TableRow key={c.id}> - <TableCell>{c.commentText}</TableCell> - <TableCell> - {!c.attachments?.length && ( - <span className="text-sm text-muted-foreground">No files</span> - )} - {c.attachments?.length && ( - <div className="flex flex-col gap-1"> - {c.attachments.map((att) => ( - <div key={att.id} className="flex items-center gap-2"> - <a - href={`/api/rfq-download?path=${encodeURIComponent(att.filePath)}`} - download - target="_blank" - rel="noreferrer" - className="inline-flex items-center gap-1 text-blue-600 underline" - > - <Download className="h-4 w-4" /> - {att.fileName} - </a> - </div> - ))} - </div> - )} - </TableCell> - <TableCell> { c.createdAt ? formatDate(c.createdAt): "-"}</TableCell> - <TableCell>{c.commentedByEmail ?? "-"}</TableCell> - </TableRow> - ))} - </TableBody> - </Table> - ) - } - - // (B) 파일 드롭 - function handleDropAccepted(files: File[]) { - append(files) - } - - // (C) Submit - async function onSubmit(data: CommentFormValues) { - if (!rfqId) return - startTransition(async () => { - try { - const res = await createRfqCommentWithAttachments({ - rfqId, - vendorId, - commentText: data.commentText, - commentedBy: currentUserId, - evaluationId: null, - cbeId: null, - files: data.newFiles, - }) - - if (!res.ok) { - throw new Error("Failed to create comment") - } - - toast.success("Comment created") - - // 임시로 새 코멘트 추가 - const newComment: MatchedVendorComment = { - id: res.commentId, // 서버 응답 - commentText: data.commentText, - commentedBy: currentUserId, - createdAt: res.createdAt, - attachments: - data.newFiles?.map((f) => ({ - id: Math.floor(Math.random() * 1e6), - fileName: f.name, - filePath: "/uploads/" + f.name, - })) || [], - } - setComments((prev) => [...prev, newComment]) - onCommentsUpdated?.([...comments, newComment]) - - form.reset() - } catch (err: any) { - console.error(err) - toast.error("Error: " + err.message) - } - }) - } - - return ( - <Sheet {...props}> - <SheetContent className="flex flex-col gap-6 sm:max-w-lg"> - <SheetHeader className="text-left"> - <SheetTitle>Comments</SheetTitle> - <SheetDescription> - 필요시 첨부파일과 함께 문의/코멘트를 남길 수 있습니다. - </SheetDescription> - </SheetHeader> - - <div className="max-h-[300px] overflow-y-auto">{renderExistingComments()}</div> - - <Form {...form}> - <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4"> - <FormField - control={form.control} - name="commentText" - render={({ field }) => ( - <FormItem> - <FormLabel>New Comment</FormLabel> - <FormControl> - <Textarea placeholder="Enter your comment..." {...field} /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - <Dropzone - maxSize={MAX_FILE_SIZE} - onDropAccepted={handleDropAccepted} - onDropRejected={(rej) => { - toast.error("File rejected: " + (rej[0]?.file?.name || "")) - }} - > - {({ maxSize }) => ( - <DropzoneZone className="flex justify-center"> - <DropzoneInput /> - <div className="flex items-center gap-6"> - <DropzoneUploadIcon /> - <div className="grid gap-0.5"> - <DropzoneTitle>Drop to attach files</DropzoneTitle> - <DropzoneDescription> - Max size: {prettyBytes(maxSize || 0)} - </DropzoneDescription> - </div> - </div> - </DropzoneZone> - )} - </Dropzone> - - {newFileFields.length > 0 && ( - <div className="flex flex-col gap-2"> - {newFileFields.map((field, idx) => { - const file = form.getValues(`newFiles.${idx}`) - if (!file) return null - return ( - <div - key={field.id} - className="flex items-center justify-between border rounded p-2" - > - <span className="text-sm"> - {file.name} ({prettyBytes(file.size)}) - </span> - <Button - variant="ghost" - size="icon" - type="button" - onClick={() => remove(idx)} - > - <X className="h-4 w-4" /> - </Button> - </div> - ) - })} - </div> - )} - - <SheetFooter className="gap-2 pt-4"> - <SheetClose asChild> - <Button type="button" variant="outline"> - Cancel - </Button> - </SheetClose> - <Button disabled={isPending}> - {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} - Save - </Button> - </SheetFooter> - </form> - </Form> - </SheetContent> - </Sheet> - ) -}
\ No newline at end of file diff --git a/lib/vendor-rfq-response/vendor-rfq-table/feature-flags-provider.tsx b/lib/vendor-rfq-response/vendor-rfq-table/feature-flags-provider.tsx deleted file mode 100644 index 81131894..00000000 --- a/lib/vendor-rfq-response/vendor-rfq-table/feature-flags-provider.tsx +++ /dev/null @@ -1,108 +0,0 @@ -"use client" - -import * as React from "react" -import { useQueryState } from "nuqs" - -import { dataTableConfig, type DataTableConfig } from "@/config/data-table" -import { cn } from "@/lib/utils" -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip" - -type FeatureFlagValue = DataTableConfig["featureFlags"][number]["value"] - -interface FeatureFlagsContextProps { - featureFlags: FeatureFlagValue[] - setFeatureFlags: (value: FeatureFlagValue[]) => void -} - -const FeatureFlagsContext = React.createContext<FeatureFlagsContextProps>({ - featureFlags: [], - setFeatureFlags: () => {}, -}) - -export function useFeatureFlags() { - const context = React.useContext(FeatureFlagsContext) - if (!context) { - throw new Error( - "useFeatureFlags must be used within a FeatureFlagsProvider" - ) - } - return context -} - -interface FeatureFlagsProviderProps { - children: React.ReactNode -} - -export function FeatureFlagsProvider({ children }: FeatureFlagsProviderProps) { - const [featureFlags, setFeatureFlags] = useQueryState<FeatureFlagValue[]>( - "flags", - { - defaultValue: [], - parse: (value) => value.split(",") as FeatureFlagValue[], - serialize: (value) => value.join(","), - eq: (a, b) => - a.length === b.length && a.every((value, index) => value === b[index]), - clearOnDefault: true, - shallow: false, - } - ) - - return ( - <FeatureFlagsContext.Provider - value={{ - featureFlags, - setFeatureFlags: (value) => void setFeatureFlags(value), - }} - > - <div className="w-full overflow-x-auto"> - <ToggleGroup - type="multiple" - variant="outline" - size="sm" - value={featureFlags} - onValueChange={(value: FeatureFlagValue[]) => setFeatureFlags(value)} - className="w-fit gap-0" - > - {dataTableConfig.featureFlags.map((flag, index) => ( - <Tooltip key={flag.value}> - <ToggleGroupItem - value={flag.value} - className={cn( - "gap-2 whitespace-nowrap rounded-none px-3 text-xs data-[state=on]:bg-accent/70 data-[state=on]:hover:bg-accent/90", - { - "rounded-l-sm border-r-0": index === 0, - "rounded-r-sm": - index === dataTableConfig.featureFlags.length - 1, - } - )} - asChild - > - <TooltipTrigger> - <flag.icon className="size-3.5 shrink-0" aria-hidden="true" /> - {flag.label} - </TooltipTrigger> - </ToggleGroupItem> - <TooltipContent - align="start" - side="bottom" - sideOffset={6} - className="flex max-w-60 flex-col space-y-1.5 border bg-background py-2 font-semibold text-foreground" - > - <div>{flag.tooltipTitle}</div> - <div className="text-xs text-muted-foreground"> - {flag.tooltipDescription} - </div> - </TooltipContent> - </Tooltip> - ))} - </ToggleGroup> - </div> - {children} - </FeatureFlagsContext.Provider> - ) -} diff --git a/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-columns.tsx b/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-columns.tsx deleted file mode 100644 index 70b91176..00000000 --- a/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-columns.tsx +++ /dev/null @@ -1,435 +0,0 @@ -"use client" - -import * as React from "react" -import { useRouter } from "next/navigation" -import { ColumnDef } from "@tanstack/react-table" -import { - Ellipsis, - MessageSquare, - Package, - Paperclip, -} from "lucide-react" -import { toast } from "sonner" - -import { Button } from "@/components/ui/button" -import { Checkbox } from "@/components/ui/checkbox" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu" -import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" -import { Badge } from "@/components/ui/badge" - -import { getErrorMessage } from "@/lib/handle-error" -import { formatDate, formatDateTime } from "@/lib/utils" -import { modifyRfqVendor } from "../../rfqs/service" -import type { RfqWithAll } from "../types" -import type { DataTableRowAction } from "@/types/table" - -type NextRouter = ReturnType<typeof useRouter> - -interface GetColumnsProps { - setRowAction: React.Dispatch< - React.SetStateAction<DataTableRowAction<RfqWithAll> | null> - > - router: NextRouter - openAttachmentsSheet: (rfqId: number) => void - openCommentSheet: (rfqId: number) => void -} - -/** - * tanstack table 컬럼 정의 (Nested Header) - */ -export function getColumns({ - setRowAction, - router, - openAttachmentsSheet, - openCommentSheet, -}: GetColumnsProps): ColumnDef<RfqWithAll>[] { - // 1) 체크박스(Select) 컬럼 - const selectColumn: ColumnDef<RfqWithAll> = { - id: "select", - header: ({ table }) => ( - <Checkbox - checked={ - table.getIsAllPageRowsSelected() || - (table.getIsSomePageRowsSelected() && "indeterminate") - } - onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> - ), - cell: ({ row }) => ( - <Checkbox - checked={row.getIsSelected()} - onCheckedChange={(value) => row.toggleSelected(!!value)} - aria-label="Select row" - /> - ), - size: 40, - enableSorting: false, - enableHiding: false, - } - - // 2) Actions (Dropdown) - const actionsColumn: ColumnDef<RfqWithAll> = { - id: "actions", - enableHiding: false, - cell: ({ row }) => { - const [isUpdatePending, startUpdateTransition] = React.useTransition() - - return ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button variant="ghost" size="icon"> - <Ellipsis className="h-4 w-4" aria-hidden="true" /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end" className="w-56"> - <DropdownMenuSub> - <DropdownMenuSubTrigger>RFQ Response</DropdownMenuSubTrigger> - <DropdownMenuSubContent> - <DropdownMenuRadioGroup - value={row.original.responseStatus} - onValueChange={(value) => { - startUpdateTransition(async () => { - let newStatus: - | "ACCEPTED" - | "DECLINED" - | "REVIEWING" - - switch (value) { - case "ACCEPTED": - newStatus = "ACCEPTED" - break - case "DECLINED": - newStatus = "DECLINED" - break - default: - newStatus = "REVIEWING" - } - - await toast.promise( - modifyRfqVendor({ - id: row.original.responseId, - status: newStatus, - }), - { - loading: "Updating response status...", - success: "Response status updated", - error: (err) => getErrorMessage(err), - } - ) - }) - }} - > - {[ - { value: "ACCEPTED", label: "Accept RFQ" }, - { value: "DECLINED", label: "Decline RFQ" }, - ].map((rep) => ( - <DropdownMenuRadioItem - key={rep.value} - value={rep.value} - className="capitalize" - disabled={isUpdatePending} - > - {rep.label} - </DropdownMenuRadioItem> - ))} - </DropdownMenuRadioGroup> - </DropdownMenuSubContent> - </DropdownMenuSub> - {/* <DropdownMenuItem - onClick={() => { - router.push(`/vendor/rfqs/${row.original.rfqId}`) - }} - > - View Details - </DropdownMenuItem> */} - {/* <DropdownMenuItem onClick={() => openAttachmentsSheet(row.original.rfqId)}> - View Attachments - </DropdownMenuItem> - <DropdownMenuItem onClick={() => openCommentSheet(row.original.rfqId)}> - View Comments - </DropdownMenuItem> - <DropdownMenuItem onClick={() => setRowAction({ row, type: "items" })}> - View Items - </DropdownMenuItem> */} - </DropdownMenuContent> - </DropdownMenu> - ) - }, - size: 40, - } - - // 3) RFQ Code 컬럼 - const rfqCodeColumn: ColumnDef<RfqWithAll> = { - id: "rfqCode", - accessorKey: "rfqCode", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="RFQ Code" /> - ), - // cell: ({ row }) => { - // return ( - // <Button - // variant="link" - // className="p-0 h-auto font-medium" - // onClick={() => router.push(`/vendor/rfqs/${row.original.rfqId}`)} - // > - // {row.original.rfqCode} - // </Button> - // ) - // }, - cell: ({ row }) => row.original.rfqCode || "-", - size: 150, - } - - const rfqTypeColumn: ColumnDef<RfqWithAll> = { - id: "rfqType", - accessorKey: "rfqType", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="RFQ Type" /> - ), - cell: ({ row }) => row.original.rfqType || "-", - size: 150, - } - - - // 4) 응답 상태 컬럼 - const responseStatusColumn: ColumnDef<RfqWithAll> = { - id: "responseStatus", - accessorKey: "responseStatus", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Response Status" /> - ), - cell: ({ row }) => { - const status = row.original.responseStatus; - let variant: "default" | "secondary" | "destructive" | "outline"; - - switch (status) { - case "REVIEWING": - variant = "default"; - break; - case "ACCEPTED": - variant = "secondary"; - break; - case "DECLINED": - variant = "destructive"; - break; - default: - variant = "outline"; - } - - return <Badge variant={variant}>{status}</Badge>; - }, - size: 150, - } - - // 5) 프로젝트 이름 컬럼 - const projectNameColumn: ColumnDef<RfqWithAll> = { - id: "projectName", - accessorKey: "projectName", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Project" /> - ), - cell: ({ row }) => row.original.projectName || "-", - size: 150, - } - - // 6) RFQ Description 컬럼 - const descriptionColumn: ColumnDef<RfqWithAll> = { - id: "rfqDescription", - accessorKey: "rfqDescription", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Description" /> - ), - cell: ({ row }) => row.original.rfqDescription || "-", - size: 200, - } - - // 7) Due Date 컬럼 - const dueDateColumn: ColumnDef<RfqWithAll> = { - id: "rfqDueDate", - accessorKey: "rfqDueDate", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Due Date" /> - ), - cell: ({ row }) => { - const date = row.original.rfqDueDate; - return date ? formatDate(date) : "-"; - }, - size: 120, - } - - // 8) Last Updated 컬럼 - const updatedAtColumn: ColumnDef<RfqWithAll> = { - id: "respondedAt", - accessorKey: "respondedAt", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Last Updated" /> - ), - cell: ({ row }) => { - const date = row.original.respondedAt; - return date ? formatDateTime(date) : "-"; - }, - size: 150, - } - - // 9) Items 컬럼 - 뱃지로 아이템 개수 표시 - const itemsColumn: ColumnDef<RfqWithAll> = { - id: "items", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Items" /> - ), - cell: ({ row }) => { - const rfq = row.original - const count = rfq.items?.length ?? 0 - - function handleClick() { - setRowAction({ row, type: "items" }) - } - - return ( - <Button - variant="ghost" - size="sm" - className="relative h-8 w-8 p-0 group" - onClick={handleClick} - aria-label={count > 0 ? `View ${count} items` : "No items"} - > - <Package className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> - {count > 0 && ( - <Badge - variant="secondary" - className="pointer-events-none absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem] leading-none flex items-center justify-center" - > - {count} - </Badge> - )} - - <span className="sr-only"> - {count > 0 ? `${count} Items` : "No Items"} - </span> - </Button> - ) - }, - enableSorting: false, - maxSize: 80, - } - - // 10) Attachments 컬럼 - 뱃지로 파일 개수 표시 - const attachmentsColumn: ColumnDef<RfqWithAll> = { - id: "attachments", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Attachments" /> - ), - cell: ({ row }) => { - const attachCount = row.original.attachments?.length ?? 0 - - function handleClick(e: React.MouseEvent<HTMLButtonElement>) { - e.preventDefault() - openAttachmentsSheet(row.original.rfqId) - } - - return ( - <Button - variant="ghost" - size="sm" - className="relative h-8 w-8 p-0 group" - onClick={handleClick} - aria-label={ - attachCount > 0 ? `View ${attachCount} files` : "No files" - } - > - <Paperclip className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> - {attachCount > 0 && ( - <Badge - variant="secondary" - className="pointer-events-none absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem] leading-none flex items-center justify-center" - > - {attachCount} - </Badge> - )} - <span className="sr-only"> - {attachCount > 0 ? `${attachCount} Files` : "No Files"} - </span> - </Button> - ) - }, - enableSorting: false, - maxSize: 80, - } - - // 11) Comments 컬럼 - 뱃지로 댓글 개수 표시 - const commentsColumn: ColumnDef<RfqWithAll> = { - id: "comments", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Comments" /> - ), - cell: ({ row }) => { - const commCount = row.original.comments?.length ?? 0 - - function handleClick() { - setRowAction({ row, type: "comments" }) - openCommentSheet(row.original.rfqId) - } - - return ( - <Button - variant="ghost" - size="sm" - className="relative h-8 w-8 p-0 group" - onClick={handleClick} - aria-label={ - commCount > 0 ? `View ${commCount} comments` : "No comments" - } - > - <MessageSquare className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> - {commCount > 0 && ( - <Badge - variant="secondary" - className="pointer-events-none absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem] leading-none flex items-center justify-center" - > - {commCount} - </Badge> - )} - <span className="sr-only"> - {commCount > 0 ? `${commCount} Comments` : "No Comments"} - </span> - </Button> - ) - }, - enableSorting: false, - maxSize: 80, - } - - // 최종 컬럼 구성 - TBE/CBE 관련 컬럼 제외 - return [ - selectColumn, - rfqCodeColumn, - rfqTypeColumn, - responseStatusColumn, - projectNameColumn, - descriptionColumn, - dueDateColumn, - itemsColumn, - attachmentsColumn, - commentsColumn, - updatedAtColumn, - actionsColumn, - ] -}
\ No newline at end of file diff --git a/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-toolbar-actions.tsx b/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-toolbar-actions.tsx deleted file mode 100644 index 1bae99ef..00000000 --- a/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-toolbar-actions.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client" - -import * as React from "react" -import { type Table } from "@tanstack/react-table" -import { Download, Upload } from "lucide-react" -import { toast } from "sonner" - -import { exportTableToExcel } from "@/lib/export" -import { Button } from "@/components/ui/button" -import { RfqWithAll } from "../types" - - -interface RfqsTableToolbarActionsProps { - table: Table<RfqWithAll> -} - -export function RfqsVendorTableToolbarActions({ table }: RfqsTableToolbarActionsProps) { - - - return ( - <div className="flex items-center gap-2"> - - {/** 4) Export 버튼 */} - <Button - variant="outline" - size="sm" - onClick={() => - exportTableToExcel(table, { - filename: "tasks", - excludeColumns: ["select", "actions"], - }) - } - className="gap-2" - > - <Download className="size-4" aria-hidden="true" /> - <span className="hidden sm:inline">Export</span> - </Button> - </div> - ) -}
\ No newline at end of file diff --git a/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table.tsx b/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table.tsx deleted file mode 100644 index 6aab7fef..00000000 --- a/lib/vendor-rfq-response/vendor-rfq-table/rfqs-table.tsx +++ /dev/null @@ -1,280 +0,0 @@ -"use client" - -import * as React from "react" -import type { - DataTableAdvancedFilterField, - DataTableFilterField, - DataTableRowAction, -} from "@/types/table" -import { useRouter } from "next/navigation" - -import { useDataTable } from "@/hooks/use-data-table" -import { DataTable } from "@/components/data-table/data-table" -import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" - -import { useFeatureFlags } from "./feature-flags-provider" -import { getColumns } from "./rfqs-table-columns" -import { RfqWithAll } from "../types" - -import { - fetchRfqAttachments, - fetchRfqAttachmentsbyCommentId, -} from "../../rfqs/service" - -import { RfqsVendorTableToolbarActions } from "./rfqs-table-toolbar-actions" -import { RfqsItemsDialog } from "./ItemsDialog" -import { RfqAttachmentsSheet } from "./attachment-rfq-sheet" -import { CommentSheet } from "./comments-sheet" -import { getRfqResponsesForVendor } from "../service" -import { useSession } from "next-auth/react" // Next-auth session hook 추가 - -interface RfqsTableProps { - promises: Promise<[Awaited<ReturnType<typeof getRfqResponsesForVendor>>]> -} - -// 코멘트+첨부파일 구조 예시 -export interface RfqCommentWithAttachments { - id: number - commentText: string - commentedBy?: number - commentedByEmail?: string - createdAt?: Date - attachments?: { - id: number - fileName: string - filePath: string - }[] -} - -export interface ExistingAttachment { - id: number - fileName: string - filePath: string - createdAt?: Date - vendorId?: number | null - size?: number -} - -export interface ExistingItem { - id?: number - itemCode: string - description: string | null - quantity: number | null - uom: string | null -} - -export function RfqsVendorTable({ promises }: RfqsTableProps) { - const { featureFlags } = useFeatureFlags() - const { data: session } = useSession() // 세션 정보 가져오기 - - // 1) 테이블 데이터( RFQs ) - const [{ data: responseData, pageCount }] = React.use(promises) - - // 데이터를 RfqWithAll 타입으로 변환 (id 필드 추가) - const data: RfqWithAll[] = React.useMemo(() => { - return responseData.map(item => ({ - ...item, - id: item.rfqId, // id 필드를 rfqId와 동일하게 설정 - })); - }, [responseData]); - - const router = useRouter() - - // 2) 첨부파일 시트 + 관련 상태 - const [attachmentsOpen, setAttachmentsOpen] = React.useState(false) - const [selectedRfqIdForAttachments, setSelectedRfqIdForAttachments] = React.useState<number | null>(null) - const [attachDefault, setAttachDefault] = React.useState<ExistingAttachment[]>([]) - - // 3) 코멘트 시트 + 관련 상태 - const [initialComments, setInitialComments] = React.useState<RfqCommentWithAttachments[]>([]) - const [commentSheetOpen, setCommentSheetOpen] = React.useState(false) - const [selectedRfqIdForComments, setSelectedRfqIdForComments] = React.useState<number | null>(null) - - // 4) rowAction으로 다양한 모달/시트 열기 - const [rowAction, setRowAction] = React.useState<DataTableRowAction<RfqWithAll> | null>(null) - - // 열리고 닫힐 때마다, rowAction 등을 확인해서 시트 열기/닫기 처리 - React.useEffect(() => { - if (rowAction?.type === "comments" && rowAction?.row.original) { - openCommentSheet(rowAction.row.original.id) - } - }, [rowAction]) - - /** - * (A) 코멘트 시트를 열기 전에, - * DB에서 (rfqId에 해당하는) 코멘트들 + 각 코멘트별 첨부파일을 조회. - */ - const openCommentSheet = React.useCallback(async (rfqId: number) => { - setInitialComments([]) - - // 여기서 rowAction을 직접 참조하지 않고, 필요한 데이터만 파라미터로 받기 - const comments = data.find(rfq => rfq.rfqId === rfqId)?.comments || [] - - if (comments && comments.length > 0) { - const commentWithAttachments = await Promise.all( - comments.map(async (c) => { - const attachments = await fetchRfqAttachmentsbyCommentId(c.id) - return { - ...c, - commentedBy: c.commentedBy || 1, - attachments, - } - }) - ) - - setInitialComments(commentWithAttachments) - } - - setSelectedRfqIdForComments(rfqId) - setCommentSheetOpen(true) - }, [data]) // data만 의존성으로 추가 - - /** - * (B) 첨부파일 시트 열기 - */ - const openAttachmentsSheet = React.useCallback(async (rfqId: number) => { - const list = await fetchRfqAttachments(rfqId) - setAttachDefault(list) - setSelectedRfqIdForAttachments(rfqId) - setAttachmentsOpen(true) - }, []) - - // 5) DataTable 컬럼 세팅 - const columns = React.useMemo( - () => - getColumns({ - setRowAction, - router, - openAttachmentsSheet, - openCommentSheet - }), - [setRowAction, router, openAttachmentsSheet, openCommentSheet] - ) - - /** - * 간단한 filterFields 예시 - */ - const filterFields: DataTableFilterField<RfqWithAll>[] = [ - { - id: "rfqCode", - label: "RFQ Code", - placeholder: "Filter RFQ Code...", - }, - { - id: "projectName", - label: "Project", - placeholder: "Filter Project...", - }, - { - id: "rfqDescription", - label: "Description", - placeholder: "Filter Description...", - }, - ] - - /** - * Advanced filter fields 예시 - */ - const advancedFilterFields: DataTableAdvancedFilterField<RfqWithAll>[] = [ - { - id: "rfqCode", - label: "RFQ Code", - type: "text", - }, - { - id: "rfqDescription", - label: "Description", - type: "text", - }, - { - id: "projectCode", - label: "Project Code", - type: "text", - }, - { - id: "projectName", - label: "Project Name", - type: "text", - }, - { - id: "rfqDueDate", - label: "Due Date", - type: "date", - }, - { - id: "responseStatus", - label: "Response Status", - type: "select", - options: [ - { label: "Reviewing", value: "REVIEWING" }, - { label: "Accepted", value: "ACCEPTED" }, - { label: "Declined", value: "DECLINED" }, - ], - } - ] - - // useDataTable() 훅 -> pagination, sorting 등 관리 - const { table } = useDataTable({ - data, - columns, - pageCount, - filterFields, - enablePinning: true, - enableAdvancedFilter: true, - initialState: { - sorting: [{ id: "respondedAt", desc: true }], - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow) => String(originalRow.id), - shallow: false, - clearOnDefault: true, - }) - - const currentUserId = session?.user?.id ? parseInt(session.user.id, 10) : 0 - const currentVendorId = session?.user?.id ? session.user.companyId : 0 - - - - return ( - <> - <DataTable table={table}> - <DataTableAdvancedToolbar - table={table} - filterFields={advancedFilterFields} - shallow={false} - > - <RfqsVendorTableToolbarActions table={table} /> - </DataTableAdvancedToolbar> - </DataTable> - - {/* 1) 아이템 목록 Dialog */} - {rowAction?.type === "items" && rowAction?.row.original && ( - <RfqsItemsDialog - open={true} - onOpenChange={() => setRowAction(null)} - rfq={rowAction.row.original} - /> - )} - - {/* 2) 코멘트 시트 */} - {selectedRfqIdForComments && ( - <CommentSheet - open={commentSheetOpen} - onOpenChange={setCommentSheetOpen} - initialComments={initialComments} - rfqId={selectedRfqIdForComments} - vendorId={currentVendorId??0} - currentUserId={currentUserId} - /> - )} - - {/* 3) 첨부파일 시트 */} - <RfqAttachmentsSheet - open={attachmentsOpen} - onOpenChange={setAttachmentsOpen} - rfqId={selectedRfqIdForAttachments ?? 0} - attachments={attachDefault} - /> - </> - ) -}
\ No newline at end of file |
