공용 — 영업 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>
This commit is contained in:
hjjeong
2026-05-13 17:10:07 +09:00
parent e208d26e51
commit 4f5dd8b47f
12 changed files with 736 additions and 889 deletions
@@ -7,10 +7,10 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { PageHeader } from "@/components/common/PageHeader";
import { CompactFilterBar, CompactFilterField } from "@/components/common/CompactFilterBar";
import {
Search, Loader2, RotateCcw, Plus, Pencil, Trash2, FileSpreadsheet,
Plus, Pencil, Trash2, FileSpreadsheet,
} from "lucide-react";
import { toast } from "sonner";
import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
@@ -124,63 +124,55 @@ export default function PartSearchPage() {
};
return (
<div className="flex h-full flex-col">
<div className="border-b bg-card px-4 py-3">
<div className="flex flex-wrap items-end gap-4">
{/* wace partMngList.jsp 1:1 — select2-part 자동완성 (양방향 동기) */}
<div className="min-w-[220px]">
<Label className="mb-1 block text-xs text-muted-foreground"></Label>
<DevPartSelect mode="partNo"
value={filter.search_part_no ?? ""}
onValueChange={(v, row) => setFilter((prev) => ({
...prev,
search_part_no: v,
search_part_name: row?.part_name ?? prev.search_part_name,
}))} />
</div>
<div className="min-w-[220px]">
<Label className="mb-1 block text-xs text-muted-foreground"></Label>
<DevPartSelect mode="partName"
value={filter.search_part_name ?? ""}
onValueChange={(v, row) => setFilter((prev) => ({
...prev,
search_part_name: v,
search_part_no: row?.part_no ?? prev.search_part_no,
}))} />
</div>
<div className="ml-auto flex items-end gap-2">
<Button variant="outline" size="sm"
onClick={() => { setFilter(EMPTY_FILTER); fetchList(EMPTY_FILTER); }}>
<RotateCcw className="h-4 w-4" /><span className="ml-1"></span>
</Button>
<Button size="sm" onClick={() => fetchList()} disabled={loading}>
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <Search className="h-4 w-4" />}
<span className="ml-1"></span>
</Button>
<Button size="sm" variant="default" onClick={handleCreate}>
<Plus className="h-4 w-4" /><span className="ml-1"></span>
</Button>
<Button size="sm" variant="secondary" onClick={handleEdit}
disabled={checkedIds.length !== 1}>
<Pencil className="h-4 w-4" /><span className="ml-1"></span>
</Button>
<Button size="sm" variant="destructive" onClick={handleDelete}
disabled={checkedIds.length === 0}>
<Trash2 className="h-4 w-4" /><span className="ml-1"></span>
</Button>
<Button size="sm" variant="outline" onClick={() => setExcelOpen(true)}>
<FileSpreadsheet className="h-4 w-4" /><span className="ml-1">Excel Upload</span>
</Button>
{/* M2 조회 — partNoList 미전달: IS_LAST='1' 전체 part_mng 매칭 (페이지 밖도 허용) */}
<PartDrawingMultiUploadButton onUploaded={() => fetchList()} />
</div>
</div>
<div className="mt-2 text-xs text-muted-foreground">
{total.toLocaleString()} (M2: status = 'release')
</div>
</div>
<div className="flex h-full flex-col gap-2 p-2">
<PageHeader actions={
<>
<Button size="sm" className="h-8 gap-1 text-xs" onClick={handleCreate}>
<Plus className="h-3.5 w-3.5" />
</Button>
<Button size="sm" variant="secondary" className="h-8 gap-1 text-xs" onClick={handleEdit}
disabled={checkedIds.length !== 1}>
<Pencil 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>
<Button size="sm" variant="outline" className="h-8 gap-1 text-xs" onClick={() => setExcelOpen(true)}>
<FileSpreadsheet className="h-3.5 w-3.5" />Excel Upload
</Button>
{/* M2 조회 — partNoList 미전달: IS_LAST='1' 전체 part_mng 매칭 (페이지 밖도 허용) */}
<PartDrawingMultiUploadButton onUploaded={() => fetchList()} />
</>
} />
<div className="min-h-0 flex-1 p-2">
<CompactFilterBar
loading={loading}
onSearch={() => fetchList()}
onReset={() => { setFilter(EMPTY_FILTER); fetchList(EMPTY_FILTER); }}
totalText={<> {total.toLocaleString()} (M2: status = 'release')</>}
>
<CompactFilterField label="품번" width={220}>
<DevPartSelect mode="partNo"
value={filter.search_part_no ?? ""}
onValueChange={(v, row) => setFilter((prev) => ({
...prev,
search_part_no: v,
search_part_name: row?.part_name ?? prev.search_part_name,
}))} />
</CompactFilterField>
<CompactFilterField label="품명" width={220}>
<DevPartSelect mode="partName"
value={filter.search_part_name ?? ""}
onValueChange={(v, row) => setFilter((prev) => ({
...prev,
search_part_name: v,
search_part_no: row?.part_no ?? prev.search_part_no,
}))} />
</CompactFilterField>
</CompactFilterBar>
<div className="min-h-0 flex-1">
<DataGrid
columns={columns}
data={gridRows}