diff options
Diffstat (limited to 'lib/rfqs')
| -rw-r--r-- | lib/rfqs/cbe-table/cbe-table.tsx | 6 | ||||
| -rw-r--r-- | lib/rfqs/service.ts | 68 | ||||
| -rw-r--r-- | lib/rfqs/table/rfqs-table.tsx | 4 | ||||
| -rw-r--r-- | lib/rfqs/tbe-table/file-dialog.tsx | 2 | ||||
| -rw-r--r-- | lib/rfqs/tbe-table/tbe-table-columns.tsx | 43 | ||||
| -rw-r--r-- | lib/rfqs/tbe-table/tbe-table.tsx | 6 | ||||
| -rw-r--r-- | lib/rfqs/vendor-table/comments-sheet.tsx | 25 | ||||
| -rw-r--r-- | lib/rfqs/vendor-table/vendors-table-columns.tsx | 120 | ||||
| -rw-r--r-- | lib/rfqs/vendor-table/vendors-table.tsx | 77 |
9 files changed, 219 insertions, 132 deletions
diff --git a/lib/rfqs/cbe-table/cbe-table.tsx b/lib/rfqs/cbe-table/cbe-table.tsx index 243b91ed..b2a74466 100644 --- a/lib/rfqs/cbe-table/cbe-table.tsx +++ b/lib/rfqs/cbe-table/cbe-table.tsx @@ -144,8 +144,11 @@ export function CbeTable({ promises, rfqId }: VendorsTableProps) { return ( <> - <DataTable +<div style={{ maxWidth: '80vw' }}> +<DataTable table={table} + // tableContainerClass="sm:max-w-[80vw] md:max-w-[80vw] lg:max-w-[80vw]" + // tableContainerClass="max-w-[80vw]" > <DataTableAdvancedToolbar table={table} @@ -155,6 +158,7 @@ export function CbeTable({ promises, rfqId }: VendorsTableProps) { {/* <VendorsTableToolbarActions table={table} rfqId={rfqId} /> */} </DataTableAdvancedToolbar> </DataTable> + </div> </> ) diff --git a/lib/rfqs/service.ts b/lib/rfqs/service.ts index b1e02cd0..6b8b4738 100644 --- a/lib/rfqs/service.ts +++ b/lib/rfqs/service.ts @@ -24,6 +24,7 @@ import { sendEmail } from "../mail/sendEmail"; import { projects } from "@/db/schema/projects"; import { items } from "@/db/schema/items"; import * as z from "zod" +import { users } from "@/db/schema/users"; interface InviteVendorsInput { @@ -116,7 +117,7 @@ export async function getRfqs(input: GetRfqsSchema) { [JSON.stringify(input)], { revalidate: 3600, - tags: [`rfqs-${input.rfqType}`], + tags: [`rfqs-${input.rfqType}`], } )(); } @@ -597,8 +598,6 @@ export async function getMatchedVendors(input: GetMatchedVendorsSchema, rfqId: n return { data: [], pageCount: 0 } } - console.log(vendorIdList,"vendorIdList") - // ───────────────────────────────────────────────────── // 3) 필터/검색/정렬 // ───────────────────────────────────────────────────── @@ -631,7 +630,9 @@ export async function getMatchedVendors(input: GetMatchedVendorsSchema, rfqId: n // 특정 rfqId(뷰에 담긴 값)도 일치해야 함. const finalWhere = and( inArray(vendorRfqView.vendorId, vendorIdList), - eq(vendorRfqView.rfqId, rfqId), + // 아래 라인은 rfq에 초대된 벤더만 필터링하는 조건으로 추정되지만 + // rfq 를 진행하기 전에도 벤더를 보여줘야 하므로 주석처리하겠습니다 + // eq(vendorRfqView.rfqId, rfqId), advancedWhere, globalWhere ) @@ -670,18 +671,21 @@ export async function getMatchedVendors(input: GetMatchedVendorsSchema, rfqId: n .offset(offset) .limit(limit) - // 총 개수 + // 중복 제거된 데이터 생성 + const distinctData = Array.from( + new Map(data.map(row => [row.id, row])).values() + ) + + // 중복 제거된 총 개수 계산 const [{ count }] = await tx - .select({ count: sql<number>`count(*)`.as("count") }) + .select({ count: sql<number>`count(DISTINCT ${vendorRfqView.vendorId})`.as("count") }) .from(vendorRfqView) .where(finalWhere) - return [data, Number(count)] + return [distinctData, Number(count)] }) - console.log(rows) - console.log(total) // ───────────────────────────────────────────────────── // 4-1) 정확한 rfqVendorStatus와 rfqVendorUpdated 조회 // ───────────────────────────────────────────────────── @@ -732,11 +736,36 @@ export async function getMatchedVendors(input: GetMatchedVendorsSchema, rfqId: n ) const commByVendorId = new Map<number, any[]>() + // 먼저 모든 사용자 ID를 수집 + const userIds = new Set(commAll.map(c => c.commentedBy)); + const userIdsArray = Array.from(userIds); + + // Drizzle의 select 메서드를 사용하여 사용자 정보를 가져옴 + const usersData = await db + .select({ + id: users.id, + email: users.email, + }) + .from(users) + .where(inArray(users.id, userIdsArray)); + + // 사용자 ID를 키로 하는 맵 생성 + const userMap = new Map(); + for (const user of usersData) { + userMap.set(user.id, user); + } + + // 댓글 정보를 벤더 ID별로 그룹화하고, 사용자 이메일 추가 for (const c of commAll) { const vid = c.vendorId! if (!commByVendorId.has(vid)) { commByVendorId.set(vid, []) } + + // 사용자 정보 가져오기 + const user = userMap.get(c.commentedBy); + const userEmail = user ? user.email : 'unknown@example.com'; // 사용자를 찾지 못한 경우 기본값 설정 + commByVendorId.get(vid)!.push({ id: c.id, commentText: c.commentText, @@ -744,9 +773,9 @@ export async function getMatchedVendors(input: GetMatchedVendorsSchema, rfqId: n evaluationId: c.evaluationId, createdAt: c.createdAt, commentedBy: c.commentedBy, + commentedByEmail: userEmail, // 이메일 추가 }) } - // ───────────────────────────────────────────────────── // 6) rows에 comments 병합 // ───────────────────────────────────────────────────── @@ -1623,9 +1652,10 @@ export async function createRfqCommentWithAttachments(params: { commentText: string commentedBy: number evaluationId?: number | null + cbeId?: number | null files?: File[] }) { - const { rfqId, vendorId, commentText, commentedBy, evaluationId, files } = params + const { rfqId, vendorId, commentText, commentedBy, evaluationId,cbeId, files } = params // 1) 새로운 코멘트 생성 @@ -1637,6 +1667,7 @@ export async function createRfqCommentWithAttachments(params: { commentText, commentedBy, evaluationId: evaluationId || null, + cbeId: cbeId || null, }) .returning({ id: rfqComments.id, createdAt: rfqComments.createdAt }) // id만 반환하도록 @@ -1644,7 +1675,7 @@ export async function createRfqCommentWithAttachments(params: { throw new Error("Failed to create comment") } - // 2) 첨부파일 처리 (S3 업로드 등은 프로젝트 상황에 따라) + // 2) 첨부파일 처리 if (files && files.length > 0) { const rfqDir = path.join(process.cwd(), "public", "rfq", String(rfqId)); @@ -1669,6 +1700,7 @@ export async function createRfqCommentWithAttachments(params: { rfqId, vendorId: vendorId || null, evaluationId: evaluationId || null, + cbeId: cbeId || null, commentId: insertedComment.id, // 새 코멘트와 연결 fileName: file.name, filePath: "/" + relativePath.replace(/\\/g, "/"), @@ -2552,10 +2584,10 @@ export async function getCBE(input: GetCBESchema, rfqId: number) { // [6] 정렬 const orderBy = input.sort?.length ? input.sort.map((s) => { - // vendor_cbe_view 컬럼 중 정렬 대상이 되는 것만 매핑 - const col = (vendorCbeView as any)[s.id]; - return s.desc ? desc(col) : asc(col); - }) + // vendor_cbe_view 컬럼 중 정렬 대상이 되는 것만 매핑 + const col = (vendorCbeView as any)[s.id]; + return s.desc ? desc(col) : asc(col); + }) : [asc(vendorCbeView.vendorId)]; // [7] 메인 SELECT @@ -2690,7 +2722,7 @@ export async function getCBE(input: GetCBESchema, rfqId: number) { // Step 2: responseIds const allResponseIds = responsesAll.map((r) => r.id); - + const commercialResponsesAll = await db .select({ id: vendorCommercialResponses.id, @@ -2708,7 +2740,7 @@ export async function getCBE(input: GetCBESchema, rfqId: number) { } const allCommercialResponseIds = commercialResponsesAll.map((cr) => cr.id); - + // 여기서는 예시로 TBE와 마찬가지로 vendorResponseAttachments를 // 직접 responseId로 관리한다고 가정(혹은 commercialResponseId로 연결) diff --git a/lib/rfqs/table/rfqs-table.tsx b/lib/rfqs/table/rfqs-table.tsx index db5c31e7..48c04930 100644 --- a/lib/rfqs/table/rfqs-table.tsx +++ b/lib/rfqs/table/rfqs-table.tsx @@ -213,7 +213,7 @@ export function RfqsTable({ promises, rfqType = RfqType.PURCHASE }: RfqsTablePro }) return ( - <> + <div style={{ maxWidth: '100vw' }}> <DataTable table={table} floatingBar={<RfqsTableFloatingBar table={table} />} @@ -259,6 +259,6 @@ export function RfqsTable({ promises, rfqType = RfqType.PURCHASE }: RfqsTablePro rfq={selectedRfq ?? null} onAttachmentsUpdated={handleAttachmentsUpdated} /> - </> + </div> ) }
\ No newline at end of file diff --git a/lib/rfqs/tbe-table/file-dialog.tsx b/lib/rfqs/tbe-table/file-dialog.tsx index 1d1a65ea..772eb930 100644 --- a/lib/rfqs/tbe-table/file-dialog.tsx +++ b/lib/rfqs/tbe-table/file-dialog.tsx @@ -76,7 +76,7 @@ export function TBEFileDialog({ // Download submitted file const downloadSubmittedFile = async (file: any) => { try { - const response = await fetch(`/api/file/${file.id}/download`) + const response = await fetch(`/api/tbe-download?path=${encodeURIComponent(file.filePath)}`) if (!response.ok) { throw new Error("Failed to download file") } diff --git a/lib/rfqs/tbe-table/tbe-table-columns.tsx b/lib/rfqs/tbe-table/tbe-table-columns.tsx index 29fbd5cd..0e9b7064 100644 --- a/lib/rfqs/tbe-table/tbe-table-columns.tsx +++ b/lib/rfqs/tbe-table/tbe-table-columns.tsx @@ -178,37 +178,30 @@ const commentsColumn: ColumnDef<VendorWithTbeFields> = { openCommentSheet(vendor.tbeId ?? 0) } - return ( - <div className="flex items-center justify-center"> + return ( <Button variant="ghost" size="sm" - className="h-8 w-8 p-0 group relative" + className="relative h-8 w-8 p-0 group" onClick={handleClick} - aria-label={commCount > 0 ? `View ${commCount} comments` : "Add comment"} + aria-label={ + commCount > 0 ? `View ${commCount} comments` : "No comments" + } > - <div className="flex items-center justify-center relative"> - {commCount > 0 ? ( - <> - <MessageSquare className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> - <Badge - variant="secondary" - className="absolute -top-2 -right-2 h-4 min-w-4 text-xs px-1 flex items-center justify-center" - > - {commCount} - </Badge> - </> - ) : ( - <MessageSquare className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> - )} - </div> - <span className="sr-only">{commCount > 0 ? `${commCount} Comments` : "Add Comment"}</span> + <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> - {/* <span className="ml-2 text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer" onClick={handleClick}> - {commCount > 0 ? `${commCount} Comments` : "Add Comment"} - </span> */} - </div> - ) + ) }, enableSorting: false, maxSize:80 diff --git a/lib/rfqs/tbe-table/tbe-table.tsx b/lib/rfqs/tbe-table/tbe-table.tsx index c385ca0b..41eff0dc 100644 --- a/lib/rfqs/tbe-table/tbe-table.tsx +++ b/lib/rfqs/tbe-table/tbe-table.tsx @@ -149,10 +149,10 @@ export function TbeTable({ promises, rfqId }: VendorsTableProps) { }) return ( - <> +<div style={{ maxWidth: '80vw' }}> <DataTable table={table} - > + > <DataTableAdvancedToolbar table={table} filterFields={advancedFilterFields} @@ -185,6 +185,6 @@ export function TbeTable({ promises, rfqId }: VendorsTableProps) { rfqId={rfqId} // Use the prop directly instead of data[0]?.rfqId onRefresh={handleRefresh} /> - </> + </div> ) }
\ No newline at end of file diff --git a/lib/rfqs/vendor-table/comments-sheet.tsx b/lib/rfqs/vendor-table/comments-sheet.tsx index 644869c6..3a2a9353 100644 --- a/lib/rfqs/vendor-table/comments-sheet.tsx +++ b/lib/rfqs/vendor-table/comments-sheet.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { useForm, useFieldArray } from "react-hook-form" import { z } from "zod" import { zodResolver } from "@hookform/resolvers/zod" -import { Loader, Download, X } from "lucide-react" +import { Download, X, Loader2 } from "lucide-react" import prettyBytes from "pretty-bytes" import { toast } from "sonner" @@ -52,6 +52,7 @@ export interface MatchedVendorComment { id: number commentText: string commentedBy?: number + commentedByEmail?: string createdAt?: Date attachments?: { id: number @@ -67,6 +68,7 @@ interface CommentSheetProps extends React.ComponentPropsWithRef<typeof Sheet> { rfqId: number vendorId: number onCommentsUpdated?: (comments: MatchedVendorComment[]) => void + isLoading?: boolean // New prop } // 2) 폼 스키마 @@ -84,8 +86,12 @@ export function CommentSheet({ 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() @@ -108,6 +114,16 @@ export function CommentSheet({ // (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> } @@ -134,7 +150,7 @@ export function CommentSheet({ {c.attachments.map((att) => ( <div key={att.id} className="flex items-center gap-2"> <a - href={att.filePath} + href={`/api/rfq-download?path=${encodeURIComponent(att.filePath)}`} download target="_blank" rel="noreferrer" @@ -149,7 +165,7 @@ export function CommentSheet({ )} </TableCell> <TableCell> { c.createdAt ? formatDate(c.createdAt): "-"}</TableCell> - <TableCell>{c.commentedBy ?? "-"}</TableCell> + <TableCell>{c.commentedByEmail ?? "-"}</TableCell> </TableRow> ))} </TableBody> @@ -173,6 +189,7 @@ export function CommentSheet({ commentText: data.commentText, commentedBy: currentUserId, evaluationId: null, + cbeId: null, files: data.newFiles, }) @@ -291,7 +308,7 @@ export function CommentSheet({ </Button> </SheetClose> <Button disabled={isPending}> - {isPending && <Loader className="mr-2 h-4 w-4 animate-spin" />} + {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} Save </Button> </SheetFooter> diff --git a/lib/rfqs/vendor-table/vendors-table-columns.tsx b/lib/rfqs/vendor-table/vendors-table-columns.tsx index 1220cb9d..f152cec5 100644 --- a/lib/rfqs/vendor-table/vendors-table-columns.tsx +++ b/lib/rfqs/vendor-table/vendors-table-columns.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { type DataTableRowAction } from "@/types/table" import { type ColumnDef } from "@tanstack/react-table" -import { Ellipsis } from "lucide-react" +import { Ellipsis, MessageSquare } from "lucide-react" import { toast } from "sonner" import { getErrorMessage } from "@/lib/handle-error" @@ -78,10 +78,6 @@ export function getColumns({ setRowAction, router, openCommentSheet }: GetColumn } // ---------------------------------------------------------------- - // 2) actions 컬럼 (Dropdown 메뉴) - // ---------------------------------------------------------------- - - // ---------------------------------------------------------------- // 3) 일반 컬럼들을 "그룹"별로 묶어 중첩 columns 생성 // ---------------------------------------------------------------- // 3-1) groupMap: { [groupName]: ColumnDef<MatchedVendorRow>[] } @@ -149,19 +145,76 @@ export function getColumns({ setRowAction, router, openCommentSheet }: GetColumn groupMap[groupName].push(childCol) }) + const commentsColumn: ColumnDef<MatchedVendorRow> = { + id: "comments", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Comments" /> + ), + cell: ({ row }) => { + const vendor = row.original + const commCount = vendor.comments?.length ?? 0 + + function handleClick() { + // rowAction + openCommentSheet + setRowAction({ row, type: "comments" }) + openCommentSheet(Number(vendor.id) ?? 0) + } + + 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 + } + const actionsColumn: ColumnDef<MatchedVendorRow> = { id: "actions", - // header: "Actions", cell: ({ row }) => { const rfq = row.original - const commCount = rfq.comments?.length ?? 0 const status = row.original.rfqVendorStatus + const isDisabled = !status || status === 'INVITED' || status === 'ACCEPTED' - // 공통 코멘트 핸들러 - function handleCommentClick() { - setRowAction({ row, type: "comments" }) - openCommentSheet(Number(row.original.id)) + if (isDisabled) { + return ( + <div className="relative group"> + <Button + aria-label="Actions disabled" + variant="ghost" + className="flex size-8 p-0 opacity-50 cursor-not-allowed" + disabled + > + <Ellipsis className="size-4" aria-hidden="true" /> + </Button> + {/* Tooltip explaining why it's disabled */} + <div className="absolute hidden group-hover:block right-0 -bottom-8 bg-popover text-popover-foreground text-xs p-2 rounded shadow-md whitespace-nowrap z-50"> + 초대 상태에서는 사용할 수 없습니다 + </div> + </div> + ) } + return ( <DropdownMenu> @@ -175,21 +228,12 @@ export function getColumns({ setRowAction, router, openCommentSheet }: GetColumn </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end" className="w-40"> - {/* 기존 기능: status가 INVITED일 때만 표시 */} {(!status || status === 'INVITED') && ( <DropdownMenuItem onSelect={() => setRowAction({ row, type: "invite" })}> 발행하기 </DropdownMenuItem> )} - {/* 두 기능 사이 구분선 */} - <DropdownMenuSeparator /> - {/* 코멘트 메뉴 항목 */} - <DropdownMenuItem onSelect={handleCommentClick}> - {commCount > 0 ? `${commCount} Comments` : "Add Comment"} - </DropdownMenuItem> - - </DropdownMenuContent> </DropdownMenu> ) @@ -199,37 +243,6 @@ export function getColumns({ setRowAction, router, openCommentSheet }: GetColumn enableHiding: false, } - - // const commentsColumn: ColumnDef<MatchedVendorRow> = { - // id: "comments", - // header: "Comments", - // cell: ({ row }) => { - // const rfq = row.original - // const commCount = rfq.comments?.length ?? 0 - - // // 공통 클릭 핸들러 - // function handleClick() { - // setRowAction({ row, type: "comments" }) - // openCommentSheet(Number(row.original.id)) - // } - - // return commCount > 0 ? ( - // <a - // href="#" - // onClick={(e) => { - // e.preventDefault() - // handleClick() - // }} - // > - // {commCount} Comments - // </a> - // ) : ( - // <Button size="sm" variant="outline" onClick={handleClick}> - // Add Comment - // </Button> - // ) - // }, - // } // ---------------------------------------------------------------- // 3-2) groupMap에서 실제 상위 컬럼(그룹)을 만들기 // ---------------------------------------------------------------- @@ -252,13 +265,12 @@ export function getColumns({ setRowAction, router, openCommentSheet }: GetColumn }) // ---------------------------------------------------------------- - // 4) 최종 컬럼 배열: select, nestedColumns, actions + // 4) 최종 컬럼 배열: select, nestedColumns, comments, actions // ---------------------------------------------------------------- return [ selectColumn, ...nestedColumns, - // commentsColumn, + commentsColumn, actionsColumn - ] }
\ No newline at end of file diff --git a/lib/rfqs/vendor-table/vendors-table.tsx b/lib/rfqs/vendor-table/vendors-table.tsx index 838342bf..ae9cba41 100644 --- a/lib/rfqs/vendor-table/vendors-table.tsx +++ b/lib/rfqs/vendor-table/vendors-table.tsx @@ -2,6 +2,7 @@ import * as React from "react" import { useRouter } from "next/navigation" +import { useSession } from "next-auth/react" // Next-auth session hook 추가 import type { DataTableAdvancedFilterField, DataTableFilterField, @@ -22,6 +23,7 @@ import { InviteVendorsDialog } from "./invite-vendors-dialog" import { CommentSheet, MatchedVendorComment } from "./comments-sheet" import { MatchedVendorRow } from "@/config/vendorRfbColumnsConfig" import { RfqType } from "@/lib/rfqs/validations" +import { toast } from "sonner" interface VendorsTableProps { promises: Promise<[Awaited<ReturnType<typeof getMatchedVendors>>]> @@ -29,8 +31,11 @@ interface VendorsTableProps { rfqType: RfqType } -export function MatchedVendorsTable({ promises, rfqId, rfqType}: VendorsTableProps) { +export function MatchedVendorsTable({ promises, rfqId, rfqType }: VendorsTableProps) { const { featureFlags } = useFeatureFlags() + const { data: session } = useSession() // 세션 정보 가져오기 + + // 1) Suspense로 받아온 데이터 const [{ data, pageCount }] = React.use(promises) @@ -47,10 +52,13 @@ export function MatchedVendorsTable({ promises, rfqId, rfqType}: VendorsTablePr const router = useRouter() // 3) CommentSheet 에 넣을 상태 - // => “댓글”은 MatchedVendorComment[] 로 관리해야 함 + // => "댓글"은 MatchedVendorComment[] 로 관리해야 함 const [initialComments, setInitialComments] = React.useState< MatchedVendorComment[] >([]) + + const [isLoadingComments, setIsLoadingComments] = React.useState(false) + const [commentSheetOpen, setCommentSheetOpen] = React.useState(false) const [selectedVendorIdForComments, setSelectedVendorIdForComments] = React.useState<number | null>(null) @@ -64,29 +72,42 @@ export function MatchedVendorsTable({ promises, rfqId, rfqType}: VendorsTablePr // 5) 댓글 시트 오픈 함수 async function openCommentSheet(vendorId: number) { + // Clear previous comments setInitialComments([]) - + + // Start loading + setIsLoadingComments(true) + + // Open the sheet immediately with loading state + setSelectedVendorIdForComments(vendorId) + setCommentSheetOpen(true) + // (a) 현재 Row의 comments 불러옴 const comments = rowAction?.row.original.comments - if (comments && comments.length > 0) { - // (b) 각 comment마다 첨부파일 fetch - const commentWithAttachments: MatchedVendorComment[] = await Promise.all( - comments.map(async (c) => { - const attachments = await fetchRfqAttachmentsbyCommentId(c.id) - return { - ...c, - attachments, - } - }) - ) - setInitialComments(commentWithAttachments) + + try { + if (comments && comments.length > 0) { + // (b) 각 comment마다 첨부파일 fetch + const commentWithAttachments: MatchedVendorComment[] = await Promise.all( + comments.map(async (c) => { + const attachments = await fetchRfqAttachmentsbyCommentId(c.id) + return { + ...c, + attachments, + } + }) + ) + setInitialComments(commentWithAttachments) + } + } catch (error) { + console.error("Error loading comments:", error) + toast.error("Failed to load comments") + } finally { + // End loading regardless of success/failure + setIsLoadingComments(false) } - - // (c) vendorId state - setSelectedVendorIdForComments(vendorId) - setCommentSheetOpen(true) } - + // 6) 컬럼 정의 (memo) const columns = React.useMemo( () => getColumns({ setRowAction, router, openCommentSheet }), @@ -140,9 +161,16 @@ export function MatchedVendorsTable({ promises, rfqId, rfqType}: VendorsTablePr clearOnDefault: true, }) + // 세션에서 userId 추출하고 숫자로 변환 + const currentUserId = session?.user?.id ? parseInt(session.user.id, 10) : 0 + + console.log(currentUserId,"currentUserId") + return ( - <> - <DataTable table={table}> + <div style={{ maxWidth: '80vw' }}> + <DataTable + table={table} + > <DataTableAdvancedToolbar table={table} filterFields={advancedFilterFields} @@ -169,13 +197,14 @@ export function MatchedVendorsTable({ promises, rfqId, rfqType}: VendorsTablePr initialComments={initialComments} rfqId={rfqId} vendorId={selectedVendorIdForComments ?? 0} - currentUserId={1} + currentUserId={currentUserId} + isLoading={isLoadingComments} // Pass the loading state onCommentsUpdated={(updatedComments) => { // Row 의 comments 필드도 업데이트 if (!rowAction?.row) return rowAction.row.original.comments = updatedComments }} /> - </> + </div> ) }
\ No newline at end of file |
