Merge pull request 'hjjeong' (#9) from hjjeong into main
Reviewed-on: https://g.wace.me/chpark/vexplor_rps/pulls/9
This commit is contained in:
@@ -17,6 +17,7 @@ import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
|
||||
import { CommCodeSelect } from "@/components/common/CommCodeSelect";
|
||||
import { devBomApi, BomReportListFilter, BomReportRow } from "@/lib/api/devBom";
|
||||
import { BomReportStatusDialog } from "@/components/development/BomReportStatusDialog";
|
||||
import { DevPartSelect } from "@/components/development/DevPartSelect";
|
||||
import { BomReportExcelImportDialog } from "@/components/development/BomReportExcelImportDialog";
|
||||
import { BomReportTreeDialog } from "@/components/development/BomReportTreeDialog";
|
||||
|
||||
@@ -110,6 +111,9 @@ export default function EbomRegistPage() {
|
||||
[openTree],
|
||||
);
|
||||
|
||||
// DataGrid 는 row.id 를 키로 사용 — backend 응답은 row.objid (lowercase) 이므로 매핑
|
||||
const gridRows = useMemo(() => rows.map((r) => ({ ...r, id: r.objid })), [rows]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b bg-card px-4 py-3">
|
||||
@@ -132,19 +136,24 @@ export default function EbomRegistPage() {
|
||||
<option key={o.code} value={o.code}>{o.label}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
{/* wace structureList.jsp 1:1 — select2-part 자동완성 (양방향 동기) */}
|
||||
<Field label="품번">
|
||||
<Input
|
||||
value={filter.search_part_no ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_no: e.target.value })}
|
||||
placeholder="품번 LIKE"
|
||||
/>
|
||||
<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,
|
||||
}))} />
|
||||
</Field>
|
||||
<Field label="품명">
|
||||
<Input
|
||||
value={filter.search_part_name ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_name: e.target.value })}
|
||||
placeholder="품명 LIKE"
|
||||
/>
|
||||
<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,
|
||||
}))} />
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
@@ -177,7 +186,7 @@ export default function EbomRegistPage() {
|
||||
<div className="min-h-0 flex-1 p-2">
|
||||
<DataGrid
|
||||
columns={columns}
|
||||
data={rows}
|
||||
data={gridRows}
|
||||
loading={loading}
|
||||
showRowNumber
|
||||
showCheckbox
|
||||
|
||||
@@ -14,12 +14,13 @@ import {
|
||||
import { toast } from "sonner";
|
||||
import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
|
||||
import { devBomApi, BomTreeFilter, BomTreeRow } from "@/lib/api/devBom";
|
||||
import { DevPartSelect } from "@/components/development/DevPartSelect";
|
||||
import { PartDetailDialog } from "@/components/development/PartDetailDialog";
|
||||
|
||||
type Direction = "ascending" | "descending";
|
||||
|
||||
const EMPTY_FILTER: BomTreeFilter = {
|
||||
project_name: "", unit_code: "",
|
||||
search_part_no: "", search_part_name: "",
|
||||
search_part_no: "", search_part_name: "", search_level: "",
|
||||
};
|
||||
|
||||
export default function EbomSearchPage() {
|
||||
@@ -29,6 +30,11 @@ export default function EbomSearchPage() {
|
||||
const [maxLevel, setMaxLevel] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
// 토글 접힘 상태: 접힌 부모 행의 child_objid 집합
|
||||
const [collapsedChildIds, setCollapsedChildIds] = useState<Set<string>>(new Set());
|
||||
// PART 상세 다이얼로그
|
||||
const [partDetailOpen, setPartDetailOpen] = useState(false);
|
||||
const [partDetailObjid, setPartDetailObjid] = useState<string | null>(null);
|
||||
|
||||
const runQuery = useCallback(async (dir: Direction) => {
|
||||
setLoading(true);
|
||||
@@ -38,6 +44,7 @@ export default function EbomSearchPage() {
|
||||
setRows(res.rows ?? []);
|
||||
setMaxLevel(Number(res.max_level) || 0);
|
||||
setDirection(dir);
|
||||
setCollapsedChildIds(new Set()); // 새 조회 시 모두 펼침
|
||||
} catch (e: any) {
|
||||
toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패");
|
||||
} finally {
|
||||
@@ -64,68 +71,150 @@ export default function EbomSearchPage() {
|
||||
}
|
||||
}, [filter]);
|
||||
|
||||
// 동적 LEVEL 컬럼: 각 레벨 컬럼은 row.lev === i 일 때만 pm_part_no 표시
|
||||
// 자식 보유 행 식별 (다른 행이 parent_objid 로 참조하는 child_objid 집합)
|
||||
const hasChildSet = useMemo(() => {
|
||||
const s = new Set<string>();
|
||||
for (const r of rows) if (r.parent_objid) s.add(String(r.parent_objid));
|
||||
return s;
|
||||
}, [rows]);
|
||||
|
||||
// 각 행의 ancestor child_objid 체인 (collapsed 검사용)
|
||||
const ancestorsByChildId = useMemo(() => {
|
||||
const byChild = new Map<string, BomTreeRow>();
|
||||
for (const r of rows) if (r.child_objid) byChild.set(String(r.child_objid), r);
|
||||
const result = new Map<string, string[]>();
|
||||
for (const r of rows) {
|
||||
if (!r.child_objid) continue;
|
||||
const list: string[] = [];
|
||||
let cur: string | null = r.parent_objid ? String(r.parent_objid) : null;
|
||||
const guard = new Set<string>();
|
||||
while (cur && !guard.has(cur)) {
|
||||
list.push(cur);
|
||||
guard.add(cur);
|
||||
const p = byChild.get(cur);
|
||||
cur = p?.parent_objid ? String(p.parent_objid) : null;
|
||||
}
|
||||
result.set(String(r.child_objid), list);
|
||||
}
|
||||
return result;
|
||||
}, [rows]);
|
||||
|
||||
// 토글 클릭 핸들러 — 부모 행의 child_objid 를 collapsed Set 에 toggle
|
||||
const toggleCollapse = useCallback((row: any) => {
|
||||
const childId = row.child_objid ? String(row.child_objid) : "";
|
||||
if (!childId || !hasChildSet.has(childId)) return;
|
||||
setCollapsedChildIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(childId)) next.delete(childId);
|
||||
else next.add(childId);
|
||||
return next;
|
||||
});
|
||||
}, [hasChildSet]);
|
||||
|
||||
// 운영판 structureAscendingList.jsp 1:1
|
||||
// - 첫 컬럼: -/+ 토글 (자식 있는 행만)
|
||||
// - L1..LMaxLevel 컬럼은 해당 레벨에만 "*" 표시 (품번 표시 X)
|
||||
// - 별도 품번 컬럼에 모든 행 part_no
|
||||
// - 3D/2D/PDF 폴더 아이콘 (renderType: "folder")
|
||||
const columns: DataGridColumn[] = useMemo(() => {
|
||||
const levelCols: DataGridColumn[] = [];
|
||||
for (let i = 1; i <= Math.max(1, maxLevel); i++) {
|
||||
levelCols.push({
|
||||
key: `__lev_${i}`,
|
||||
label: `L${i}`,
|
||||
width: "w-[140px]",
|
||||
label: String(i),
|
||||
width: "w-[36px]",
|
||||
align: "center",
|
||||
});
|
||||
}
|
||||
return [
|
||||
{ key: "__toggle", label: "", width: "w-[36px]", align: "center",
|
||||
onClick: (row: any) => toggleCollapse(row) },
|
||||
...levelCols,
|
||||
{ key: "pm_part_no", label: "품번", width: "w-[160px]", frozen: false },
|
||||
{ key: "pm_part_name", label: "품명", minWidth: "min-w-[200px]" },
|
||||
{ key: "cu01_cnt", label: "3D", width: "w-[60px]", align: "right", formatNumber: true },
|
||||
{ key: "cu02_cnt", label: "2D", width: "w-[60px]", align: "right", formatNumber: true },
|
||||
{ key: "cu03_cnt", label: "PDF", width: "w-[60px]", align: "right", formatNumber: true },
|
||||
{ key: "qty", label: "수량", width: "w-[90px]", align: "right", formatNumber: true },
|
||||
{ key: "edit_date", label: "변경일", width: "w-[120px]", align: "center" },
|
||||
{ key: "revision", label: "REV", width: "w-[60px]", align: "center" },
|
||||
{ key: "spec", label: "규격", width: "w-[120px]" },
|
||||
{ key: "material", label: "재질", width: "w-[100px]" },
|
||||
{ key: "weight", label: "중량", width: "w-[80px]", align: "right" },
|
||||
{ key: "remark", label: "비고", minWidth: "min-w-[140px]" },
|
||||
// 품번 셀 클릭 → PART 상세 (wace partMngDetailPopUp 1:1). row.part_no = part_mng.objid 임.
|
||||
{ key: "pm_part_no", label: "품번", width: "w-[160px]",
|
||||
onClick: (row: any) => {
|
||||
if (row.part_no) {
|
||||
setPartDetailObjid(String(row.part_no));
|
||||
setPartDetailOpen(true);
|
||||
}
|
||||
} },
|
||||
{ key: "pm_part_name", label: "품명", minWidth: "min-w-[200px]" },
|
||||
{ key: "qty", label: "수량", width: "w-[70px]", align: "right", formatNumber: true },
|
||||
{ key: "p_qty", label: "항목수량", width: "w-[80px]", align: "right", formatNumber: true },
|
||||
{ key: "cu01_cnt", label: "3D", width: "w-[60px]", align: "center", renderType: "folder" },
|
||||
{ key: "cu02_cnt", label: "2D", width: "w-[60px]", align: "center", renderType: "folder" },
|
||||
{ key: "cu03_cnt", label: "PDF", width: "w-[60px]", align: "center", renderType: "folder" },
|
||||
{ key: "material", label: "재료", width: "w-[100px]" },
|
||||
{ key: "heat_treatment_hardness", label: "열처리경도", width: "w-[110px]" },
|
||||
{ key: "heat_treatment_method", label: "열처리방법", width: "w-[110px]" },
|
||||
{ key: "surface_treatment", label: "표면처리", width: "w-[100px]" },
|
||||
{ key: "maker", label: "메이커", width: "w-[110px]" },
|
||||
{ key: "part_type_title", label: "범주 이름", width: "w-[100px]", align: "center" },
|
||||
{ key: "remark", label: "비고", minWidth: "min-w-[140px]" },
|
||||
];
|
||||
}, [maxLevel]);
|
||||
}, [maxLevel, toggleCollapse]);
|
||||
|
||||
// 행 데이터: __lev_{i} 가상 셀에 lev 일치 시에만 part_no 채움
|
||||
const gridData = useMemo(
|
||||
() => rows.map((r) => {
|
||||
const expanded: any = { ...r };
|
||||
for (let i = 1; i <= Math.max(1, maxLevel); i++) {
|
||||
expanded[`__lev_${i}`] = r.lev === i ? (r.pm_part_no ?? r.part_no ?? "") : "";
|
||||
}
|
||||
return expanded;
|
||||
}),
|
||||
[rows, maxLevel],
|
||||
);
|
||||
// 가시 행: collapsed 부모를 ancestor 로 가진 행은 hide
|
||||
// 행 데이터: __toggle 셀 + __lev_{i} "*" 표시
|
||||
const gridData = useMemo(() => {
|
||||
return rows
|
||||
.filter((r) => {
|
||||
if (!r.child_objid) return true;
|
||||
const ancestors = ancestorsByChildId.get(String(r.child_objid)) ?? [];
|
||||
return !ancestors.some((a) => collapsedChildIds.has(a));
|
||||
})
|
||||
.map((r) => {
|
||||
const expanded: any = { ...r };
|
||||
const lev = Number(r.lev ?? 0);
|
||||
for (let i = 1; i <= Math.max(1, maxLevel); i++) {
|
||||
expanded[`__lev_${i}`] = lev === i ? "*" : "";
|
||||
}
|
||||
const childId = r.child_objid ? String(r.child_objid) : "";
|
||||
const hasChild = childId && hasChildSet.has(childId);
|
||||
expanded.__toggle = hasChild ? (collapsedChildIds.has(childId) ? "+" : "−") : "";
|
||||
return expanded;
|
||||
});
|
||||
}, [rows, maxLevel, hasChildSet, ancestorsByChildId, collapsedChildIds]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b bg-card px-4 py-3">
|
||||
<div className="grid grid-cols-4 gap-3 text-sm">
|
||||
<Field label="프로젝트 OBJID">
|
||||
<Input value={filter.project_name ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, project_name: e.target.value })}
|
||||
placeholder="project_mgmt.objid" />
|
||||
</Field>
|
||||
<Field label="UNIT_CODE">
|
||||
<Input value={filter.unit_code ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, unit_code: e.target.value })}
|
||||
placeholder="pms_wbs_task.objid" />
|
||||
</Field>
|
||||
{/* 운영판 wace structureAscendingList.jsp 1:1 — 노출 검색 필드 3개
|
||||
(고객사/프로젝트번호/유닛명 은 운영판에서도 주석 처리되어 노출 안 됨) */}
|
||||
<div className="grid grid-cols-3 gap-3 text-sm">
|
||||
<Field label="품번">
|
||||
<Input value={filter.search_part_no ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_no: e.target.value })}
|
||||
placeholder="part_no LIKE" />
|
||||
<DevPartSelect mode="partNo"
|
||||
value={filter.search_part_no ?? ""}
|
||||
onValueChange={(v, row) => setFilter((prev) => ({
|
||||
...prev,
|
||||
search_part_no: v,
|
||||
// 품번 선택 시 품명 자동 채움 (wace select2-part 1:1)
|
||||
search_part_name: row?.part_name ?? prev.search_part_name,
|
||||
}))} />
|
||||
</Field>
|
||||
<Field label="품명">
|
||||
<Input value={filter.search_part_name ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_name: e.target.value })}
|
||||
placeholder="part_name LIKE" />
|
||||
<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,
|
||||
}))} />
|
||||
</Field>
|
||||
<Field label="표시 레벨">
|
||||
<select
|
||||
className="h-9 w-full rounded-md border bg-background px-2 text-sm"
|
||||
value={String(filter.search_level ?? "")}
|
||||
onChange={(e) => setFilter({ ...filter, search_level: e.target.value })}
|
||||
>
|
||||
<option value="">전체</option>
|
||||
<option value="1">1레벨</option>
|
||||
<option value="2">2레벨</option>
|
||||
<option value="3">3레벨</option>
|
||||
<option value="4">4레벨</option>
|
||||
<option value="5">5레벨</option>
|
||||
</select>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center justify-between">
|
||||
@@ -163,7 +252,7 @@ export default function EbomSearchPage() {
|
||||
</div>
|
||||
{direction === "descending" && (
|
||||
<div className="mt-2 text-xs text-amber-600">
|
||||
역전개는 PART 검색(품번/품명) 또는 BOM/프로젝트 한정 조건이 필요합니다.
|
||||
역전개는 품번 또는 품명 검색 조건이 필요합니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -178,6 +267,12 @@ export default function EbomSearchPage() {
|
||||
gridId={`development-ebom-search-${direction}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PartDetailDialog
|
||||
open={partDetailOpen}
|
||||
onOpenChange={setPartDetailOpen}
|
||||
objid={partDetailObjid}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import { devPartApi, PartListFilter, PartRow } from "@/lib/api/devPart";
|
||||
import { PartFormDialog } from "@/components/development/PartFormDialog";
|
||||
import { PartDetailDialog } from "@/components/development/PartDetailDialog";
|
||||
import { PartExcelImportDialog } from "@/components/development/PartExcelImportDialog";
|
||||
import { PartDrawingMultiUploadButton } from "@/components/development/PartDrawingMultiUploadButton";
|
||||
import { DevPartSelect } from "@/components/development/DevPartSelect";
|
||||
|
||||
// wace 23셀 + 부속 (PARENT_PART_INFO/PARTNER_TITLE/Q_QTY)
|
||||
const GRID_COLUMNS: DataGridColumn[] = [
|
||||
@@ -100,6 +102,9 @@ export default function PartRegistPage() {
|
||||
[],
|
||||
);
|
||||
|
||||
// DataGrid 는 row.id 를 키로 사용 — backend 응답은 row.objid 이므로 매핑
|
||||
const gridRows = useMemo(() => rows.map((r) => ({ ...r, id: r.objid })), [rows]);
|
||||
|
||||
// 등록
|
||||
const handleCreate = () => {
|
||||
setFormMode("create");
|
||||
@@ -154,21 +159,26 @@ export default function PartRegistPage() {
|
||||
{/* 검색폼 — wace partMngTempList.jsp 활성 2필드 */}
|
||||
<div className="border-b bg-card px-4 py-3">
|
||||
<div className="flex flex-wrap items-end gap-4">
|
||||
<div className="min-w-[200px]">
|
||||
{/* wace partMngTempList.jsp 1:1 — select2-part 자동완성 (양방향 동기) */}
|
||||
<div className="min-w-[220px]">
|
||||
<Label className="mb-1 block text-xs text-muted-foreground">품번</Label>
|
||||
<Input
|
||||
value={filter.search_part_no ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_no: e.target.value })}
|
||||
placeholder="품번 LIKE"
|
||||
/>
|
||||
<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-[200px]">
|
||||
<div className="min-w-[220px]">
|
||||
<Label className="mb-1 block text-xs text-muted-foreground">품명</Label>
|
||||
<Input
|
||||
value={filter.search_part_name ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_name: e.target.value })}
|
||||
placeholder="품명 LIKE"
|
||||
/>
|
||||
<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"
|
||||
@@ -193,6 +203,10 @@ export default function PartRegistPage() {
|
||||
<Button size="sm" variant="outline" onClick={() => setExcelOpen(true)}>
|
||||
<FileSpreadsheet className="h-4 w-4" /><span className="ml-1">Excel Upload</span>
|
||||
</Button>
|
||||
<PartDrawingMultiUploadButton
|
||||
partNoList={rows.map((r) => r.part_no).filter(Boolean) as string[]}
|
||||
onUploaded={() => fetchList()}
|
||||
/>
|
||||
<Button size="sm" onClick={handleDeploy}
|
||||
disabled={checkedIds.length === 0}
|
||||
className="bg-emerald-600 hover:bg-emerald-700 text-white">
|
||||
@@ -208,7 +222,7 @@ export default function PartRegistPage() {
|
||||
<div className="min-h-0 flex-1 p-2">
|
||||
<DataGrid
|
||||
columns={columns}
|
||||
data={rows}
|
||||
data={gridRows}
|
||||
loading={loading}
|
||||
showRowNumber
|
||||
showCheckbox
|
||||
|
||||
@@ -18,6 +18,8 @@ import { devPartApi, PartListFilter, PartRow } from "@/lib/api/devPart";
|
||||
import { PartFormDialog } from "@/components/development/PartFormDialog";
|
||||
import { PartDetailDialog } from "@/components/development/PartDetailDialog";
|
||||
import { PartExcelImportDialog } from "@/components/development/PartExcelImportDialog";
|
||||
import { PartDrawingMultiUploadButton } from "@/components/development/PartDrawingMultiUploadButton";
|
||||
import { DevPartSelect } from "@/components/development/DevPartSelect";
|
||||
|
||||
const GRID_COLUMNS: DataGridColumn[] = [
|
||||
{ key: "part_no", label: "품번", width: "w-[140px]", frozen: true },
|
||||
@@ -95,6 +97,9 @@ export default function PartSearchPage() {
|
||||
[],
|
||||
);
|
||||
|
||||
// DataGrid 는 row.id 를 키로 사용 — backend 응답은 row.objid 이므로 매핑
|
||||
const gridRows = useMemo(() => rows.map((r) => ({ ...r, id: r.objid })), [rows]);
|
||||
|
||||
const handleCreate = () => {
|
||||
setFormMode("create"); setFormObjid(null); setFormOpen(true);
|
||||
};
|
||||
@@ -122,21 +127,26 @@ export default function PartSearchPage() {
|
||||
<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">
|
||||
<div className="min-w-[200px]">
|
||||
{/* wace partMngList.jsp 1:1 — select2-part 자동완성 (양방향 동기) */}
|
||||
<div className="min-w-[220px]">
|
||||
<Label className="mb-1 block text-xs text-muted-foreground">품번</Label>
|
||||
<Input
|
||||
value={filter.search_part_no ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_no: e.target.value })}
|
||||
placeholder="품번 LIKE"
|
||||
/>
|
||||
<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-[200px]">
|
||||
<div className="min-w-[220px]">
|
||||
<Label className="mb-1 block text-xs text-muted-foreground">품명</Label>
|
||||
<Input
|
||||
value={filter.search_part_name ?? ""}
|
||||
onChange={(e) => setFilter({ ...filter, search_part_name: e.target.value })}
|
||||
placeholder="품명 LIKE"
|
||||
/>
|
||||
<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"
|
||||
@@ -161,6 +171,8 @@ export default function PartSearchPage() {
|
||||
<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">
|
||||
@@ -171,7 +183,7 @@ export default function PartSearchPage() {
|
||||
<div className="min-h-0 flex-1 p-2">
|
||||
<DataGrid
|
||||
columns={columns}
|
||||
data={rows}
|
||||
data={gridRows}
|
||||
loading={loading}
|
||||
showRowNumber
|
||||
showCheckbox
|
||||
|
||||
Reference in New Issue
Block a user