summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:11:18 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:11:18 +0000
commit0fddf148402fd6b99a1b3800d73679899bcb2ed3 (patch)
treeeb51c02e6fa6037ddcc38a3b57d10d8c739125cf /lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
parentc72d0897f7b37843109c86f61d97eba05ba3ca0d (diff)
(대표님) 20250613 16시 10분 global css, b-rfq, document 등
Diffstat (limited to 'lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx')
-rw-r--r--lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx616
1 files changed, 326 insertions, 290 deletions
diff --git a/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx b/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
index b80c0869..ad184378 100644
--- a/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
+++ b/lib/vendor-document-list/ship/enhanced-doc-table-columns.tsx
@@ -16,123 +16,36 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Button } from "@/components/ui/button"
-import { Badge } from "@/components/ui/badge"
import {
Ellipsis,
- Calendar,
- CalendarClock,
- User,
FileText,
Eye,
Edit,
Trash2,
- Building,
- Code,
- Settings
} from "lucide-react"
import { cn } from "@/lib/utils"
import { SimplifiedDocumentsView } from "@/db/schema"
+// DocumentSelectionContext를 import (실제 파일 경로에 맞게 수정 필요)
+// 예: import { DocumentSelectionContext } from "../user-vendor-document-display"
+// 또는: import { DocumentSelectionContext } from "./user-vendor-document-display"
+import { DocumentSelectionContext } from "@/components/ship-vendor-document/user-vendor-document-table-container"
+
interface GetColumnsProps {
setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<SimplifiedDocumentsView> | null>>
}
-// 유틸리티 함수들
-const getDrawingKindText = (drawingKind: string) => {
- switch (drawingKind) {
- case 'B3': return 'B3 도면'
- case 'B4': return 'B4 도면'
- case 'B5': return 'B5 도면'
- default: return drawingKind
- }
-}
-
-const getDrawingKindColor = (drawingKind: string) => {
- switch (drawingKind) {
- case 'B3': return 'bg-blue-100 text-blue-800'
- case 'B4': return 'bg-green-100 text-green-800'
- case 'B5': return 'bg-purple-100 text-purple-800'
- default: return 'bg-gray-100 text-gray-800'
- }
-}
-
-// 스테이지별 이름 표시 컴포넌트
-const StageNameDisplay = ({
- stageName,
- drawingKind,
- isFirst = true
-}: {
- stageName: string | null,
- drawingKind: string | null,
- isFirst?: boolean
-}) => {
- if (!stageName) return <span className="text-gray-400">-</span>
-
- const stageType = isFirst ? "1차" : "2차"
- const getExpectedStage = () => {
- if (drawingKind === 'B4') return isFirst ? 'Pre' : 'Work'
- if (drawingKind === 'B3') return isFirst ? 'Approval' : 'Work'
- if (drawingKind === 'B5') return isFirst ? 'First' : 'Second'
- return ''
- }
-
- return (
- <div className="flex flex-col gap-1">
- <div className="text-xs text-gray-500">{stageType} 스테이지</div>
- <div className="text-sm font-medium">{stageName}</div>
- {getExpectedStage() && (
- <div className="text-xs text-gray-400">({getExpectedStage()})</div>
- )}
- </div>
- )
-}
-
-// 날짜 정보 표시 컴포넌트
-const StageDateInfo = ({
- planDate,
- actualDate,
- stageName
-}: {
- planDate: string | null
- actualDate: string | null
- stageName: string | null
-}) => {
- if (!planDate && !actualDate) {
- return <span className="text-gray-400">날짜 미설정</span>
- }
-
- const isCompleted = !!actualDate
- const isLate = actualDate && planDate && new Date(actualDate) > new Date(planDate)
-
+// 날짜 표시 컴포넌트 (간단 버전)
+const DateDisplay = ({ date, isSelected = false }: { date: string | null, isSelected?: boolean }) => {
+ if (!date) return <span className="text-gray-400">-</span>
+
return (
- <div className="flex flex-col gap-1">
- {planDate && (
- <div className="text-sm">
- <span className="text-gray-500">계획: </span>
- <span>{formatDate(planDate)}</span>
- </div>
- )}
- {actualDate && (
- <div className="text-sm">
- <span className="text-gray-500">실제: </span>
- <span className={cn(
- isLate ? "text-red-600 font-medium" : "text-green-600 font-medium"
- )}>
- {formatDate(actualDate)}
- </span>
- </div>
- )}
- {!actualDate && planDate && (
- <div className="text-xs text-orange-600">
- 진행중
- </div>
- )}
- {isCompleted && (
- <div className="text-xs text-green-600">
- ✓ 완료
- </div>
- )}
- </div>
+ <span className={cn(
+ "text-sm",
+ isSelected && "text-blue-600 font-semibold"
+ )}>
+ {formatDate(date)}
+ </span>
)
}
@@ -140,36 +53,28 @@ export function getSimplifiedDocumentColumns({
setRowAction,
}: GetColumnsProps): ColumnDef<SimplifiedDocumentsView>[] {
- // 기본 컬럼들
- const baseColumns: ColumnDef<SimplifiedDocumentsView>[] = [
- // 체크박스 선택
+ const columns: ColumnDef<SimplifiedDocumentsView>[] = [
+ // 라디오 버튼 같은 체크박스 선택
{
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"
- />
+ <div className="flex items-center justify-center">
+ <span className="text-xs text-gray-500">선택</span>
+ </div>
),
+ cell: ({ row }) => {
+ const doc = row.original
+
+ return (
+ <SelectCell documentId={doc.documentId} />
+ )
+ },
size: 40,
enableSorting: false,
enableHiding: false,
},
- // 문서번호 + Drawing Kind
+ // 문서번호 (선택된 행 하이라이트 적용)
{
accessorKey: "docNumber",
header: ({ column }) => (
@@ -177,33 +82,19 @@ export function getSimplifiedDocumentColumns({
),
cell: ({ row }) => {
const doc = row.original
+
return (
- <div className="flex flex-col gap-1 items-start">
- <span className="font-mono text-sm font-medium">{doc.docNumber}</span>
- {doc.vendorDocNumber && (
- <span className="font-mono text-xs text-gray-500">
- 벤더: {doc.vendorDocNumber}
- </span>
- )}
- {doc.drawingKind && (
- <Badge
- variant="outline"
- className={cn("text-xs", getDrawingKindColor(doc.drawingKind))}
- >
- {getDrawingKindText(doc.drawingKind)}
- </Badge>
- )}
- </div>
+ <DocNumberCell doc={doc} />
)
},
- size: 140,
+ size: 120,
enableResizing: true,
meta: {
excelHeader: "문서번호"
},
},
- // 문서명 + 프로젝트/벤더 정보
+ // 문서명 (선택된 행 하이라이트 적용)
{
accessorKey: "title",
header: ({ column }) => (
@@ -211,148 +102,136 @@ export function getSimplifiedDocumentColumns({
),
cell: ({ row }) => {
const doc = row.original
+
return (
- <div className="min-w-0 flex-1">
- <div className="font-medium text-gray-900 truncate" title={doc.title}>
- {doc.title}
- </div>
- <div className="flex items-center gap-2 text-sm text-gray-500 mt-1">
- {doc.pic && (
- <span className="text-xs bg-gray-100 px-2 py-0.5 rounded">
- PIC: {doc.pic}
- </span>
- )}
- {doc.projectCode && (
- <div className="flex items-center gap-1">
- <Building className="w-3 h-3" />
- <span>{doc.projectCode}</span>
- </div>
- )}
- {doc.vendorName && (
- <div className="flex items-center gap-1">
- <Code className="w-3 h-3" />
- <span className="truncate max-w-[100px]">{doc.vendorName}</span>
- </div>
- )}
- </div>
- </div>
+ <TitleCell doc={doc} />
)
},
- size: 200,
enableResizing: true,
meta: {
excelHeader: "문서명"
},
},
- // 첫 번째 스테이지 정보
+ // 프로젝트 코드
{
- accessorKey: "firstStageName",
+ accessorKey: "projectCode",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="1차 스테이지" />
+ <DataTableColumnHeaderSimple column={column} title="프로젝트" />
),
cell: ({ row }) => {
- const doc = row.original
+ const projectCode = row.original.projectCode
+
return (
- <StageNameDisplay
- stageName={doc.firstStageName}
- drawingKind={doc.drawingKind}
- isFirst={true}
- />
+ <ProjectCodeCell projectCode={projectCode} documentId={row.original.documentId} />
)
},
- size: 130,
enableResizing: true,
meta: {
- excelHeader: "1차 스테이지"
+ excelHeader: "프로젝트"
},
},
- // 첫 번째 스테이지 날짜
+ // 1차 스테이지 그룹
{
- accessorKey: "firstStagePlanDate",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="1차 일정" />
- ),
- cell: ({ row }) => {
- const doc = row.original
+ id: "firstStageGroup",
+ header: ({ table }) => {
+ // 첫 번째 행의 firstStageName을 그룹 헤더로 사용
+ const firstRow = table.getRowModel().rows[0]?.original
+ const stageName = firstRow?.firstStageName || "1차 스테이지"
return (
- <StageDateInfo
- planDate={doc.firstStagePlanDate}
- actualDate={doc.firstStageActualDate}
- stageName={doc.firstStageName}
- />
- )
- },
- size: 140,
- enableResizing: true,
- meta: {
- excelHeader: "1차 일정"
- },
- },
-
- // 두 번째 스테이지 정보
- {
- accessorKey: "secondStageName",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="2차 스테이지" />
- ),
- cell: ({ row }) => {
- const doc = row.original
- return (
- <StageNameDisplay
- stageName={doc.secondStageName}
- drawingKind={doc.drawingKind}
- isFirst={false}
- />
+ <div className="text-center font-medium text-gray-700">
+ {stageName}
+ </div>
)
},
- size: 130,
- enableResizing: true,
- meta: {
- excelHeader: "2차 스테이지"
- },
+ columns: [
+ {
+ accessorKey: "firstStagePlanDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계획일" />
+ ),
+ cell: ({ row }) => {
+ return <FirstStagePlanDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "1차 계획일"
+ },
+ },
+ {
+ accessorKey: "firstStageActualDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="실제일" />
+ ),
+ cell: ({ row }) => {
+ return <FirstStageActualDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "1차 실제일"
+ },
+ },
+ ],
},
- // 두 번째 스테이지 날짜
+ // 2차 스테이지 그룹
{
- accessorKey: "secondStagePlanDate",
- header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="2차 일정" />
- ),
- cell: ({ row }) => {
- const doc = row.original
+ id: "secondStageGroup",
+ header: ({ table }) => {
+ // 첫 번째 행의 secondStageName을 그룹 헤더로 사용
+ const firstRow = table.getRowModel().rows[0]?.original
+ const stageName = firstRow?.secondStageName || "2차 스테이지"
return (
- <StageDateInfo
- planDate={doc.secondStagePlanDate}
- actualDate={doc.secondStageActualDate}
- stageName={doc.secondStageName}
- />
+ <div className="text-center font-medium text-gray-700">
+ {stageName}
+ </div>
)
},
- size: 140,
- enableResizing: true,
- meta: {
- excelHeader: "2차 일정"
- },
+ columns: [
+ {
+ accessorKey: "secondStagePlanDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계획일" />
+ ),
+ cell: ({ row }) => {
+ return <SecondStagePlanDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "2차 계획일"
+ },
+ },
+ {
+ accessorKey: "secondStageActualDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="실제일" />
+ ),
+ cell: ({ row }) => {
+ return <SecondStageActualDateCell row={row} />
+ },
+ enableResizing: true,
+ meta: {
+ excelHeader: "2차 실제일"
+ },
+ },
+ ],
},
// 첨부파일 수
{
accessorKey: "attachmentCount",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="첨부파일" />
+ <DataTableColumnHeaderSimple column={column} title="파일" />
),
cell: ({ row }) => {
const count = row.original.attachmentCount || 0
+
return (
- <div className="flex items-center gap-1">
- <FileText className="w-4 h-4 text-gray-400" />
- <span className="text-sm font-medium">{count}</span>
- </div>
+ <AttachmentCountCell count={count} documentId={row.original.documentId} />
)
},
- size: 80,
+ size: 60,
enableResizing: true,
meta: {
excelHeader: "첨부파일"
@@ -365,12 +244,11 @@ export function getSimplifiedDocumentColumns({
header: ({ column }) => (
<DataTableColumnHeaderSimple column={column} title="업데이트" />
),
- cell: ({ cell }) => (
- <span className="text-sm text-gray-600">
- {formatDateTime(cell.getValue() as Date)}
- </span>
- ),
- size: 140,
+ cell: ({ cell, row }) => {
+ return (
+ <UpdatedAtCell updatedAt={cell.getValue() as Date} documentId={row.original.documentId} />
+ )
+ },
enableResizing: true,
meta: {
excelHeader: "업데이트"
@@ -378,50 +256,208 @@ export function getSimplifiedDocumentColumns({
},
// 액션 버튼
- {
- 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,
- },
+ // {
+ // 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 baseColumns
+ return columns
+}
+
+// 개별 셀 컴포넌트들 (Context 사용)
+function SelectCell({ documentId }: { documentId: number }) {
+ const { selectedDocumentId, setSelectedDocumentId } = React.useContext(DocumentSelectionContext);
+ 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(DocumentSelectionContext);
+ 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(DocumentSelectionContext);
+ 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(DocumentSelectionContext);
+ 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 FirstStagePlanDateCell({ row }: { row: any }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContext);
+ const isSelected = selectedDocumentId === row.original.documentId;
+
+ return <DateDisplay date={row.original.firstStagePlanDate} isSelected={isSelected} />;
+}
+
+function FirstStageActualDateCell({ row }: { row: any }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContext);
+ 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(DocumentSelectionContext);
+ const isSelected = selectedDocumentId === row.original.documentId;
+
+ return <DateDisplay date={row.original.secondStagePlanDate} isSelected={isSelected} />;
+}
+
+function SecondStageActualDateCell({ row }: { row: any }) {
+ const { selectedDocumentId } = React.useContext(DocumentSelectionContext);
+ 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(DocumentSelectionContext);
+ 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(DocumentSelectionContext);
+ 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