summaryrefslogtreecommitdiff
path: root/lib/tbe-last/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tbe-last/table')
-rw-r--r--lib/tbe-last/table/tbe-last-table-columns.tsx376
-rw-r--r--lib/tbe-last/table/tbe-last-table.tsx419
2 files changed, 795 insertions, 0 deletions
diff --git a/lib/tbe-last/table/tbe-last-table-columns.tsx b/lib/tbe-last/table/tbe-last-table-columns.tsx
new file mode 100644
index 00000000..71b3acde
--- /dev/null
+++ b/lib/tbe-last/table/tbe-last-table-columns.tsx
@@ -0,0 +1,376 @@
+// lib/tbe-last/table/tbe-last-table-columns.tsx
+
+"use client"
+
+import * as React from "react"
+import { type ColumnDef } from "@tanstack/react-table"
+import { FileText, MessageSquare, Package, ListChecks } from "lucide-react"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import { Checkbox } from "@/components/ui/checkbox"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { formatDate } from "@/lib/utils"
+import { TbeLastView } from "@/db/schema/tbeLastView"
+
+interface GetColumnsProps {
+ onOpenSessionDetail: (sessionId: number) => void;
+ onOpenDocuments: (sessionId: number) => void;
+ onOpenPrItems: (rfqId: number) => void;
+ onOpenEvaluation: (session: TbeLastView) => void;
+}
+
+export function getColumns({
+ onOpenSessionDetail,
+ onOpenDocuments,
+ onOpenPrItems,
+ onOpenEvaluation,
+}: GetColumnsProps): ColumnDef<TbeLastView>[] {
+
+ const columns: ColumnDef<TbeLastView>[] = [
+ // Select Column
+ {
+ id: "select",
+ header: ({ table }) => (
+ <Checkbox
+ checked={
+ table.getIsAllPageRowsSelected() ||
+ (table.getIsSomePageRowsSelected() && "indeterminate")
+ }
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ className="translate-y-0.5"
+ />
+ ),
+ cell: ({ row }) => (
+ <Checkbox
+ checked={row.getIsSelected()}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
+ aria-label="Select row"
+ className="translate-y-0.5"
+ />
+ ),
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ },
+
+ // TBE Session Code
+ {
+ accessorKey: "sessionCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="TBE Code" />
+ ),
+ cell: ({ row }) => {
+ const sessionId = row.original.tbeSessionId;
+ const sessionCode = row.original.sessionCode;
+
+ return (
+ <Button
+ variant="link"
+ className="p-0 h-auto font-medium"
+ onClick={() => onOpenSessionDetail(sessionId)}
+ >
+ {sessionCode}
+ </Button>
+ );
+ },
+ size: 120,
+ },
+
+ // RFQ Info Group
+ {
+ id: "rfqInfo",
+ header: "RFQ Information",
+ columns: [
+ {
+ accessorKey: "rfqCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="RFQ Code" />
+ ),
+ cell: ({ row }) => row.original.rfqCode,
+ size: 120,
+ },
+ {
+ accessorKey: "rfqTitle",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="RFQ Title" />
+ ),
+ cell: ({ row }) => row.original.rfqTitle || "-",
+ size: 200,
+ },
+ {
+ accessorKey: "rfqDueDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Due Date" />
+ ),
+ cell: ({ row }) => {
+ const date = row.original.rfqDueDate;
+ return date ? formatDate(date, "KR") : "-";
+ },
+ size: 100,
+ },
+ ],
+ },
+
+ // Package Info
+ {
+ id: "packageInfo",
+ header: "Package",
+ columns: [
+ {
+ accessorKey: "packageNo",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Package No" />
+ ),
+ cell: ({ row }) => {
+ const packageNo = row.original.packageNo;
+ const packageName = row.original.packageName;
+
+ if (!packageNo) return "-";
+
+ return (
+ <div className="flex flex-col">
+ <span className="font-medium">{packageNo}</span>
+ {packageName && (
+ <span className="text-xs text-muted-foreground">{packageName}</span>
+ )}
+ </div>
+ );
+ },
+ size: 150,
+ },
+ ],
+ },
+
+ // Project Info
+ {
+ accessorKey: "projectCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Project" />
+ ),
+ cell: ({ row }) => {
+ const projectCode = row.original.projectCode;
+ const projectName = row.original.projectName;
+
+ if (!projectCode) return "-";
+
+ return (
+ <div className="flex flex-col">
+ <span className="font-medium">{projectCode}</span>
+ {projectName && (
+ <span className="text-xs text-muted-foreground">{projectName}</span>
+ )}
+ </div>
+ );
+ },
+ size: 150,
+ },
+
+ // Vendor Info
+ {
+ id: "vendorInfo",
+ header: "Vendor",
+ columns: [
+ {
+ accessorKey: "vendorCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Vendor Code" />
+ ),
+ cell: ({ row }) => row.original.vendorCode || "-",
+ size: 100,
+ },
+ {
+ accessorKey: "vendorName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Vendor Name" />
+ ),
+ cell: ({ row }) => row.original.vendorName,
+ size: 200,
+ },
+ ],
+ },
+
+ // TBE Status
+ {
+ accessorKey: "sessionStatus",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Status" />
+ ),
+ cell: ({ row }) => {
+ const status = row.original.sessionStatus;
+
+ let variant: "default" | "secondary" | "outline" | "destructive" = "outline";
+
+ switch (status) {
+ case "준비중":
+ variant = "outline";
+ break;
+ case "진행중":
+ variant = "default";
+ break;
+ case "검토중":
+ variant = "secondary";
+ break;
+ case "완료":
+ variant = "default";
+ break;
+ case "보류":
+ variant = "destructive";
+ break;
+ }
+
+ return <Badge variant={variant}>{status}</Badge>;
+ },
+ size: 100,
+ },
+
+ // Evaluation Result
+ {
+ accessorKey: "evaluationResult",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Result" />
+ ),
+ cell: ({ row }) => {
+ const result = row.original.evaluationResult;
+ const session = row.original;
+
+ if (!result) {
+ return (
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => onOpenEvaluation(session)}
+ >
+ 평가입력
+ </Button>
+ );
+ }
+
+ let variant: "default" | "secondary" | "destructive" = "default";
+ let displayText = result;
+
+ switch (result) {
+ case "pass":
+ variant = "default";
+ displayText = "Pass";
+ break;
+ case "conditional_pass":
+ variant = "secondary";
+ displayText = "Conditional";
+ break;
+ case "non_pass":
+ variant = "destructive";
+ displayText = "Non-Pass";
+ break;
+ }
+
+ return (
+ <Button
+ variant="link"
+ className="p-0 h-auto"
+ onClick={() => onOpenEvaluation(session)}
+ >
+ <Badge variant={variant}>{displayText}</Badge>
+ </Button>
+ );
+ },
+ size: 120,
+ },
+
+ // PR Items
+ {
+ id: "prItems",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="PR Items" />
+ ),
+ cell: ({ row }) => {
+ const rfqId = row.original.rfqId;
+ const totalCount = row.original.prItemsCount;
+ const majorCount = row.original.majorItemsCount;
+
+ return (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 px-2"
+ onClick={() => onOpenPrItems(rfqId)}
+ >
+ <ListChecks className="h-4 w-4 mr-1" />
+ <span className="text-xs">
+ {totalCount} ({majorCount})
+ </span>
+ </Button>
+ );
+ },
+ size: 100,
+ enableSorting: false,
+ },
+
+ // Documents
+ {
+ id: "documents",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Documents" />
+ ),
+ cell: ({ row }) => {
+ const sessionId = row.original.tbeSessionId;
+ const buyerDocs = row.original.buyerDocumentsCount;
+ const vendorDocs = row.original.vendorDocumentsCount;
+ const reviewedDocs = row.original.reviewedDocumentsCount;
+ const totalDocs = buyerDocs + vendorDocs;
+
+ return (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 px-2"
+ onClick={() => onOpenDocuments(sessionId)}
+ >
+ <FileText className="h-4 w-4 mr-1" />
+ <span className="text-xs">
+ {reviewedDocs}/{totalDocs}
+ </span>
+ </Button>
+ );
+ },
+ size: 100,
+ enableSorting: false,
+ },
+
+ // Comments
+ {
+ id: "comments",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Comments" />
+ ),
+ cell: ({ row }) => {
+ const sessionId = row.original.tbeSessionId;
+ const totalComments = row.original.totalCommentsCount;
+ const unresolvedComments = row.original.unresolvedCommentsCount;
+
+ return (
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-8 px-2 relative"
+ onClick={() => onOpenDocuments(sessionId)}
+ >
+ <MessageSquare className="h-4 w-4" />
+ {totalComments > 0 && (
+ <Badge
+ variant={unresolvedComments > 0 ? "destructive" : "secondary"}
+ className="absolute -top-1 -right-1 h-4 min-w-[1rem] p-0 text-[0.625rem]"
+ >
+ {unresolvedComments > 0 ? unresolvedComments : totalComments}
+ </Badge>
+ )}
+ </Button>
+ );
+ },
+ size: 80,
+ enableSorting: false,
+ },
+ ];
+
+ return columns;
+} \ No newline at end of file
diff --git a/lib/tbe-last/table/tbe-last-table.tsx b/lib/tbe-last/table/tbe-last-table.tsx
new file mode 100644
index 00000000..64707e4e
--- /dev/null
+++ b/lib/tbe-last/table/tbe-last-table.tsx
@@ -0,0 +1,419 @@
+// lib/tbe-last/table/tbe-last-table.tsx
+
+"use client"
+
+import * as React from "react"
+import { useRouter } from "next/navigation"
+import { type DataTableFilterField } 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 "./tbe-last-table-columns"
+import { TbeLastView } from "@/db/schema"
+import { getAllTBELast, getTBESessionDetail } from "@/lib/tbe-last/service"
+import { Button } from "@/components/ui/button"
+import { Download, RefreshCw } from "lucide-react"
+import { exportTableToExcel } from "@/lib/export"
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription
+} from "@/components/ui/dialog"
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+ SheetDescription
+} from "@/components/ui/sheet"
+import { Badge } from "@/components/ui/badge"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { formatDate } from "@/lib/utils"
+
+interface TbeLastTableProps {
+ promises: Promise<[
+ Awaited<ReturnType<typeof getAllTBELast>>,
+ ]>
+}
+
+export function TbeLastTable({ promises }: TbeLastTableProps) {
+ const router = useRouter()
+ const [{ data, pageCount }] = React.use(promises)
+
+ // Dialog states
+ const [sessionDetailOpen, setSessionDetailOpen] = React.useState(false)
+ const [documentsOpen, setDocumentsOpen] = React.useState(false)
+ const [prItemsOpen, setPrItemsOpen] = React.useState(false)
+ const [evaluationOpen, setEvaluationOpen] = React.useState(false)
+
+ const [selectedSessionId, setSelectedSessionId] = React.useState<number | null>(null)
+ const [selectedRfqId, setSelectedRfqId] = React.useState<number | null>(null)
+ const [selectedSession, setSelectedSession] = React.useState<TbeLastView | null>(null)
+ const [sessionDetail, setSessionDetail] = React.useState<any>(null)
+ const [isLoadingDetail, setIsLoadingDetail] = React.useState(false)
+
+ // Load session detail when needed
+ const loadSessionDetail = React.useCallback(async (sessionId: number) => {
+ setIsLoadingDetail(true)
+ try {
+ const detail = await getTBESessionDetail(sessionId)
+ setSessionDetail(detail)
+ } catch (error) {
+ console.error("Failed to load session detail:", error)
+ } finally {
+ setIsLoadingDetail(false)
+ }
+ }, [])
+
+ // Handlers
+ const handleOpenSessionDetail = React.useCallback((sessionId: number) => {
+ setSelectedSessionId(sessionId)
+ setSessionDetailOpen(true)
+ loadSessionDetail(sessionId)
+ }, [loadSessionDetail])
+
+ const handleOpenDocuments = React.useCallback((sessionId: number) => {
+ setSelectedSessionId(sessionId)
+ setDocumentsOpen(true)
+ loadSessionDetail(sessionId)
+ }, [loadSessionDetail])
+
+ const handleOpenPrItems = React.useCallback((rfqId: number) => {
+ setSelectedRfqId(rfqId)
+ setPrItemsOpen(true)
+ loadSessionDetail(rfqId)
+ }, [loadSessionDetail])
+
+ const handleOpenEvaluation = React.useCallback((session: TbeLastView) => {
+ setSelectedSession(session)
+ setEvaluationOpen(true)
+ }, [])
+
+ const handleRefresh = React.useCallback(() => {
+ router.refresh()
+ }, [router])
+
+ // Table columns
+ const columns = React.useMemo(
+ () =>
+ getColumns({
+ onOpenSessionDetail: handleOpenSessionDetail,
+ onOpenDocuments: handleOpenDocuments,
+ onOpenPrItems: handleOpenPrItems,
+ onOpenEvaluation: handleOpenEvaluation,
+ }),
+ [handleOpenSessionDetail, handleOpenDocuments, handleOpenPrItems, handleOpenEvaluation]
+ )
+
+ // Filter fields
+ const filterFields: DataTableFilterField<TbeLastView>[] = [
+ {
+ id: "sessionStatus",
+ label: "Status",
+ type: "select",
+ options: [
+ { label: "준비중", value: "준비중" },
+ { label: "진행중", value: "진행중" },
+ { label: "검토중", value: "검토중" },
+ { label: "보류", value: "보류" },
+ { label: "완료", value: "완료" },
+ ],
+ },
+ {
+ id: "evaluationResult",
+ label: "Result",
+ type: "select",
+ options: [
+ { label: "Pass", value: "pass" },
+ { label: "Conditional Pass", value: "conditional_pass" },
+ { label: "Non-Pass", value: "non_pass" },
+ ],
+ },
+ ]
+
+ // Data table
+ const { table } = useDataTable({
+ data,
+ columns,
+ pageCount,
+ filterFields,
+ enablePinning: true,
+ enableAdvancedFilter: true,
+ initialState: {
+ sorting: [{ id: "createdAt", desc: true }],
+ columnPinning: { right: ["documents", "comments"] },
+ },
+ getRowId: (originalRow) => String(originalRow.tbeSessionId),
+ shallow: false,
+ clearOnDefault: true,
+ })
+
+ return (
+ <>
+ <DataTable table={table}>
+ <DataTableAdvancedToolbar
+ table={table}
+ filterFields={filterFields}
+ shallow={false}
+ >
+ <div className="flex items-center gap-2">
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleRefresh}
+ className="gap-2"
+ >
+ <RefreshCw className="size-4" />
+ <span>Refresh</span>
+ </Button>
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() =>
+ exportTableToExcel(table, {
+ filename: "tbe-sessions",
+ excludeColumns: ["select", "actions"],
+ })
+ }
+ className="gap-2"
+ >
+ <Download className="size-4" />
+ <span>Export</span>
+ </Button>
+ </div>
+ </DataTableAdvancedToolbar>
+ </DataTable>
+
+ {/* Session Detail Dialog */}
+ <Dialog open={sessionDetailOpen} onOpenChange={setSessionDetailOpen}>
+ <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
+ <DialogHeader>
+ <DialogTitle>TBE Session Detail</DialogTitle>
+ <DialogDescription>
+ {sessionDetail?.session?.sessionCode} - {sessionDetail?.session?.vendorName}
+ </DialogDescription>
+ </DialogHeader>
+ {isLoadingDetail ? (
+ <div className="p-8 text-center">Loading...</div>
+ ) : sessionDetail ? (
+ <div className="space-y-4">
+ {/* Session info */}
+ <div className="grid grid-cols-2 gap-4">
+ <div>
+ <p className="text-sm font-medium">RFQ Code</p>
+ <p className="text-sm text-muted-foreground">{sessionDetail.session.rfqCode}</p>
+ </div>
+ <div>
+ <p className="text-sm font-medium">Status</p>
+ <Badge>{sessionDetail.session.sessionStatus}</Badge>
+ </div>
+ <div>
+ <p className="text-sm font-medium">Project</p>
+ <p className="text-sm text-muted-foreground">
+ {sessionDetail.session.projectCode} - {sessionDetail.session.projectName}
+ </p>
+ </div>
+ <div>
+ <p className="text-sm font-medium">Package</p>
+ <p className="text-sm text-muted-foreground">
+ {sessionDetail.session.packageNo} - {sessionDetail.session.packageName}
+ </p>
+ </div>
+ </div>
+
+ {/* PR Items */}
+ {sessionDetail.prItems?.length > 0 && (
+ <div>
+ <h3 className="font-medium mb-2">PR Items</h3>
+ <div className="border rounded-lg">
+ <table className="w-full text-sm">
+ <thead>
+ <tr className="border-b">
+ <th className="text-left p-2">PR No</th>
+ <th className="text-left p-2">Material Code</th>
+ <th className="text-left p-2">Description</th>
+ <th className="text-left p-2">Qty</th>
+ <th className="text-left p-2">Delivery</th>
+ </tr>
+ </thead>
+ <tbody>
+ {sessionDetail.prItems.map((item: any) => (
+ <tr key={item.id} className="border-b">
+ <td className="p-2">{item.prNo}</td>
+ <td className="p-2">{item.materialCode}</td>
+ <td className="p-2">{item.materialDescription}</td>
+ <td className="p-2">{item.quantity} {item.uom}</td>
+ <td className="p-2">
+ {item.deliveryDate ? formatDate(item.deliveryDate, "KR") : "-"}
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ )}
+ </div>
+ ) : null}
+ </DialogContent>
+ </Dialog>
+
+ {/* Documents Sheet */}
+ <Sheet open={documentsOpen} onOpenChange={setDocumentsOpen}>
+ <SheetContent className="w-[600px] sm:w-[800px]">
+ <SheetHeader>
+ <SheetTitle>Documents & Comments</SheetTitle>
+ <SheetDescription>
+ Review documents and PDFTron comments
+ </SheetDescription>
+ </SheetHeader>
+
+ {isLoadingDetail ? (
+ <div className="p-8 text-center">Loading...</div>
+ ) : sessionDetail?.documents ? (
+ <Tabs defaultValue="buyer" className="mt-4">
+ <TabsList className="grid w-full grid-cols-2">
+ <TabsTrigger value="buyer">Buyer Documents</TabsTrigger>
+ <TabsTrigger value="vendor">Vendor Documents</TabsTrigger>
+ </TabsList>
+
+ <TabsContent value="buyer">
+ <ScrollArea className="h-[calc(100vh-200px)]">
+ <div className="space-y-2">
+ {sessionDetail.documents
+ .filter((doc: any) => doc.documentSource === "buyer")
+ .map((doc: any) => (
+ <div key={doc.documentId} className="border rounded-lg p-3">
+ <div className="flex items-start justify-between">
+ <div className="flex-1">
+ <p className="font-medium text-sm">{doc.documentName}</p>
+ <p className="text-xs text-muted-foreground">
+ Type: {doc.documentType} | Status: {doc.reviewStatus}
+ </p>
+ </div>
+ <div className="flex items-center gap-2">
+ {doc.comments.totalCount > 0 && (
+ <Badge variant={doc.comments.openCount > 0 ? "destructive" : "secondary"}>
+ {doc.comments.openCount}/{doc.comments.totalCount} comments
+ </Badge>
+ )}
+ <Button size="sm" variant="outline">
+ View in PDFTron
+ </Button>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </TabsContent>
+
+ <TabsContent value="vendor">
+ <ScrollArea className="h-[calc(100vh-200px)]">
+ <div className="space-y-2">
+ {sessionDetail.documents
+ .filter((doc: any) => doc.documentSource === "vendor")
+ .map((doc: any) => (
+ <div key={doc.documentId} className="border rounded-lg p-3">
+ <div className="flex items-start justify-between">
+ <div className="flex-1">
+ <p className="font-medium text-sm">{doc.documentName}</p>
+ <p className="text-xs text-muted-foreground">
+ Type: {doc.documentType} | Status: {doc.reviewStatus}
+ </p>
+ {doc.submittedAt && (
+ <p className="text-xs text-muted-foreground">
+ Submitted: {formatDate(doc.submittedAt, "KR")}
+ </p>
+ )}
+ </div>
+ <div className="flex items-center gap-2">
+ <Button size="sm" variant="outline">
+ Download
+ </Button>
+ <Button size="sm" variant="outline">
+ Review
+ </Button>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </TabsContent>
+ </Tabs>
+ ) : null}
+ </SheetContent>
+ </Sheet>
+
+ {/* PR Items Dialog */}
+ <Dialog open={prItemsOpen} onOpenChange={setPrItemsOpen}>
+ <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
+ <DialogHeader>
+ <DialogTitle>PR Items</DialogTitle>
+ <DialogDescription>
+ Purchase Request items for this RFQ
+ </DialogDescription>
+ </DialogHeader>
+ {sessionDetail?.prItems && (
+ <div className="border rounded-lg">
+ <table className="w-full text-sm">
+ <thead>
+ <tr className="border-b bg-muted/50">
+ <th className="text-left p-2">PR No</th>
+ <th className="text-left p-2">Material Code</th>
+ <th className="text-left p-2">Description</th>
+ <th className="text-left p-2">Size</th>
+ <th className="text-left p-2">Qty</th>
+ <th className="text-left p-2">Unit</th>
+ <th className="text-left p-2">Delivery</th>
+ <th className="text-left p-2">Major</th>
+ </tr>
+ </thead>
+ <tbody>
+ {sessionDetail.prItems.map((item: any) => (
+ <tr key={item.id} className="border-b hover:bg-muted/20">
+ <td className="p-2">{item.prNo}</td>
+ <td className="p-2">{item.materialCode}</td>
+ <td className="p-2">{item.materialDescription}</td>
+ <td className="p-2">{item.size || "-"}</td>
+ <td className="p-2 text-right">{item.quantity}</td>
+ <td className="p-2">{item.uom}</td>
+ <td className="p-2">
+ {item.deliveryDate ? formatDate(item.deliveryDate, "KR") : "-"}
+ </td>
+ <td className="p-2 text-center">
+ {item.majorYn && <Badge variant="default">Major</Badge>}
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ )}
+ </DialogContent>
+ </Dialog>
+
+ {/* Evaluation Dialog */}
+ <Dialog open={evaluationOpen} onOpenChange={setEvaluationOpen}>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle>TBE Evaluation</DialogTitle>
+ <DialogDescription>
+ Enter evaluation result for {selectedSession?.sessionCode}
+ </DialogDescription>
+ </DialogHeader>
+ <div className="space-y-4 mt-4">
+ {/* Evaluation form would go here */}
+ <p className="text-sm text-muted-foreground">
+ Evaluation form to be implemented...
+ </p>
+ </div>
+ </DialogContent>
+ </Dialog>
+ </>
+ )
+} \ No newline at end of file