summaryrefslogtreecommitdiff
path: root/lib/basic-contract/status
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-27 12:06:26 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-27 12:06:26 +0000
commit7548e2ad6948f1c6aa102fcac408bc6c9c0f9796 (patch)
tree8e66703ec821888ad51dcc242a508813a027bf71 /lib/basic-contract/status
parent7eac558470ef179dad626a8e82db5784fe86a556 (diff)
(대표님, 최겸) 기본계약, 입찰, 파일라우트, 계약서명라우트, 인포메이션, 메뉴설정, PQ(메일템플릿 관련)
Diffstat (limited to 'lib/basic-contract/status')
-rw-r--r--lib/basic-contract/status/basic-contract-columns.tsx363
-rw-r--r--lib/basic-contract/status/basic-contract-table.tsx44
-rw-r--r--lib/basic-contract/status/basicContract-table-toolbar-actions.tsx4
3 files changed, 263 insertions, 148 deletions
diff --git a/lib/basic-contract/status/basic-contract-columns.tsx b/lib/basic-contract/status/basic-contract-columns.tsx
index cc9d9bff..8ae8fa1e 100644
--- a/lib/basic-contract/status/basic-contract-columns.tsx
+++ b/lib/basic-contract/status/basic-contract-columns.tsx
@@ -8,26 +8,34 @@ import { formatDateTime } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header"
-import {
- FileActionsDropdown,
- FileNameLink
-} from "@/components/ui/file-actions"
+import { Button } from "@/components/ui/button"
+import { MoreHorizontal, Eye, FileText, Calendar } from "lucide-react"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { useRouter } from "next/navigation"
+import { BasicContractTemplateStatsView } from "@/db/schema"
+
+type NextRouter = ReturnType<typeof useRouter>;
-import { basicContractColumnsConfig } from "@/config/basicContractColumnsConfig"
-import { BasicContractView } from "@/db/schema"
interface GetColumnsProps {
- setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<BasicContractView> | null>>
+ setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<BasicContractTemplateStatsView> | null>>
+ router: NextRouter;
+
}
/**
- * 공용 파일 다운로드 유틸리티를 사용하는 간소화된 컬럼 정의
+ * BasicContractTemplateStatsView용 컬럼 정의
*/
-export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<BasicContractView>[] {
+export function getColumns({ setRowAction, router }: GetColumnsProps): ColumnDef<BasicContractTemplateStatsView>[] {
// ----------------------------------------------------------------
// 1) select 컬럼 (체크박스)
// ----------------------------------------------------------------
- const selectColumn: ColumnDef<BasicContractView> = {
+ const selectColumn: ColumnDef<BasicContractTemplateStatsView> = {
id: "select",
header: ({ table }) => (
<Checkbox
@@ -54,143 +62,252 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<BasicCo
}
// ----------------------------------------------------------------
- // 2) 파일 다운로드 컬럼 (공용 컴포넌트 사용)
+ // 2) Actions 컬럼
// ----------------------------------------------------------------
- const downloadColumn: ColumnDef<BasicContractView> = {
- id: "download",
- header: "",
+ const actionsColumn: ColumnDef<BasicContractTemplateStatsView> = {
+ id: "actions",
+ header: "작업",
cell: ({ row }) => {
- const template = row.original;
-
- if (!template.filePath || !template.fileName) {
- return null;
- }
+ const template = row.original
+ const detailUrl = `/evcp/basic-contract/${template.templateId}`;
return (
- <FileActionsDropdown
- filePath={template.filePath}
- fileName={template.fileName}
- variant="ghost"
- size="icon"
- />
- );
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="ghost" className="h-8 w-8 p-0">
+ <span className="sr-only">Open menu</span>
+ <MoreHorizontal className="h-4 w-4" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent align="end">
+ <DropdownMenuItem onClick={() => router.push(detailUrl)}>
+ <Eye className="mr-2 h-4 w-4" />
+ 상세 보기
+ </DropdownMenuItem>
+ <DropdownMenuItem onClick={() => setRowAction({ type: "view", row })}>
+ <FileText className="mr-2 h-4 w-4" />
+ 템플릿 보기
+ </DropdownMenuItem>
+ </DropdownMenuContent>
+ </DropdownMenu>
+ )
},
- maxSize: 30,
enableSorting: false,
+ enableHiding: false,
+ maxSize: 80,
}
// ----------------------------------------------------------------
- // 3) 일반 컬럼들을 "그룹"별로 묶어 중첩 columns 생성
+ // 3) 기본 정보 컬럼들
// ----------------------------------------------------------------
- const groupMap: Record<string, ColumnDef<BasicContractView>[]> = {}
-
- basicContractColumnsConfig.forEach((cfg) => {
- const groupName = cfg.group || "_noGroup"
-
- if (!groupMap[groupName]) {
- groupMap[groupName] = []
- }
+ const basicInfoColumns: ColumnDef<BasicContractTemplateStatsView>[] = [
+ {
+ accessorKey: "templateName",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="템플릿명" />
+ ),
+ cell: ({ row }) => {
+ const name = row.getValue("templateName") as string
+ return (
+ <div className="font-medium">
+ {name}
+ </div>
+ )
+ },
+ minSize: 200,
+ },
+ {
+ accessorKey: "revision",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="리비전" />
+ ),
+ cell: ({ row }) => {
+ const revision = row.getValue("revision") as number
+ return (
+ <Badge variant="outline">
+ v{revision}
+ </Badge>
+ )
+ },
+ minSize: 80,
+ },
+ ]
- const childCol: ColumnDef<BasicContractView> = {
- accessorKey: cfg.id,
- enableResizing: true,
+ // ----------------------------------------------------------------
+ // 4) 발송/처리 현황 컬럼들
+ // ----------------------------------------------------------------
+ const processStatusColumns: ColumnDef<BasicContractTemplateStatsView>[] = [
+ {
+ accessorKey: "totalSentCount",
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title={cfg.label} />
+ <DataTableColumnHeaderSimple column={column} title="발송건수" />
),
- meta: {
- excelHeader: cfg.excelHeader,
- group: cfg.group,
- type: cfg.type,
+ cell: ({ row }) => {
+ const count = row.getValue("totalSentCount") as number
+ return (
+ <div className="text-center font-medium">
+ {count.toLocaleString()}
+ </div>
+ )
},
- cell: ({ row, cell }) => {
- // 날짜 형식 처리
- if (cfg.id === "createdAt" || cfg.id === "updatedAt") {
- const dateVal = cell.getValue() as Date
- return formatDateTime(dateVal, "KR")
- }
+ minSize: 100,
+ },
+ {
+ accessorKey: "overdueCount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="지연건수" />
+ ),
+ cell: ({ row }) => {
+ const count = row.getValue("overdueCount") as number
+ const total = row.getValue("totalSentCount") as number
+ const isHigh = total > 0 && (count / total) > 0.2
- // Status 컬럼에 Badge 적용 (확장)
- if (cfg.id === "status") {
- const status = row.getValue(cfg.id) as string
-
- let variant: "default" | "secondary" | "destructive" | "outline" = "secondary";
- let label = status;
-
- switch (status) {
- case "ACTIVE":
- variant = "default";
- label = "활성";
- break;
- case "INACTIVE":
- variant = "secondary";
- label = "비활성";
- break;
- case "PENDING":
- variant = "outline";
- label = "대기중";
- break;
- case "COMPLETED":
- variant = "default";
- label = "완료";
- break;
- default:
- variant = "secondary";
- label = status;
- }
-
- return <Badge variant={variant}>{label}</Badge>
- }
-
- // ✅ 파일 이름 컬럼 (공용 컴포넌트 사용)
- if (cfg.id === "fileName") {
- const fileName = cell.getValue() as string;
- const filePath = row.original.filePath;
-
- if (fileName && filePath) {
- return (
- <FileNameLink
- filePath={filePath}
- fileName={fileName}
- maxLength={200}
- showIcon={true}
- />
- );
- }
- return fileName || "";
- }
-
- // 나머지 컬럼은 그대로 값 표시
- return row.getValue(cfg.id) ?? ""
+ return (
+ <div className={`text-center font-medium ${isHigh ? 'text-red-600' : 'text-gray-900'}`}>
+ {count.toLocaleString()}
+ </div>
+ )
},
- minSize: 80,
- }
+ minSize: 100,
+ },
+ {
+ accessorKey: "unsignedCount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="미서명건수" />
+ ),
+ cell: ({ row }) => {
+ const count = row.getValue("unsignedCount") as number
+ return (
+ <div className="text-center font-medium text-orange-600">
+ {count.toLocaleString()}
+ </div>
+ )
+ },
+ minSize: 120,
+ },
+ ]
- groupMap[groupName].push(childCol)
- })
+ // ----------------------------------------------------------------
+ // 5) 법무검토 현황 컬럼들
+ // ----------------------------------------------------------------
+ const legalReviewColumns: ColumnDef<BasicContractTemplateStatsView>[] = [
+ {
+ accessorKey: "legalRequestCount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="법무요청" />
+ ),
+ cell: ({ row }) => {
+ const count = row.getValue("legalRequestCount") as number
+ return (
+ <div className="text-center font-medium text-purple-600">
+ {count.toLocaleString()}
+ </div>
+ )
+ },
+ minSize: 100,
+ },
+ {
+ accessorKey: "legalCompletedCount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="법무완료" />
+ ),
+ cell: ({ row }) => {
+ const count = row.getValue("legalCompletedCount") as number
+ return (
+ <div className="text-center font-medium text-indigo-600">
+ {count.toLocaleString()}
+ </div>
+ )
+ },
+ minSize: 100,
+ },
+ {
+ accessorKey: "contractCompletedCount",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="계약완료" />
+ ),
+ cell: ({ row }) => {
+ const count = row.getValue("contractCompletedCount") as number
+ return (
+ <div className="text-center font-medium text-indigo-600">
+ {count.toLocaleString()}
+ </div>
+ )
+ },
+ minSize: 100,
+ },
+ ]
// ----------------------------------------------------------------
- // 4) groupMap에서 실제 상위 컬럼(그룹)을 만들기
+ // 6) 성과 지표 컬럼들
// ----------------------------------------------------------------
- const nestedColumns: ColumnDef<BasicContractView>[] = []
+ const performanceColumns: ColumnDef<BasicContractTemplateStatsView>[] = [
+ {
+ accessorKey: "avgProcessingDays",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="평균처리일" />
+ ),
+ cell: ({ row }) => {
+ const days = row.getValue("avgProcessingDays") as number | null
+ if (!days) return "-"
+
+ const rounded = Math.round(days * 10) / 10
+ const isGood = rounded <= 5
+ const isWarning = rounded > 5 && rounded <= 10
+
+ let colorClass = "text-gray-900"
+ if (isGood) colorClass = "text-green-600"
+ else if (isWarning) colorClass = "text-orange-600"
+ else colorClass = "text-red-600"
+
+ return (
+ <div className={`text-center font-medium ${colorClass}`}>
+ {rounded}일
+ </div>
+ )
+ },
+ minSize: 100,
+ },
+ ]
- Object.entries(groupMap).forEach(([groupName, colDefs]) => {
- if (groupName === "_noGroup") {
- nestedColumns.push(...colDefs)
- } else {
- nestedColumns.push({
- id: groupName,
- header: groupName,
- columns: colDefs,
- })
- }
- })
+ // ----------------------------------------------------------------
+ // 7) 시간 정보 컬럼들
+ // ----------------------------------------------------------------
+ const timeInfoColumns: ColumnDef<BasicContractTemplateStatsView>[] = [
+ {
+ accessorKey: "templateCreatedAt",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="생성일" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("templateCreatedAt") as Date
+ return formatDateTime(date, "KR")
+ },
+ minSize: 120,
+ },
+ {
+ accessorKey: "lastActivityDate",
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="최근활동" />
+ ),
+ cell: ({ row }) => {
+ const date = row.getValue("lastActivityDate") as Date | null
+ return date ? formatDateTime(date, "KR") : "-"
+ },
+ minSize: 120,
+ },
+ ]
// ----------------------------------------------------------------
- // 5) 최종 컬럼 배열
+ // 8) 최종 컬럼 배열 (평면 구조)
// ----------------------------------------------------------------
return [
selectColumn,
- downloadColumn, // ✅ 공용 파일 액션 컴포넌트 사용
- ...nestedColumns,
+ ...basicInfoColumns,
+ ...processStatusColumns,
+ ...legalReviewColumns,
+ ...performanceColumns,
+ ...timeInfoColumns,
+ actionsColumn,
]
-}
+} \ No newline at end of file
diff --git a/lib/basic-contract/status/basic-contract-table.tsx b/lib/basic-contract/status/basic-contract-table.tsx
index 22845144..07707ff0 100644
--- a/lib/basic-contract/status/basic-contract-table.tsx
+++ b/lib/basic-contract/status/basic-contract-table.tsx
@@ -14,9 +14,9 @@ import type {
import { toast } from "sonner";
import { getColumns } from "./basic-contract-columns";
import { getBasicContracts } from "../service";
-import { BasicContractView } from "@/db/schema";
+import { BasicContractTemplateStatsView } from "@/db/schema";
import { BasicContractTableToolbarActions } from "./basicContract-table-toolbar-actions";
-
+import { useRouter } from "next/navigation"
interface BasicTemplateTableProps {
promises: Promise<
@@ -26,38 +26,36 @@ interface BasicTemplateTableProps {
>
}
-
export function BasicContractsTable({ promises }: BasicTemplateTableProps) {
-
const [rowAction, setRowAction] =
- React.useState<DataTableRowAction<BasicContractView> | null>(null)
-
+ React.useState<DataTableRowAction<BasicContractTemplateStatsView> | null>(null)
+ const router = useRouter()
const [{ data, pageCount }] =
React.use(promises)
// 컬럼 설정 - 외부 파일에서 가져옴
const columns = React.useMemo(
- () => getColumns({ setRowAction }),
- [setRowAction]
+ () => getColumns({ setRowAction,router }),
+ [setRowAction,router]
)
// config 기반으로 필터 필드 설정
- const advancedFilterFields: DataTableAdvancedFilterField<BasicContractView>[] = [
+ const advancedFilterFields: DataTableAdvancedFilterField<BasicContractTemplateStatsView>[] = [
{ id: "templateName", label: "템플릿명", type: "text" },
- {
- id: "status", label: "상태", type: "select", options: [
- { label: "활성", value: "ACTIVE" },
- { label: "비활성", value: "INACTIVE" },
- ]
- },
- { id: "userName", label: "요청자", type: "text" },
- { id: "vendorName", label: "업체명", type: "text" },
- { id: "vendorCode", label: "업체코드", type: "text" },
- { id: "vendorEmail", label: "업체대표이메일", type: "text" },
- { id: "createdAt", label: "생성일", type: "date" },
- { id: "updatedAt", label: "수정일", type: "date" },
+ { id: "revision", label: "리비전", type: "number" },
+ { id: "validityPeriod", label: "유효기간(개월)", type: "number" },
+ { id: "totalSentCount", label: "발송건수", type: "number" },
+ { id: "overdueCount", label: "지연건수", type: "number" },
+ { id: "unsignedCount", label: "미서명건수", type: "number" },
+ { id: "legalRequestCount", label: "법무요청건수", type: "number" },
+ { id: "legalCompletedCount", label: "법무완료건수", type: "number" },
+ { id: "contractCompletedCount", label: "계약완료건수", type: "number" },
+ { id: "rejectedCount", label: "거절건수", type: "number" },
+ { id: "avgProcessingDays", label: "평균처리일", type: "number" },
+ { id: "templateCreatedAt", label: "템플릿 생성일", type: "date" },
+ { id: "lastActivityDate", label: "최근활동일", type: "date" },
];
const { table } = useDataTable({
@@ -68,10 +66,10 @@ export function BasicContractsTable({ promises }: BasicTemplateTableProps) {
enablePinning: true,
enableAdvancedFilter: true,
initialState: {
- sorting: [{ id: "createdAt", desc: true }],
+ sorting: [{ id: "lastActivityDate", desc: true }],
columnPinning: { right: ["actions"] },
},
- getRowId: (originalRow) => String(originalRow.id),
+ getRowId: (originalRow) => String(originalRow.templateId),
shallow: false,
clearOnDefault: true,
})
diff --git a/lib/basic-contract/status/basicContract-table-toolbar-actions.tsx b/lib/basic-contract/status/basicContract-table-toolbar-actions.tsx
index cee94790..a74a6e4d 100644
--- a/lib/basic-contract/status/basicContract-table-toolbar-actions.tsx
+++ b/lib/basic-contract/status/basicContract-table-toolbar-actions.tsx
@@ -7,10 +7,10 @@ import { Download, Upload } from "lucide-react"
import { exportTableToExcel } from "@/lib/export"
import { Button } from "@/components/ui/button"
-import { BasicContractView } from "@/db/schema"
+import { BasicContractTemplateStatsView } from "@/db/schema"
interface TemplateTableToolbarActionsProps {
- table: Table<BasicContractView>
+ table: Table<BasicContractTemplateStatsView>
}
export function BasicContractTableToolbarActions({ table }: TemplateTableToolbarActionsProps) {