summaryrefslogtreecommitdiff
path: root/lib/tech-vendor-rfq-response/vendor-cbe-table/cbe-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendor-rfq-response/vendor-cbe-table/cbe-table.tsx')
-rw-r--r--lib/tech-vendor-rfq-response/vendor-cbe-table/cbe-table.tsx272
1 files changed, 272 insertions, 0 deletions
diff --git a/lib/tech-vendor-rfq-response/vendor-cbe-table/cbe-table.tsx b/lib/tech-vendor-rfq-response/vendor-cbe-table/cbe-table.tsx
new file mode 100644
index 00000000..94e29a95
--- /dev/null
+++ b/lib/tech-vendor-rfq-response/vendor-cbe-table/cbe-table.tsx
@@ -0,0 +1,272 @@
+"use client"
+
+import * as React from "react"
+import { useRouter } from "next/navigation"
+import type {
+ DataTableAdvancedFilterField,
+ DataTableFilterField,
+ DataTableRowAction,
+} from "@/types/table"
+
+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 { getColumns } from "./cbe-table-columns"
+import {
+ fetchRfqAttachmentsbyCommentId,
+ getCBEbyVendorId,
+ getFileFromRfqAttachmentsbyid,
+ fetchCbeFiles
+} from "../../rfqs-tech/service"
+import { useSession } from "next-auth/react"
+import { CbeComment, CommentSheet } from "./comments-sheet"
+import { VendorWithCbeFields } from "@/config/vendorCbeColumnsConfig"
+import { toast } from "sonner"
+import { RfqDeailDialog } from "./rfq-detail-dialog"
+import { CommercialResponseSheet } from "./respond-cbe-sheet"
+
+interface VendorsTableProps {
+ promises: Promise<
+ [
+ Awaited<ReturnType<typeof getCBEbyVendorId>>,
+ ]
+ >
+}
+
+export function CbeVendorTable({ promises }: VendorsTableProps) {
+ const { data: session } = useSession()
+ const userVendorId = session?.user?.companyId
+ const userId = Number(session?.user?.id)
+ // Suspense로 받아온 데이터
+ const [{ data, pageCount }] = React.use(promises)
+ const [rowAction, setRowAction] = React.useState<DataTableRowAction<VendorWithCbeFields> | null>(null)
+ const [selectedCbeId, setSelectedCbeId] = React.useState<number | null>(null)
+
+ // 개별 협력업체별 로딩 상태를 관리하는 맵
+ const [loadingVendors, setLoadingVendors] = React.useState<Record<string, boolean>>({})
+
+ const router = useRouter()
+
+ // 코멘트 관련 상태
+ const [initialComments, setInitialComments] = React.useState<CbeComment[]>([])
+ const [commentSheetOpen, setCommentSheetOpen] = React.useState(false)
+ const [selectedRfqIdForComments, setSelectedRfqIdForComments] = React.useState<number | null>(null)
+
+ // 상업 응답 관련 상태
+ const [commercialResponseSheetOpen, setCommercialResponseSheetOpen] = React.useState(false)
+ const [selectedResponseId, setSelectedResponseId] = React.useState<number | null>(null)
+ const [selectedRfq, setSelectedRfq] = React.useState<VendorWithCbeFields | null>(null)
+
+ // RFQ 상세 관련 상태
+ const [rfqDetailDialogOpen, setRfqDetailDialogOpen] = React.useState(false)
+ const [selectedRfqId, setSelectedRfqId] = React.useState<number | null>(null)
+ const [selectedRfqDetail, setSelectedRfqDetail] = React.useState<VendorWithCbeFields | null>(null)
+
+ React.useEffect(() => {
+ if (rowAction?.type === "comments") {
+ // rowAction가 새로 세팅된 뒤 여기서 openCommentSheet 실행
+ openCommentSheet(Number(rowAction.row.original.responseId))
+ }
+ }, [rowAction])
+
+ async function openCommentSheet(responseId: number) {
+ setInitialComments([])
+
+ const comments = rowAction?.row.original.comments
+ const rfqId = rowAction?.row.original.rfqId
+
+ if (comments && comments.length > 0) {
+ const commentWithAttachments: CbeComment[] = await Promise.all(
+ comments.map(async (c) => {
+ // 서버 액션을 사용하여 코멘트 첨부 파일 가져오기
+ const attachments = await fetchRfqAttachmentsbyCommentId(c.id)
+
+ return {
+ ...c,
+ commentedBy: userId, // DB나 API 응답에 있다고 가정
+ attachments,
+ }
+ })
+ )
+
+ setInitialComments(commentWithAttachments)
+ }
+
+ if(rfqId) {
+ setSelectedRfqIdForComments(rfqId)
+ }
+ setSelectedCbeId(responseId)
+ setCommentSheetOpen(true)
+ }
+
+ // 상업 응답 시트 열기
+ function openCommercialResponseSheet(responseId: number, rfq: VendorWithCbeFields) {
+ setSelectedResponseId(responseId)
+ setSelectedRfq(rfq)
+ setCommercialResponseSheetOpen(true)
+ }
+
+ // RFQ 상세 대화상자 열기
+ function openRfqDetailDialog(rfqId: number, rfq: VendorWithCbeFields) {
+ setSelectedRfqId(rfqId)
+ setSelectedRfqDetail(rfq)
+ setRfqDetailDialogOpen(true)
+ }
+
+ const handleDownloadCbeFiles = React.useCallback(
+ async (vendorId: number, rfqId: number) => {
+ // 고유 키 생성: vendorId_rfqId
+ const rowKey = `${vendorId}_${rfqId}`
+
+ // 해당 협력업체의 로딩 상태만 true로 설정
+ setLoadingVendors(prev => ({
+ ...prev,
+ [rowKey]: true
+ }))
+
+ try {
+ const { files, error } = await fetchCbeFiles(vendorId, rfqId);
+ if (error) {
+ toast.error(error);
+ return;
+ }
+ if (files.length === 0) {
+ toast.warning("다운로드할 CBE 파일이 없습니다");
+ return;
+ }
+ // 순차적으로 파일 다운로드
+ for (const file of files) {
+ await downloadFile(file.id);
+ }
+ toast.success(`${files.length}개의 CBE 파일이 다운로드되었습니다`);
+ } catch (error) {
+ toast.error("CBE 파일을 다운로드하는 데 실패했습니다");
+ console.error(error);
+ } finally {
+ // 해당 협력업체의 로딩 상태만 false로 되돌림
+ setLoadingVendors(prev => ({
+ ...prev,
+ [rowKey]: false
+ }))
+ }
+ },
+ []
+ );
+
+ const downloadFile = React.useCallback(async (fileId: number) => {
+ try {
+ const { file, error } = await getFileFromRfqAttachmentsbyid(fileId);
+ if (error || !file) {
+ throw new Error(error || "파일 정보를 가져오는 데 실패했습니다");
+ }
+
+ const link = document.createElement("a");
+ link.href = `/api/rfq-download?path=${encodeURIComponent(file.filePath)}`;
+ link.download = file.fileName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ return true;
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ }, []);
+
+ // 응답 성공 후 데이터 갱신
+ const handleResponseSuccess = React.useCallback(() => {
+ // 필요한 경우 데이터 다시 가져오기
+ router.refresh()
+ }, [router]);
+
+ // getColumns() 호출 시 필요한 핸들러들 주입
+ const columns = React.useMemo(
+ () => getColumns({
+ setRowAction,
+ router,
+ openCommentSheet,
+ handleDownloadCbeFiles,
+ loadingVendors,
+ openVendorContactsDialog: openRfqDetailDialog,
+ openCommercialResponseSheet,
+ }),
+ [
+ setRowAction,
+ router,
+ openCommentSheet,
+ handleDownloadCbeFiles,
+ loadingVendors,
+ openRfqDetailDialog,
+ openCommercialResponseSheet
+ ]
+ );
+
+ // 필터 필드 정의
+ const filterFields: DataTableFilterField<VendorWithCbeFields>[] = []
+ const advancedFilterFields: DataTableAdvancedFilterField<VendorWithCbeFields>[] = [
+
+ ]
+
+ const { table } = useDataTable({
+ data,
+ columns,
+ pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "respondedAt", desc: true }],
+ columnPinning: { right: ["respond", "comments"] }, // respond 컬럼을 오른쪽에 고정
+ },
+ getRowId: (originalRow) => String(originalRow.responseId),
+ shallow: false,
+ clearOnDefault: true,
+ })
+
+ return (
+ <>
+ <DataTable table={table}>
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={advancedFilterFields}
+ shallow={false}
+ />
+ </DataTable>
+
+ {/* 코멘트 시트 */}
+ {commentSheetOpen && selectedRfqIdForComments && selectedCbeId !== null && (
+ <CommentSheet
+ open={commentSheetOpen}
+ onOpenChange={setCommentSheetOpen}
+ rfqId={selectedRfqIdForComments}
+ initialComments={initialComments}
+ vendorId={userVendorId || 0}
+ currentUserId={userId || 0}
+ cbeId={selectedCbeId}
+ />
+ )}
+
+ {/* 상업 응답 시트 */}
+ {commercialResponseSheetOpen && selectedResponseId !== null && selectedRfq && (
+ <CommercialResponseSheet
+ open={commercialResponseSheetOpen}
+ onOpenChange={setCommercialResponseSheetOpen}
+ responseId={selectedResponseId}
+ rfq={selectedRfq}
+ onSuccess={handleResponseSuccess}
+ />
+ )}
+
+ {/* RFQ 상세 대화상자 */}
+ {rfqDetailDialogOpen && selectedRfqId !== null && (
+ <RfqDeailDialog
+ isOpen={rfqDetailDialogOpen}
+ onOpenChange={setRfqDetailDialogOpen}
+ rfqId={selectedRfqId}
+ rfq={selectedRfqDetail}
+ />
+ )}
+ </>
+ )
+} \ No newline at end of file