summaryrefslogtreecommitdiff
path: root/lib/vendor-rfq-response/vendor-rfq-table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-rfq-response/vendor-rfq-table')
-rw-r--r--lib/vendor-rfq-response/vendor-rfq-table/ItemsDialog.tsx125
-rw-r--r--lib/vendor-rfq-response/vendor-rfq-table/attachment-rfq-sheet.tsx106
-rw-r--r--lib/vendor-rfq-response/vendor-rfq-table/comments-sheet.tsx320
-rw-r--r--lib/vendor-rfq-response/vendor-rfq-table/feature-flags-provider.tsx108
-rw-r--r--lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-columns.tsx435
-rw-r--r--lib/vendor-rfq-response/vendor-rfq-table/rfqs-table-toolbar-actions.tsx40
-rw-r--r--lib/vendor-rfq-response/vendor-rfq-table/rfqs-table.tsx280
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