diff options
Diffstat (limited to 'lib/pq/pq-review-table-new/vendors-table-columns.tsx')
| -rw-r--r-- | lib/pq/pq-review-table-new/vendors-table-columns.tsx | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/lib/pq/pq-review-table-new/vendors-table-columns.tsx b/lib/pq/pq-review-table-new/vendors-table-columns.tsx new file mode 100644 index 00000000..0491f1dc --- /dev/null +++ b/lib/pq/pq-review-table-new/vendors-table-columns.tsx @@ -0,0 +1,640 @@ +"use client" + +import * as React from "react" +import { type DataTableRowAction } from "@/types/table" +import { type ColumnDef } from "@tanstack/react-table" +import { Ellipsis, Eye, PaperclipIcon, FileEdit } from "lucide-react" + +import { formatDate } from "@/lib/utils" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header" +import { useRouter } from "next/navigation" + +// PQ 제출 타입 정의 +export interface PQSubmission { + id: number + pqNumber: string + type: string + status: string + requesterName: string | null // 요청자 이름 + createdAt: Date + updatedAt: Date + submittedAt: Date | null + approvedAt: Date | null + rejectedAt: Date | null + rejectReason: string | null + vendorId: number + vendorName: string + vendorCode: string + taxId: string + vendorStatus: string + projectId: number | null + projectName: string | null + projectCode: string | null + answerCount: number + attachmentCount: number + pqStatus: string + pqTypeLabel: string + investigation: { + id: number + investigationStatus: string + requesterName: string | null // 실사 요청자 이름 + evaluationType: "SITE_AUDIT" | "QM_SELF_AUDIT" | null + qmManagerId: number | null + qmManagerName: string | null // QM 담당자 이름 + qmManagerEmail: string | null // QM 담당자 이메일 + investigationAddress: string | null + investigationMethod: string | null + scheduledStartAt: Date | null + scheduledEndAt: Date | null + requestedAt: Date | null + confirmedAt: Date | null + completedAt: Date | null + forecastedAt: Date | null + evaluationScore: number | null + evaluationResult: "APPROVED" | "SUPPLEMENT" | "REJECTED" | null + investigationNotes: string | null + } | null + // 통합 상태를 위한 새 필드 + combinedStatus: { + status: string + label: string + variant: "default" | "outline" | "secondary" | "destructive" | "success" + } +} + +type NextRouter = ReturnType<typeof useRouter>; + +interface GetColumnsProps { + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<PQSubmission> | null>>; + router: NextRouter; +} + +// 상태에 따른 Badge 변형 결정 함수 +function getStatusBadge(status: string) { + switch (status) { + case "REQUESTED": + return <Badge variant="outline">요청됨</Badge> + case "IN_PROGRESS": + return <Badge variant="secondary">진행 중</Badge> + case "SUBMITTED": + return <Badge>제출됨</Badge> + case "APPROVED": + return <Badge variant="success">승인됨</Badge> + case "REJECTED": + return <Badge variant="destructive">거부됨</Badge> + default: + return <Badge variant="outline">{status}</Badge> + } +} + +/** + * tanstack table 컬럼 정의 + */ +export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef<PQSubmission>[] { + // ---------------------------------------------------------------- + // 1) select 컬럼 (체크박스) + // ---------------------------------------------------------------- + const selectColumn: ColumnDef<PQSubmission> = { + 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, + } + + // ---------------------------------------------------------------- + // 2) 일반 컬럼들 + // -------------------------- + // -------------------------------------- + + const pqNoColumn: ColumnDef<PQSubmission> = { + accessorKey: "pqNumber", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="PQ No." /> + ), + cell: ({ row }) => ( + <div className="flex flex-col"> + <span className="font-medium">{row.getValue("pqNumber")}</span> + </div> + ), + } + + // 협력업체 컬럼 + const vendorColumn: ColumnDef<PQSubmission> = { + accessorKey: "vendorName", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="협력업체" /> + ), + cell: ({ row }) => ( + <div className="flex flex-col"> + <span className="font-medium">{row.getValue("vendorName")}</span> + <span className="text-xs text-muted-foreground">{row.original.vendorCode ? row.original.vendorCode : "-"}/{row.original.taxId}</span> + </div> + ), + } + + // PQ 유형 컬럼 + const typeColumn: ColumnDef<PQSubmission> = { + accessorKey: "type", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="PQ 유형" /> + ), + cell: ({ row }) => { + return ( + <div className="flex items-center"> + <Badge variant={row.original.type === "PROJECT" ? "default" : "outline"}> + {row.original.pqTypeLabel} + </Badge> + </div> + ) + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)) + }, + } + + // 프로젝트 컬럼 + const projectColumn: ColumnDef<PQSubmission> = { + accessorKey: "projectName", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="프로젝트" /> + ), + cell: ({ row }) => { + const projectName = row.original.projectName + const projectCode = row.original.projectCode + + if (!projectName) { + return <span className="text-muted-foreground">-</span> + } + + return ( + <div className="flex flex-col"> + <span>{projectName}</span> + {projectCode && ( + <span className="text-xs text-muted-foreground">{projectCode}</span> + )} + </div> + ) + }, + } + + // 상태 컬럼 + const statusColumn: ColumnDef<PQSubmission> = { + accessorKey: "combinedStatus", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="진행현황" /> + ), + cell: ({ row }) => { + const combinedStatus = getCombinedStatus(row.original); + return <Badge variant={combinedStatus.variant}>{combinedStatus.label}</Badge>; + }, + filterFn: (row, id, value) => { + const combinedStatus = getCombinedStatus(row.original); + return value.includes(combinedStatus.status); + }, + }; + + // PQ 상태와 실사 상태를 결합하는 헬퍼 함수 + function getCombinedStatus(submission: PQSubmission) { + // PQ가 승인되지 않은 경우, PQ 상태를 우선 표시 + if (submission.status !== "APPROVED") { + switch (submission.status) { + case "REQUESTED": + return { status: "PQ_REQUESTED", label: "PQ 요청됨", variant: "outline" as const }; + case "IN_PROGRESS": + return { status: "PQ_IN_PROGRESS", label: "PQ 진행 중", variant: "secondary" as const }; + case "SUBMITTED": + return { status: "PQ_SUBMITTED", label: "PQ 제출됨", variant: "default" as const }; + case "REJECTED": + return { status: "PQ_REJECTED", label: "PQ 거부됨", variant: "destructive" as const }; + default: + return { status: submission.status, label: submission.status, variant: "outline" as const }; + } + } + + // PQ가 승인되었지만 실사가 없는 경우 + if (!submission.investigation) { + return { status: "PQ_APPROVED", label: "PQ 승인됨", variant: "success" as const }; + } + + // PQ가 승인되고 실사가 있는 경우 + switch (submission.investigation.investigationStatus) { + case "PLANNED": + return { status: "INVESTIGATION_PLANNED", label: "실사 계획됨", variant: "outline" as const }; + case "IN_PROGRESS": + return { status: "INVESTIGATION_IN_PROGRESS", label: "실사 진행 중", variant: "secondary" as const }; + case "COMPLETED": + // 실사 완료 후 평가 결과에 따라 다른 상태 표시 + if (submission.investigation.evaluationResult) { + switch (submission.investigation.evaluationResult) { + case "APPROVED": + return { status: "INVESTIGATION_APPROVED", label: "실사 승인", variant: "success" as const }; + case "SUPPLEMENT": + return { status: "INVESTIGATION_SUPPLEMENT", label: "실사 보완필요", variant: "secondary" as const }; + case "REJECTED": + return { status: "INVESTIGATION_REJECTED", label: "실사 불가", variant: "destructive" as const }; + default: + return { status: "INVESTIGATION_COMPLETED", label: "실사 완료", variant: "default" as const }; + } + } + return { status: "INVESTIGATION_COMPLETED", label: "실사 완료", variant: "default" as const }; + case "CANCELED": + return { status: "INVESTIGATION_CANCELED", label: "실사 취소됨", variant: "destructive" as const }; + default: + return { + status: `INVESTIGATION_${submission.investigation.investigationStatus}`, + label: `실사 ${submission.investigation.investigationStatus}`, + variant: "outline" as const + }; + } + } + + const evaluationTypeColumn: ColumnDef<PQSubmission> = { + accessorKey: "evaluationType", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="평가 유형" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.evaluationType) { + return <span className="text-muted-foreground">-</span>; + } + + switch (investigation.evaluationType) { + case "SITE_AUDIT": + return <Badge variant="outline">실사의뢰평가</Badge>; + case "QM_SELF_AUDIT": + return <Badge variant="secondary">QM자체평가</Badge>; + default: + return <span>{investigation.evaluationType}</span>; + } + }, + filterFn: (row, id, value) => { + const investigation = row.original.investigation; + if (!investigation || !investigation.evaluationType) return value.includes("null"); + return value.includes(investigation.evaluationType); + }, + }; + + + const evaluationResultColumn: ColumnDef<PQSubmission> = { + accessorKey: "evaluationResult", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="평가 결과" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.evaluationResult) { + return <span className="text-muted-foreground">-</span>; + } + + switch (investigation.evaluationResult) { + case "APPROVED": + return <Badge variant="success">승인</Badge>; + case "SUPPLEMENT": + return <Badge variant="secondary">보완</Badge>; + case "REJECTED": + return <Badge variant="destructive">불가</Badge>; + default: + return <span>{investigation.evaluationResult}</span>; + } + }, + filterFn: (row, id, value) => { + const investigation = row.original.investigation; + if (!investigation || !investigation.evaluationResult) return value.includes("null"); + return value.includes(investigation.evaluationResult); + }, + }; + + // 답변 수 컬럼 + const answerCountColumn: ColumnDef<PQSubmission> = { + accessorKey: "answerCount", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="답변 수" /> + ), + cell: ({ row }) => { + return ( + <div className="flex items-center gap-2"> + <span>{row.original.answerCount}</span> + </div> + ) + }, + } + + const investigationAddressColumn: ColumnDef<PQSubmission> = { + accessorKey: "investigationAddress", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="실사 주소" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.evaluationType) { + return <span className="text-muted-foreground">-</span>; + } + + return ( + <div className="flex items-center gap-2"> + <span>{investigation.investigationAddress}</span> + </div> + ) + }, + } + + const investigationNotesColumn: ColumnDef<PQSubmission> = { + accessorKey: "investigationNotes", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="QM 의견" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.investigationNotes) { + return <span className="text-muted-foreground">-</span>; + } + + return ( + <div className="flex items-center gap-2"> + <span>{investigation.investigationNotes}</span> + </div> + ) + }, + } + + + const investigationRequestedAtColumn: ColumnDef<PQSubmission> = { + accessorKey: "investigationRequestedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="실사 의뢰일" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.requestedAt) { + return <span className="text-muted-foreground">-</span>; + } + const dateVal = investigation.requestedAt + + return ( + <div className="flex items-center gap-2"> + <span>{dateVal ? formatDate(dateVal, 'KR') : "-"}</span> + </div> + ) + }, + } + + + const investigationForecastedAtColumn: ColumnDef<PQSubmission> = { + accessorKey: "investigationForecastedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="실사 예정일" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.forecastedAt) { + return <span className="text-muted-foreground">-</span>; + } + const dateVal = investigation.forecastedAt + + return ( + <div className="flex items-center gap-2"> + <span>{dateVal ? formatDate(dateVal, 'KR') : "-"}</span> + </div> + ) + }, + } + + const investigationConfirmedAtColumn: ColumnDef<PQSubmission> = { + accessorKey: "investigationConfirmedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="실사 확정일" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.confirmedAt) { + return <span className="text-muted-foreground">-</span>; + } + const dateVal = investigation.confirmedAt + + return ( + <div className="flex items-center gap-2"> + <span>{dateVal ? formatDate(dateVal, 'KR') : "-"}</span> + </div> + ) + }, + } + + const investigationCompletedAtColumn: ColumnDef<PQSubmission> = { + accessorKey: "investigationCompletedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="실제 실사일" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.completedAt) { + return <span className="text-muted-foreground">-</span>; + } + const dateVal = investigation.completedAt + + return ( + <div className="flex items-center gap-2"> + <span>{dateVal ? formatDate(dateVal, 'KR') : "-"}</span> + </div> + ) + }, + } + + // 제출일 컬럼 + const createdAtColumn: ColumnDef<PQSubmission> = { + accessorKey: "createdAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="PQ 전송일" /> + ), + cell: ({ row }) => { + const dateVal = row.original.createdAt as Date + return formatDate(dateVal, 'KR') + }, + } + + // 제출일 컬럼 + const submittedAtColumn: ColumnDef<PQSubmission> = { + accessorKey: "submittedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="PQ 회신일" /> + ), + cell: ({ row }) => { + const dateVal = row.original.submittedAt as Date + return dateVal ? formatDate(dateVal, 'KR') : "-" + }, + } + + // 승인/거부일 컬럼 + const approvalDateColumn: ColumnDef<PQSubmission> = { + accessorKey: "approvedAt", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="PQ 승인/거부일" /> + ), + cell: ({ row }) => { + if (row.original.approvedAt) { + return <span className="text-green-600">{formatDate(row.original.approvedAt)}</span> + } + if (row.original.rejectedAt) { + return <span className="text-red-600">{formatDate(row.original.rejectedAt)}</span> + } + return "-" + }, + } + + // ---------------------------------------------------------------- + // 3) actions 컬럼 (Dropdown 메뉴) + // ---------------------------------------------------------------- + const actionsColumn: ColumnDef<PQSubmission> = { + id: "actions", + enableHiding: false, + cell: function Cell({ row }) { + const pq = row.original + const isSubmitted = pq.status === "SUBMITTED" + const reviewUrl = `/evcp/pq_new/${pq.vendorId}/${pq.id}` + + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + aria-label="Open menu" + variant="ghost" + className="flex size-8 p-0 data-[state=open]:bg-muted" + > + <Ellipsis className="size-4" aria-hidden="true" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="w-40"> + <DropdownMenuItem + onSelect={() => { + router.push(reviewUrl); + }} + > + {isSubmitted ? ( + <> + <FileEdit className="mr-2 h-4 w-4" /> + 검토 + </> + ) : ( + <> + <Eye className="mr-2 h-4 w-4" /> + 보기 + </> + )} + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ) + }, + size: 40, + } + + // 요청자 컬럼 추가 +const requesterColumn: ColumnDef<PQSubmission> = { + accessorKey: "requesterName", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="PQ/실사 요청자" /> + ), + cell: ({ row }) => { + // PQ 요청자와 실사 요청자를 모두 표시 + const pqRequesterName = row.original.requesterName; + const investigationRequesterName = row.original.investigation?.requesterName; + + // 상태에 따라 적절한 요청자 표시 + const status = getCombinedStatus(row.original).status; + + if (status.startsWith('INVESTIGATION_') && investigationRequesterName) { + return <span>{investigationRequesterName}</span>; + } + + return pqRequesterName + ? <span>{pqRequesterName}</span> + : <span className="text-muted-foreground">-</span>; + }, +}; +const qmManagerColumn: ColumnDef<PQSubmission> = { + accessorKey: "qmManager", + header: ({ column }) => ( + <DataTableColumnHeader column={column} title="QM 담당자" /> + ), + cell: ({ row }) => { + const investigation = row.original.investigation; + + if (!investigation || !investigation.qmManagerName) { + return <span className="text-muted-foreground">-</span>; + } + + return ( + <div className="flex flex-col"> + <span>{investigation.qmManagerName}</span> + {investigation.qmManagerEmail && ( + <span className="text-xs text-muted-foreground">{investigation.qmManagerEmail}</span> + )} + </div> + ); + }, +}; + + + // ---------------------------------------------------------------- + // 4) 최종 컬럼 배열 + // ---------------------------------------------------------------- + return [ + selectColumn, + statusColumn, // 통합된 진행현황 컬럼 + pqNoColumn, + vendorColumn, + investigationAddressColumn, + typeColumn, + projectColumn, + createdAtColumn, + submittedAtColumn, + approvalDateColumn, + answerCountColumn, + evaluationTypeColumn, // 평가 유형 컬럼 + investigationForecastedAtColumn, + investigationRequestedAtColumn, + investigationConfirmedAtColumn, + investigationCompletedAtColumn, + evaluationResultColumn, // 평가 결과 컬럼 + requesterColumn, + qmManagerColumn, + investigationNotesColumn, + actionsColumn, + ]; +}
\ No newline at end of file |
