diff --git a/frontend/app/(main)/COMPANY_16/project/progress/page.tsx b/frontend/app/(main)/COMPANY_16/project/progress/page.tsx index bcc7fa37..207fd6a1 100644 --- a/frontend/app/(main)/COMPANY_16/project/progress/page.tsx +++ b/frontend/app/(main)/COMPANY_16/project/progress/page.tsx @@ -22,6 +22,7 @@ import { PageHeader } from "@/components/common/PageHeader"; import { CompactFilterBar, CompactFilterField, CompactDateRange } from "@/components/common/CompactFilterBar"; import { ProjectInfoDialog, ProjectInfoData } from "@/components/project/ProjectInfoDialog"; import { projectMgmtApi, ProgressListFilter, ProgressRow } from "@/lib/api/projectMgmt"; +import { exportToExcel } from "@/lib/utils/excelExport"; // 진행관리 row → 정규화된 ProjectInfoData 매핑 const toProjectInfo = (r: ProgressRow): ProjectInfoData => ({ @@ -45,32 +46,32 @@ const toProjectInfo = (r: ProgressRow): ProjectInfoData => ({ const GRID_COLUMNS: DataGridColumn[] = [ { key: "project_no", label: "프로젝트번호", width: "w-[160px]", frozen: true }, // 프로젝트정보 그룹 - { key: "category_name", label: "주문유형", width: "w-[90px]", align: "center" }, - { key: "product_name", label: "제품구분", width: "w-[100px]", align: "left" }, - { key: "area_name", label: "국내/해외", width: "w-[90px]", align: "center" }, - { key: "reg_date", label: "접수일", width: "w-[110px]", align: "center" }, + { key: "category_name", label: "주문유형", width: "w-[115px]", align: "center" }, + { key: "product_name", label: "제품구분", width: "w-[115px]", align: "left" }, + { key: "area_name", label: "국내/해외", width: "w-[115px]", align: "center" }, + { key: "reg_date", label: "접수일", width: "w-[115px]", align: "center" }, { key: "customer_name", label: "고객사", width: "w-[140px]" }, - { key: "free_of_charge", label: "유/무상", width: "w-[80px]", align: "center" }, + { key: "free_of_charge", label: "유/무상", width: "w-[100px]", align: "center" }, { key: "product_item_code", label: "품번", width: "w-[150px]" }, { key: "product_item_name", label: "품명", width: "w-[180px]" }, { key: "serial_no", label: "S/N", width: "w-[150px]" }, - { key: "contract_qty", label: "수주수량", width: "w-[90px]", align: "right", formatNumber: true }, - { key: "req_del_date", label: "요청납기", width: "w-[110px]", align: "center" }, + { key: "contract_qty", label: "수주수량", width: "w-[115px]", align: "right", formatNumber: true }, + { key: "req_del_date", label: "요청납기", width: "w-[115px]", align: "center" }, // 설계 - { key: "ebom_status", label: "E-BOM", width: "w-[100px]", align: "center" }, + { key: "ebom_status", label: "E-BOM", width: "w-[115px]", align: "center" }, // 생산관리 - { key: "mbom_status", label: "M-BOM", width: "w-[100px]", align: "center" }, + { key: "mbom_status", label: "M-BOM", width: "w-[115px]", align: "center" }, // 구매 - { key: "order_date", label: "발주일", width: "w-[110px]", align: "center" }, - { key: "receiving_rate", label: "입고율", width: "w-[90px]", align: "right" }, + { key: "order_date", label: "발주일", width: "w-[115px]", align: "center" }, + { key: "receiving_rate", label: "입고율", width: "w-[100px]", align: "right" }, // 생산 - { key: "production_team_12", label: "제조1,2팀", width: "w-[100px]", align: "center" }, - { key: "production_team_3", label: "제조3팀", width: "w-[100px]", align: "center" }, + { key: "production_team_12", label: "제조1,2팀", width: "w-[120px]", align: "center" }, + { key: "production_team_3", label: "제조3팀", width: "w-[115px]", align: "center" }, // 장비 - { key: "assembly", label: "조립", width: "w-[90px]", align: "center" }, - { key: "verification", label: "검증", width: "w-[90px]", align: "center" }, + { key: "assembly", label: "조립", width: "w-[100px]", align: "center" }, + { key: "verification", label: "검증", width: "w-[100px]", align: "center" }, // 출하 - { key: "shipment_date", label: "출하일", width: "w-[110px]", align: "center" }, + { key: "shipment_date", label: "출하일", width: "w-[115px]", align: "center" }, ]; const CATEGORY_GROUP = "0000167"; // 주문유형 @@ -145,6 +146,24 @@ export default function ProjectProgressPage() { setTimeout(() => fetchList(), 0); }; + // ─── 하단 통계 ────────────────────────────────────────────── + // 프로젝트 건수 / 수주수량 합계 / 입고율 평균 + const progressSummary = useMemo(() => { + const count = rows.length; + const qtySum = rows.reduce((acc, r) => acc + Number(r.contract_qty || 0), 0); + // 입고율은 "85%" 같은 문자열 가능 → 숫자만 추출 + const rateNums = rows + .map((r) => parseFloat(String((r as any).receiving_rate ?? "").replace(/[^0-9.]/g, ""))) + .filter((n) => !Number.isNaN(n)); + const rateAvg = rateNums.length === 0 ? 0 : rateNums.reduce((a, b) => a + b, 0) / rateNums.length; + const intFmt = (n: number) => n.toLocaleString(); + return [ + { label: "프로젝트 건수", value: intFmt(count), suffix: "건" }, + { label: "수주수량 합계", value: intFmt(qtySum) }, + { label: "입고율 평균", value: rateAvg.toFixed(1), suffix: "%" }, + ]; + }, [rows]); + return (