summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/ship-all/enhanced-doc-table-columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-21 06:57:36 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-21 06:57:36 +0000
commit02b1cf005cf3e1df64183d20ba42930eb2767a9f (patch)
treee932c54d5260b0e6fda2b46be2a6ba1c3ee30434 /lib/vendor-document-list/ship-all/enhanced-doc-table-columns.tsx
parentd78378ecd7ceede1429359f8058c7a99ac34b1b7 (diff)
(대표님, 최겸) 설계메뉴추가, 작업사항 업데이트
설계메뉴 - 문서관리 설계메뉴 - 벤더 데이터 gtc 메뉴 업데이트 정보시스템 - 메뉴리스트 및 정보 업데이트 파일 라우트 업데이트 엑셀임포트 개선 기본계약 개선 벤더 가입과정 변경 및 개선 벤더 기본정보 - pq 돌체 오류 수정 및 개선 벤더 로그인 과정 이메일 오류 수정
Diffstat (limited to 'lib/vendor-document-list/ship-all/enhanced-doc-table-columns.tsx')
-rw-r--r--lib/vendor-document-list/ship-all/enhanced-doc-table-columns.tsx540
1 files changed, 540 insertions, 0 deletions
diff --git a/lib/vendor-document-list/ship-all/enhanced-doc-table-columns.tsx b/lib/vendor-document-list/ship-all/enhanced-doc-table-columns.tsx
new file mode 100644
index 00000000..6c9a9ab6
--- /dev/null
+++ b/lib/vendor-document-list/ship-all/enhanced-doc-table-columns.tsx
@@ -0,0 +1,540 @@
+// simplified-doc-table-columns.tsx
+"use client"
+
+import * as React from "react"
+import { ColumnDef } from "@tanstack/react-table"
+import { formatDate, formatDateTime } from "@/lib/utils"
+import { Checkbox } from "@/components/ui/checkbox"
+import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
+import { DataTableRowAction } from "@/types/table"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { Button } from "@/components/ui/button"
+import {
+ Ellipsis,
+ FileText,
+ Eye,
+ Edit,
+ Trash2,
+} from "lucide-react"
+import { cn } from "@/lib/utils"
+import { SimplifiedDocumentsView } from "@/db/schema"
+
+// DocumentSelectionContext를 import (실제 파일 경로에 맞게 수정 필요)
+// 예: import { DocumentSelectionContextAll } from "../user-vendor-document-display"
+// 또는: import { DocumentSelectionContextAll } from "./user-vendor-document-display"
+import { DocumentSelectionContextAll } from "@/components/ship-vendor-document-all/user-vendor-document-table-container"
+
+interface GetColumnsProps {
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<SimplifiedDocumentsView> | null>>
+}
+
+// 날짜 표시 컴포넌트 (간단 버전)
+const DateDisplay = ({ date, isSelected = false }: { date: string | null, isSelected?: boolean }) => {
+ if (!date) return <span className="text-gray-400">-</span>
+
+ return (
+ <span className={cn(
+ "text-sm",
+ isSelected && "text-blue-600 font-semibold"
+ )}>
+ {formatDate(date)}
+ </span>
+ )
+}
+
+export function getSimplifiedDocumentColumns({
+ setRowAction,
+}: GetColumnsProps): ColumnDef<SimplifiedDocumentsView>[] {
+
+ const columns: ColumnDef<SimplifiedDocumentsView>[] = [
+ // 라디오 버튼 같은 체크박스 선택
+ {
+ id: "select",
+ header: ({ table }) => (
+ <div className="flex items-center justify-center">
+ <span className="text-xs text-gray-500">Select</span>
+ </div>
+ ),
+ cell: ({ row }) => {
+ const doc = row.original
+
+ return (
+ <SelectCell documentId={doc.documentId} />
+ )
+ },
+ size: 40,
+ enableSorting: false,
+ enableHiding: false,
+ },
+
+ // 문서번호 (선택된 행 하이라이트 적용)
+ {
+ accessorKey: "docNumber",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Document No" />
+ ),
+ cell: ({ row }) => {
+ const doc = row.original
+
+ return (
+ <DocNumberCell doc={doc} />
+ )
+ },
+ size: 120,
+ enableResizing: true,
+ meta: {
+ excelHeader: "Document No"
+ },
+ },
+
+ // 문서명 (선택된 행 하이라이트 적용)
+ {
+ accessorKey: "title",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Title" />
+ ),
+ cell: ({ row }) => {
+ const doc = row.original
+
+ return (
+ <TitleCell doc={doc} />
+ )
+ },
+ enableResizing: true,
+ maxSize:300,
+ meta: {
+ excelHeader: "Title"
+ },
+ },
+
+ // 프로젝트 코드
+ {
+ accessorKey: "projectCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Project" />
+ ),
+ cell: ({ row }) => {
+ const projectCode = row.original.projectCode
+
+ return (
+ <ProjectCodeCell projectCode={projectCode} documentId={row.original.documentId} />
+ )
+ },
+ enableResizing: true,
+ maxSize:100,
+ meta: {
+ excelHeader: "Project"
+ },
+ },
+
+ // 벤더명
+ {
+ accessorKey: "vendorName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Vendor Name" />
+ ),
+ cell: ({ row }) => {
+ const vendorName = row.original.vendorName
+
+ return (
+ <VendorNameCell vendorName={vendorName} documentId={row.original.documentId} />
+ )
+ },
+ enableResizing: true,
+ maxSize: 200,
+ meta: {
+ excelHeader: "Vendor Name"
+ },
+ },
+
+ // 벤더 코드
+ {
+ accessorKey: "vendorCode",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Vendor Code" />
+ ),
+ cell: ({ row }) => {
+ const vendorCode = row.original.vendorCode
+
+ return (
+ <VendorCodeCell vendorCode={vendorCode} documentId={row.original.documentId} />
+ )
+ },
+ enableResizing: true,
+ maxSize: 120,
+ meta: {
+ excelHeader: "Vendor Code"
+ },
+ },
+
+ // 1차 스테이지 그룹
+ {
+ id: "firstStageGroup",
+ header: ({ table }) => {
+ // 첫 번째 행의 firstStageName을 그룹 헤더로 사용
+ const firstRow = table.getRowModel().rows[0]?.original
+ const stageName = firstRow?.firstStageName || "First Stage"
+ return (
+ <div className="text-center font-medium text-gray-700">
+ {stageName}
+ </div>
+ )
+ },
+ columns: [
+ {
+ accessorKey: "firstStagePlanDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Planned Date" />
+ ),
+ cell: ({ row }) => {
+ return <FirstStagePlanDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "First Planned Date"
+ },
+ },
+ {
+ accessorKey: "firstStageActualDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Actual Date" />
+ ),
+ cell: ({ row }) => {
+ return <FirstStageActualDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "First Actual Date"
+ },
+ },
+ ],
+ },
+
+ // 2차 스테이지 그룹
+ {
+ id: "secondStageGroup",
+ header: ({ table }) => {
+ // 첫 번째 행의 secondStageName을 그룹 헤더로 사용
+ const firstRow = table.getRowModel().rows[0]?.original
+ const stageName = firstRow?.secondStageName || "Second Stage"
+ return (
+ <div className="text-center font-medium text-gray-700">
+ {stageName}
+ </div>
+ )
+ },
+ columns: [
+ {
+ accessorKey: "secondStagePlanDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Planned Date" />
+ ),
+ cell: ({ row }) => {
+ return <SecondStagePlanDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "Second Planned Date"
+ },
+ },
+ {
+ accessorKey: "secondStageActualDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Actual Date" />
+ ),
+ cell: ({ row }) => {
+ return <SecondStageActualDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "Second Actual Date"
+ },
+ },
+ ],
+ },
+
+ // 첨부파일 수
+ {
+ accessorKey: "attachmentCount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Files" />
+ ),
+ cell: ({ row }) => {
+ const count = row.original.attachmentCount || 0
+
+ return (
+ <AttachmentCountCell count={count} documentId={row.original.documentId} />
+ )
+ },
+ size: 60,
+ enableResizing: true,
+ meta: {
+ excelHeader: "Attachments"
+ },
+ },
+
+ // 업데이트 일시
+ {
+ accessorKey: "updatedAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="Updated" />
+ ),
+ cell: ({ cell, row }) => {
+ return (
+ <UpdatedAtCell updatedAt={cell.getValue() as Date} documentId={row.original.documentId} />
+ )
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "Updated"
+ },
+ },
+
+ // 액션 버튼
+ // {
+ // id: "actions",
+ // header: () => <span className="sr-only">Actions</span>,
+ // cell: ({ row }) => {
+ // const doc = row.original
+ // return (
+ // <DropdownMenu>
+ // <DropdownMenuTrigger asChild>
+ // <Button variant="ghost" className="h-8 w-8 p-0">
+ // <span className="sr-only">Open menu</span>
+ // <Ellipsis className="h-4 w-4" />
+ // </Button>
+ // </DropdownMenuTrigger>
+ // <DropdownMenuContent align="end">
+ // <DropdownMenuItem
+ // onClick={() => setRowAction({ type: "view", row: doc })}
+ // >
+ // <Eye className="mr-2 h-4 w-4" />
+ // 보기
+ // </DropdownMenuItem>
+ // <DropdownMenuItem
+ // onClick={() => setRowAction({ type: "edit", row: doc })}
+ // >
+ // <Edit className="mr-2 h-4 w-4" />
+ // 편집
+ // </DropdownMenuItem>
+ // <DropdownMenuSeparator />
+ // <DropdownMenuItem
+ // onClick={() => setRowAction({ type: "delete", row: doc })}
+ // className="text-red-600"
+ // >
+ // <Trash2 className="mr-2 h-4 w-4" />
+ // 삭제
+ // <DropdownMenuShortcut>⌫</DropdownMenuShortcut>
+ // </DropdownMenuItem>
+ // </DropdownMenuContent>
+ // </DropdownMenu>
+ // )
+ // },
+ // size: 50,
+ // enableSorting: false,
+ // enableHiding: false,
+ // },
+ ]
+
+ return columns
+}
+
+// 개별 셀 컴포넌트들 (Context 사용)
+function SelectCell({ documentId }: { documentId: number }) {
+ const { selectedDocumentId, setSelectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === documentId;
+
+ return (
+ <div className="flex items-center justify-center">
+ <input
+ type="radio"
+ checked={isSelected}
+ onChange={() => {
+ const newSelection = isSelected ? null : documentId;
+ setSelectedDocumentId(newSelection);
+ }}
+ className="cursor-pointer w-4 h-4"
+ />
+ </div>
+ );
+}
+
+function DocNumberCell({ doc }: { doc: SimplifiedDocumentsView }) {
+ const { selectedDocumentId, setSelectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === doc.documentId;
+
+ return (
+ <div
+ className={cn(
+ "font-mono text-sm font-medium cursor-pointer px-2 py-1 rounded transition-colors",
+ isSelected
+ ? "text-blue-600 font-bold bg-blue-50"
+ : "hover:bg-gray-50"
+ )}
+ onClick={() => {
+ const newSelection = isSelected ? null : doc.documentId;
+ setSelectedDocumentId(newSelection);
+ }}
+ >
+ {doc.docNumber}
+ </div>
+ );
+}
+
+function TitleCell({ doc }: { doc: SimplifiedDocumentsView }) {
+ const { selectedDocumentId, setSelectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === doc.documentId;
+
+ return (
+ <div
+ className={cn(
+ "font-medium text-gray-900 truncate max-w-[300px] cursor-pointer px-2 py-1 rounded transition-colors",
+ isSelected
+ ? "text-blue-600 font-bold bg-blue-50"
+ : "hover:bg-gray-50"
+ )}
+ title={doc.title}
+ onClick={() => {
+ const newSelection = isSelected ? null : doc.documentId;
+ setSelectedDocumentId(newSelection);
+ }}
+ >
+ {doc.title}
+ </div>
+ );
+}
+
+function ProjectCodeCell({ projectCode, documentId }: { projectCode: string | null, documentId: number }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === documentId;
+
+ if (!projectCode) return <span className="text-gray-400">-</span>;
+
+ return (
+ <span className={cn(
+ "text-sm font-medium",
+ isSelected && "text-blue-600 font-bold"
+ )}>
+ {projectCode}
+ </span>
+ );
+}
+
+function VendorNameCell({ vendorName, documentId }: { vendorName: string | null, documentId: number }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === documentId;
+
+ if (!vendorName) return <span className="text-gray-400">-</span>;
+
+ return (
+ <div
+ className={cn(
+ "text-sm font-medium truncate max-w-[200px]",
+ isSelected && "text-blue-600 font-bold"
+ )}
+ title={vendorName}
+ >
+ {vendorName}
+ </div>
+ );
+}
+
+function VendorCodeCell({ vendorCode, documentId }: { vendorCode: string | null, documentId: number }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === documentId;
+
+ if (!vendorCode) return <span className="text-gray-400">-</span>;
+
+ return (
+ <span className={cn(
+ "text-sm font-medium font-mono",
+ isSelected && "text-blue-600 font-bold"
+ )}>
+ {vendorCode}
+ </span>
+ );
+}
+
+function FirstStagePlanDateCell({ row }: { row: any }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === row.original.documentId;
+
+ return <DateDisplay date={row.original.firstStagePlanDate} isSelected={isSelected} />;
+}
+
+function FirstStageActualDateCell({ row }: { row: any }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === row.original.documentId;
+ const date = row.original.firstStageActualDate;
+
+ return (
+ <div className={cn(
+ date ? "text-green-600 font-medium" : "",
+ isSelected && date && "text-green-700 font-bold"
+ )}>
+ <DateDisplay date={date} isSelected={isSelected && !date} />
+ {date && <span className="text-xs block">✓ 완료</span>}
+ </div>
+ );
+}
+
+function SecondStagePlanDateCell({ row }: { row: any }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === row.original.documentId;
+
+ return <DateDisplay date={row.original.secondStagePlanDate} isSelected={isSelected} />;
+}
+
+function SecondStageActualDateCell({ row }: { row: any }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === row.original.documentId;
+ const date = row.original.secondStageActualDate;
+
+ return (
+ <div className={cn(
+ date ? "text-green-600 font-medium" : "",
+ isSelected && date && "text-green-700 font-bold"
+ )}>
+ <DateDisplay date={date} isSelected={isSelected && !date} />
+ {date && <span className="text-xs block">✓ 완료</span>}
+ </div>
+ );
+}
+
+function AttachmentCountCell({ count, documentId }: { count: number, documentId: number }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === documentId;
+
+ return (
+ <div className="flex items-center justify-center gap-1">
+ <FileText className="w-4 h-4 text-gray-400" />
+ <span className={cn(
+ "text-sm font-medium",
+ isSelected && "text-blue-600 font-bold"
+ )}>
+ {count}
+ </span>
+ </div>
+ );
+}
+
+function UpdatedAtCell({ updatedAt, documentId }: { updatedAt: Date, documentId: number }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContextAll);
+ const isSelected = selectedDocumentId === documentId;
+
+ return (
+ <span className={cn(
+ "text-sm text-gray-600",
+ isSelected && "text-blue-600 font-semibold"
+ )}>
+ {formatDateTime(updatedAt)}
+ </span>
+ );
+} \ No newline at end of file