From 6a1813719a53b2eef22c4a719a261ca6501090e7 Mon Sep 17 00:00:00 2001 From: hjjeong Date: Thu, 14 May 2026 14:56:54 +0900 Subject: [PATCH] =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=E2=80=94=20=EC=A7=84=ED=96=89=EA=B4=80=EB=A6=AC/WB?= =?UTF-8?q?S=ED=85=9C=ED=94=8C=EB=A6=BF=EC=97=90=20logicstudio=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20DataGrid=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 영업관리(fc959d88)와 동일 패턴을 프로젝트관리 2개 메뉴로 확장: 공통 DataGrid props: - gridId 는 기존(project-progress-wbslist3 / project-wbs-template) 그대로 유지 - showColumnSettings, paginationStyle="range", pageSizeOptions=[10,15,20,50,100] - onRefresh = fetchList(혹은 fetchList(filterProduct)), onDownload = exportToExcel(라벨 매핑) - showChart 도메인별 summaryStats: - 진행관리: 프로젝트 건수 / 수주수량 합계 / 입고율 평균(% 문자열 파싱) - WBS 템플릿: 템플릿 건수 / WBS 작업 합계 / 평균 WBS 작업수 WBS 템플릿 systemColumnKeys: writer_title, reg_date_title (등록자/등록일 시스템 영역) 컬럼 폭 보정: - ⋮⋮ 드래그 핸들 추가로 좁아진 4글자 한국어/영문 라벨 95~120px 로 확대 - 진행관리: 주문유형·제품구분·국내/해외·요청납기·E-BOM·M-BOM·제조1,2팀·제조3팀 등 - WBS: WBS 컬럼 100→115, 등록일 130→140 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../COMPANY_16/project/progress/page.tsx | 66 ++++++++++++++----- .../COMPANY_16/project/wbs-template/page.tsx | 37 ++++++++++- 2 files changed, 84 insertions(+), 19 deletions(-) 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 (
{ + if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } + const exportRows = rows.map((r) => { + const out: Record = {}; + GRID_COLUMNS.forEach((col) => { out[col.label] = (r as any)[col.key] ?? ""; }); + return out; + }); + exportToExcel(exportRows, "진행관리.xlsx", "진행관리"); + }} + showChart />
diff --git a/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx b/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx index 6c5c6565..b0918928 100644 --- a/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx +++ b/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx @@ -10,7 +10,7 @@ // 검색: 제품구분 단일 // 등록/수정 통합 다이얼로그: WbsTemplateDialog (wace WBSExcelImportPopUp.jsp 1:1) -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; import { Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; @@ -20,6 +20,7 @@ import { PageHeader } from "@/components/common/PageHeader"; import { CompactFilterBar, CompactFilterField } from "@/components/common/CompactFilterBar"; import { wbsTemplateApi, TemplateRow } from "@/lib/api/wbsTemplate"; import { WbsTemplateDialog } from "@/components/project/WbsTemplateDialog"; +import { exportToExcel } from "@/lib/utils/excelExport"; const PRODUCT_GROUP = "0000001"; // 제품구분 @@ -29,12 +30,12 @@ const GRID_COLUMNS: DataGridColumn[] = [ { key: "wbs_task_cnt", label: "WBS", - width: "w-[100px]", + width: "w-[115px]", align: "center", renderType: "folder", // wace fnc_getFolderIcon }, { key: "writer_title", label: "등록자", width: "w-[180px]" }, - { key: "reg_date_title", label: "등록일", width: "w-[130px]", align: "center" }, + { key: "reg_date_title", label: "등록일", width: "w-[140px]", align: "center" }, ]; export default function WbsTemplatePage() { @@ -110,6 +111,20 @@ export default function WbsTemplatePage() { c.key === "wbs_task_cnt" ? { ...c, onClick: handleOpenEdit } : c ); + // ─── 하단 통계 ────────────────────────────────────────────── + // 템플릿 건수 / WBS 작업 합계 / 평균 WBS 작업수 + const templateSummary = useMemo(() => { + const count = rows.length; + const taskSum = rows.reduce((acc, r) => acc + Number((r as any).wbs_task_cnt || 0), 0); + const taskAvg = count === 0 ? 0 : taskSum / count; + const intFmt = (n: number) => n.toLocaleString(); + return [ + { label: "템플릿 건수", value: intFmt(count), suffix: "건" }, + { label: "WBS 작업 합계", value: intFmt(taskSum) }, + { label: "평균 WBS 작업수", value: taskAvg.toFixed(1) }, + ]; + }, [rows]); + return (
fetchList(filterProduct)} + onDownload={() => { + if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } + const exportRows = rows.map((r) => { + const out: Record = {}; + GRID_COLUMNS.forEach((col) => { out[col.label] = (r as any)[col.key] ?? ""; }); + return out; + }); + exportToExcel(exportRows, "WBS_템플릿.xlsx", "WBS_템플릿"); + }} + showChart />