0872199b30
backend (M3+M4): - devBomService: list/getByObjid/updateStatus/removeMany/ascending/descending - M3 그리드 SQL (getBOMStandardStructureGridList 1:1) - customer_mng 매핑 (wace SUPPLY_MNG → vexplor customer_mng.customer_code) - PRODUCT_NAME LEFT JOIN comm_code (CODE_NAME 함수 대체) - M3 다중 삭제 트랜잭션 (bom_part_qty + part_bom_report CASCADE) - M4 정/역전개 재귀 CTE (bom_part_qty 트리 + part_mng JOIN) - vexplor 적응: M4 product_mgmt_spec/upg/vc 분기 제거 (스키마 단순화) - PG 재귀 CTE 타입 일치: ARRAY[BP.objid::varchar] 명시 cast frontend (M3+M4): - ebom-regist (M3): 9셀 그리드 (제품구분·품번·품명·E-BOM·등록자·등록일·확정일·Version·상태) - ebom-search (M4): 동적 LEVEL 컬럼 + 정/역전개 토글 - BomReportStatusDialog: 상태 변경 (create/changeDesign/deploy + version) - AdminPageRenderer dynamic 임포트 2건 + menu_info URL spec 정렬 본 PR 제외 (별 PR): E-BOM Excel Import, 정/역전개 Excel Download, BOM_PART_QTY 수량 편집 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
131 lines
4.7 KiB
TypeScript
131 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
// 개발관리 > E-BOM 상태 변경 다이얼로그.
|
|
// wace structureStatusChangePopup 1:1 — STATUS select(create/changeDesign/deploy) + 부속 4필드.
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import {
|
|
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
|
} from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Loader2, Save } from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { devBomApi, BomReportRow } from "@/lib/api/devBom";
|
|
|
|
const STATUS_OPTIONS = [
|
|
{ code: "create", label: "등록중" },
|
|
{ code: "changeDesign", label: "설계변경미배포" },
|
|
{ code: "deploy", label: "배포완료" },
|
|
];
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
objid: string | null;
|
|
onSaved: () => void;
|
|
}
|
|
|
|
export function BomReportStatusDialog({ open, onOpenChange, objid, onSaved }: Props) {
|
|
const [row, setRow] = useState<BomReportRow | null>(null);
|
|
const [status, setStatus] = useState<string>("");
|
|
const [version, setVersion] = useState<string>("");
|
|
const [loading, setLoading] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!open || !objid) return;
|
|
let alive = true;
|
|
setLoading(true);
|
|
devBomApi.detail(objid)
|
|
.then((data) => {
|
|
if (!alive) return;
|
|
if (!data) {
|
|
toast.error("E-BOM 보고서를 찾을 수 없습니다.");
|
|
onOpenChange(false);
|
|
return;
|
|
}
|
|
setRow(data);
|
|
setStatus(data.status ?? "");
|
|
setVersion(data.revision ?? "");
|
|
})
|
|
.catch((e: any) => {
|
|
toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패");
|
|
onOpenChange(false);
|
|
})
|
|
.finally(() => { if (alive) setLoading(false); });
|
|
return () => { alive = false; };
|
|
}, [open, objid, onOpenChange]);
|
|
|
|
const handleSave = async () => {
|
|
if (!objid) return;
|
|
if (!status) return toast.error("상태를 선택하세요.");
|
|
setSaving(true);
|
|
try {
|
|
await devBomApi.updateStatus(objid, {
|
|
status,
|
|
version: version || undefined,
|
|
});
|
|
toast.success("상태가 변경되었습니다.");
|
|
onSaved();
|
|
onOpenChange(false);
|
|
} catch (e: any) {
|
|
toast.error(e?.response?.data?.message ?? e?.message ?? "저장 실패");
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>E-BOM 상태 변경</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
{loading || !row ? (
|
|
<div className="flex h-40 items-center justify-center">
|
|
<Loader2 className="h-6 w-6 animate-spin" />
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3 py-2">
|
|
<div className="rounded-md border bg-muted/30 p-3 text-sm space-y-1">
|
|
<div><span className="text-muted-foreground">제품구분:</span> {row.product_name ?? row.product_cd ?? "—"}</div>
|
|
<div><span className="text-muted-foreground">품번:</span> {row.part_no ?? "—"}</div>
|
|
<div><span className="text-muted-foreground">품명:</span> {row.part_name ?? "—"}</div>
|
|
<div><span className="text-muted-foreground">현재상태:</span> {row.status_title ?? row.status ?? "—"}</div>
|
|
</div>
|
|
<div>
|
|
<Label className="mb-1 block text-xs text-muted-foreground">변경 상태 *</Label>
|
|
<select
|
|
className="h-9 w-full rounded-md border bg-background px-2 text-sm"
|
|
value={status}
|
|
onChange={(e) => setStatus(e.target.value)}
|
|
>
|
|
<option value="">선택</option>
|
|
{STATUS_OPTIONS.map((o) =>
|
|
<option key={o.code} value={o.code}>{o.label}</option>)}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<Label className="mb-1 block text-xs text-muted-foreground">Version</Label>
|
|
<Input value={version} onChange={(e) => setVersion(e.target.value)} placeholder="예: RE, A, B..." />
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
|
|
취소
|
|
</Button>
|
|
<Button onClick={handleSave} disabled={saving || loading}>
|
|
{saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
|
|
<span className="ml-1">저장</span>
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|