summaryrefslogtreecommitdiff
path: root/lib/techsales-rfq/vendor-response/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/techsales-rfq/vendor-response/table')
-rw-r--r--lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx55
-rw-r--r--lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx72
2 files changed, 122 insertions, 5 deletions
diff --git a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
index 5c6971cc..109698ea 100644
--- a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
+++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table-columns.tsx
@@ -2,7 +2,7 @@
import * as React from "react"
import { type ColumnDef } from "@tanstack/react-table"
-import { Edit } from "lucide-react"
+import { Edit, Paperclip } from "lucide-react"
import { formatCurrency, formatDate, formatDateTime } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
@@ -31,13 +31,15 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations {
quotationVersion?: number | null;
rejectionReason?: string | null;
acceptedAt?: Date | null;
+ attachmentCount?: number;
}
interface GetColumnsProps {
router: AppRouterInstance;
+ openAttachmentsSheet: (rfqId: number) => void;
}
-export function getColumns({ router }: GetColumnsProps): ColumnDef<QuotationWithRfqCode>[] {
+export function getColumns({ router, openAttachmentsSheet }: GetColumnsProps): ColumnDef<QuotationWithRfqCode>[] {
return [
{
id: "select",
@@ -163,6 +165,55 @@ export function getColumns({ router }: GetColumnsProps): ColumnDef<QuotationWith
enableHiding: true,
},
{
+ id: "attachments",
+ header: ({ column }) => (
+ <DataTableColumnHeader column={column} title="첨부파일" />
+ ),
+ cell: ({ row }) => {
+ const quotation = row.original
+ const attachmentCount = quotation.attachmentCount || 0
+
+ const handleClick = () => {
+ openAttachmentsSheet(quotation.rfqId)
+ }
+
+ return (
+ <div className="w-20">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="relative h-8 w-8 p-0 group"
+ onClick={handleClick}
+ aria-label={
+ attachmentCount > 0 ? `View ${attachmentCount} attachments` : "No attachments"
+ }
+ >
+ <Paperclip className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
+ {attachmentCount > 0 && (
+ <span className="pointer-events-none absolute -top-1 -right-1 inline-flex h-4 min-w-[1rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-medium leading-none text-primary-foreground">
+ {attachmentCount}
+ </span>
+ )}
+ <span className="sr-only">
+ {attachmentCount > 0 ? `${attachmentCount} 첨부파일` : "첨부파일 없음"}
+ </span>
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p>{attachmentCount > 0 ? `${attachmentCount}개 첨부파일 보기` : "첨부파일 없음"}</p>
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </div>
+ )
+ },
+ enableSorting: false,
+ enableHiding: true,
+ },
+ {
accessorKey: "status",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="상태" />
diff --git a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx
index 63d4674b..e1b82579 100644
--- a/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx
+++ b/lib/techsales-rfq/vendor-response/table/vendor-quotations-table.tsx
@@ -9,6 +9,9 @@ import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-adv
import { TechSalesVendorQuotations, TECH_SALES_QUOTATION_STATUSES, TECH_SALES_QUOTATION_STATUS_CONFIG } from "@/db/schema"
import { useRouter } from "next/navigation"
import { getColumns } from "./vendor-quotations-table-columns"
+import { TechSalesRfqAttachmentsSheet, ExistingTechSalesAttachment } from "../../table/tech-sales-rfq-attachments-sheet"
+import { getTechSalesRfqAttachments } from "@/lib/techsales-rfq/service"
+import { toast } from "sonner"
interface QuotationWithRfqCode extends TechSalesVendorQuotations {
rfqCode?: string;
@@ -18,13 +21,14 @@ interface QuotationWithRfqCode extends TechSalesVendorQuotations {
itemName?: string;
projNm?: string;
quotationCode?: string | null;
- quotationVersion?: number | null;
+ quotationVersion: number | null;
rejectionReason?: string | null;
acceptedAt?: Date | null;
+ attachmentCount?: number;
}
interface VendorQuotationsTableProps {
- promises: Promise<[{ data: any[], pageCount: number, total?: number }]>;
+ promises: Promise<[{ data: QuotationWithRfqCode[], pageCount: number, total?: number }]>;
}
export function VendorQuotationsTable({ promises }: VendorQuotationsTableProps) {
@@ -34,16 +38,68 @@ export function VendorQuotationsTable({ promises }: VendorQuotationsTableProps)
const [{ data, pageCount }] = React.use(promises);
const router = useRouter();
+
+ // 첨부파일 시트 상태
+ const [attachmentsOpen, setAttachmentsOpen] = React.useState(false)
+ const [selectedRfqForAttachments, setSelectedRfqForAttachments] = React.useState<{ id: number; rfqCode: string | null; status: string } | null>(null)
+ const [attachmentsDefault, setAttachmentsDefault] = React.useState<ExistingTechSalesAttachment[]>([])
// 데이터 안정성을 위한 메모이제이션 - 핵심 속성만 비교
const stableData = React.useMemo(() => {
return data;
}, [data.length, data.map(item => `${item.id}-${item.status}-${item.updatedAt}`).join(',')]);
+ // 첨부파일 시트 열기 함수
+ const openAttachmentsSheet = React.useCallback(async (rfqId: number) => {
+ try {
+ // RFQ 정보 조회 (data에서 rfqId에 해당하는 데이터 찾기)
+ const quotationWithRfq = data.find(item => item.rfqId === rfqId)
+ if (!quotationWithRfq) {
+ toast.error("RFQ 정보를 찾을 수 없습니다.")
+ return
+ }
+
+ // 실제 첨부파일 목록 조회 API 호출
+ const result = await getTechSalesRfqAttachments(rfqId)
+
+ if (result.error) {
+ toast.error(result.error)
+ return
+ }
+
+ // API 응답을 ExistingTechSalesAttachment 형식으로 변환
+ const attachments: ExistingTechSalesAttachment[] = result.data.map(att => ({
+ id: att.id,
+ techSalesRfqId: att.techSalesRfqId || rfqId,
+ fileName: att.fileName,
+ originalFileName: att.originalFileName,
+ filePath: att.filePath,
+ fileSize: att.fileSize || undefined,
+ fileType: att.fileType || undefined,
+ attachmentType: att.attachmentType as "RFQ_COMMON" | "VENDOR_SPECIFIC",
+ description: att.description || undefined,
+ createdBy: att.createdBy,
+ createdAt: att.createdAt,
+ }))
+
+ setAttachmentsDefault(attachments)
+ setSelectedRfqForAttachments({
+ id: rfqId,
+ rfqCode: quotationWithRfq.rfqCode || null,
+ status: quotationWithRfq.rfqStatus || "Unknown"
+ })
+ setAttachmentsOpen(true)
+ } catch (error) {
+ console.error("첨부파일 조회 오류:", error)
+ toast.error("첨부파일 조회 중 오류가 발생했습니다.")
+ }
+ }, [data])
+
// 테이블 컬럼 정의 - router는 안정적이므로 한 번만 생성
const columns = React.useMemo(() => getColumns({
router,
- }), [router]);
+ openAttachmentsSheet,
+ }), [router, openAttachmentsSheet]);
// 필터 필드 - 중앙화된 상태 상수 사용
const filterFields = React.useMemo<DataTableFilterField<QuotationWithRfqCode>[]>(() => [
@@ -138,6 +194,16 @@ export function VendorQuotationsTable({ promises }: VendorQuotationsTableProps)
</DataTableAdvancedToolbar>
</DataTable>
</div>
+
+ {/* 첨부파일 관리 시트 (읽기 전용) */}
+ <TechSalesRfqAttachmentsSheet
+ open={attachmentsOpen}
+ onOpenChange={setAttachmentsOpen}
+ defaultAttachments={attachmentsDefault}
+ rfq={selectedRfqForAttachments}
+ onAttachmentsUpdated={() => {}} // 읽기 전용이므로 빈 함수
+ readOnly={true} // 벤더 쪽에서는 항상 읽기 전용
+ />
</div>
);
} \ No newline at end of file