Files
wace_rps/frontend/app/(main)/COMPANY_16/project/wbs-template/page.tsx
T
hjjeong 4f5dd8b47f 공용 — 영업 4 + 프로젝트 2 + 개발 5메뉴 PageHeader + CompactFilterBar 일괄 적용
총 11개 페이지를 동일한 페이지 구조 표준으로 마이그레이션. 페이지 메뉴명은
PageHeader 가 useMenu() 자동 매칭, 검색 영역은 CompactFilterBar/CompactFilterField,
날짜 범위는 CompactDateRange 로 통일. 모든 자체 grid 검색폼 + 자체 h1 + 자체 액션
버튼 그룹 제거.

영업관리 4:
  - sales/estimate (견적관리) — 7필드 + 결재상태 SmartSelect
  - sales/order    (주문서관리) — 9필드 (날짜 2종)
  - sales/sale     (판매관리)   — 10필드 (출하지시상태 SmartSelect)
  - sales/revenue  (매출관리)   — 11필드 (날짜 3종)

프로젝트관리 2:
  - project/progress     (진행관리)         — 11필드 (그리드 6→자동 wrap)
  - project/wbs-template (제품구분_WBS관리) — 1필드

개발관리 5:
  - development/part-regist  (PART 등록)      — 2필드 (자동완성) + 7 액션
  - development/part-search  (PART 조회)      — 2필드 + 5 액션
  - development/ebom-regist  (E-BOM 등록)     — 4필드 + 3 액션 (잔재 Field helper 제거)
  - development/ebom-search  (E-BOM 조회)     — 3필드 + 4 액션 (정/역전개)
  - development/change-list  (설계변경 리스트) — 8필드 (read-only)

DB:
  - menu_info.menu_desc 11개 메뉴 보강 (PageHeader 자동 표시)
  - docs/migration/common/menu_desc_sync.sql (멱등 UPDATE)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 17:10:07 +09:00

166 lines
5.5 KiB
TypeScript

"use client";
// 프로젝트관리 > 제품구분_WBS관리 (wace wbsTemplateMngList.jsp 1:1 이식)
// 원본:
// - JSP: /Users/jhj/wace_plm/WebContent/WEB-INF/view/project/wbsTemplateMngList.jsp (378줄)
// - 매퍼: wace_plm/src/com/pms/mapper/project.xml:5552 wbsTemplateMngGridList
// GAP: docs/migration/project/02-wbs-template.md
//
// 그리드: 5컬럼 (제품구분 / 제목 / WBS(folder) / 등록자 / 등록일)
// 검색: 제품구분 단일
// 등록/수정 통합 다이얼로그: WbsTemplateDialog (wace WBSExcelImportPopUp.jsp 1:1)
import React, { useCallback, useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Plus, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
import { CommCodeSelect } from "@/components/common/CommCodeSelect";
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";
const PRODUCT_GROUP = "0000001"; // 제품구분
const GRID_COLUMNS: DataGridColumn[] = [
{ key: "product_name", label: "제품구분", width: "w-[200px]", frozen: true },
{ key: "title", label: "제목", minWidth: "min-w-[260px]" },
{
key: "wbs_task_cnt",
label: "WBS",
width: "w-[100px]",
align: "center",
renderType: "folder", // wace fnc_getFolderIcon
},
{ key: "writer_title", label: "등록자", width: "w-[180px]" },
{ key: "reg_date_title", label: "등록일", width: "w-[130px]", align: "center" },
];
export default function WbsTemplatePage() {
const [rows, setRows] = useState<TemplateRow[]>([]);
const [loading, setLoading] = useState(false);
const [filterProduct, setFilterProduct] = useState<string>("");
const [checkedIds, setCheckedIds] = useState<string[]>([]);
// 다이얼로그 상태
const [dialogOpen, setDialogOpen] = useState(false);
const [editObjId, setEditObjId] = useState<string | null>(null);
const [defaultProduct, setDefaultProduct] = useState<string>("");
const fetchList = useCallback(async (product?: string) => {
setLoading(true);
try {
const data = await wbsTemplateApi.list(product || undefined);
setRows(data);
setCheckedIds([]);
} catch (e: any) {
toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패");
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchList();
}, [fetchList]);
const handleSearch = () => fetchList(filterProduct);
const handleReset = () => {
setFilterProduct("");
fetchList();
};
// 등록 (wace btnRegist click — product 선택 필수)
const handleRegist = () => {
if (!filterProduct) {
toast.error("제품은 필수값입니다. 제품을 선택해 주세요.");
return;
}
setEditObjId(null);
setDefaultProduct(filterProduct);
setDialogOpen(true);
};
// 수정 (wace fn_openWBSTaskListPopUp — WBS 폴더 컬럼 클릭)
const handleOpenEdit = (row: any) => {
setEditObjId(row.objid);
setDefaultProduct("");
setDialogOpen(true);
};
// 삭제 (wace fn_delete — 체크된 행 다건 삭제)
const handleDelete = async () => {
if (checkedIds.length === 0) {
toast.error("선택된 대상이 없습니다.");
return;
}
if (!confirm("삭제하시겠습니까?")) return;
try {
const res = await wbsTemplateApi.remove(checkedIds);
toast.success(res?.msg ?? "삭제하였습니다.");
fetchList(filterProduct);
} catch (e: any) {
toast.error(e?.response?.data?.message ?? e?.message ?? "삭제 실패");
}
};
// DataGrid 컬럼에 folder 클릭 핸들러 주입
const columns: DataGridColumn[] = GRID_COLUMNS.map((c) =>
c.key === "wbs_task_cnt" ? { ...c, onClick: handleOpenEdit } : c
);
return (
<div className="flex flex-col h-full gap-2 p-2">
<PageHeader actions={
<>
<Button size="sm" className="h-8 gap-1 text-xs" onClick={handleRegist}>
<Plus className="h-3.5 w-3.5" />
</Button>
<Button size="sm" variant="destructive" className="h-8 gap-1 text-xs" onClick={handleDelete} disabled={checkedIds.length === 0}>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</>
} />
<CompactFilterBar
loading={loading}
onSearch={handleSearch}
onReset={handleReset}
totalText={<> {rows.length.toLocaleString()}</>}
>
<CompactFilterField label="제품구분" width={200}>
<CommCodeSelect
groupId={PRODUCT_GROUP}
value={filterProduct}
onValueChange={setFilterProduct}
/>
</CompactFilterField>
</CompactFilterBar>
<div className="flex-1 min-h-0">
<DataGrid
columns={columns}
data={rows}
loading={loading}
showRowNumber
showCheckbox
checkedIds={checkedIds}
onCheckedChange={setCheckedIds}
emptyMessage="등록된 WBS 템플릿이 없습니다."
gridId="project-wbs-template"
/>
</div>
{/* 통합 팝업 */}
<WbsTemplateDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
templateObjId={editObjId}
defaultProduct={defaultProduct}
onSaved={() => fetchList(filterProduct)}
/>
</div>
);
}