"use client"; // 구매관리 > 품의서관리 — wace salesMng/proposalMngList.jsp 1:1 // 그리드: sales_request_master(doc_type='PROPOSAL') + mbom 품번/품명 // 검색: 품의서No / 프로젝트번호 / 결재상태 / 작성일 / 구매유형(multi) / 작성자 / 제품구분 // 액션: 조회 / 결재상신 / 발주서생성 import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Send, ClipboardCheck } from "lucide-react"; import { toast } from "sonner"; import { DataGrid, DataGridColumn } from "@/components/common/DataGrid"; import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect"; import { CompactFilterBar, CompactFilterField, CompactDateRange } from "@/components/common/CompactFilterBar"; import { PageHeader } from "@/components/common/PageHeader"; import { apiClient } from "@/lib/api/client"; import { purchaseApi, PurchaseListFilter, OptionItem } from "@/lib/api/purchase"; import { exportToExcel } from "@/lib/utils/excelExport"; const PARENT_PURCHASE_TYPE = "0001814"; // 구매유형 comm_code const PARENT_PART_TYPE = "0000001"; // 제품구분 comm_code const STATUS_OPTS: SmartSelectOption[] = [ { code: "create", label: "작성중" }, { code: "approvalRequest", label: "결재중" }, { code: "approvalComplete", label: "결재완료" }, { code: "reject", label: "반려" }, ]; const EMPTY_FILTER: PurchaseListFilter = { proposal_no: "", project_no: "", search_status: "", regdate_start: "", regdate_end: "", purchase_type: "", writer: "", part_type: "", page: 1, page_size: 50, }; export default function ProposalPage() { const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [filter, setFilter] = useState(EMPTY_FILTER); const [checkedIds, setCheckedIds] = useState([]); const [purchaseTypeOpts, setPurchaseTypeOpts] = useState([]); const [partTypeOpts, setPartTypeOpts] = useState([]); const [userOpts, setUserOpts] = useState([]); const fetchList = useCallback(async (override?: Partial) => { setLoading(true); try { const f = { ...filter, ...override }; const res = await purchaseApi.listProposal(f); setRows(res.rows ?? []); setTotal(res.totalCount ?? 0); } catch (e: any) { toast.error(e?.response?.data?.message ?? e?.message ?? "조회 실패"); } finally { setLoading(false); } }, [filter]); useEffect(() => { let dead = false; (async () => { try { const [pt, ptt, u] = await Promise.all([ apiClient.get(`/sales/codes/${PARENT_PURCHASE_TYPE}`), apiClient.get(`/sales/codes/${PARENT_PART_TYPE}`), purchaseApi.listUsers(), ]); if (dead) return; setPurchaseTypeOpts(pt.data?.data ?? []); setPartTypeOpts(ptt.data?.data ?? []); setUserOpts(u); } catch { /* skip */ } })(); fetchList(EMPTY_FILTER); return () => { dead = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const gridRows = useMemo(() => rows.map((r, i) => ({ ...r, id: r.objid ?? `p_${i}`, part_display: r.part_extra_count > 0 ? `${r.part_no} 외 ${r.part_extra_count}건` : r.part_no, part_name_display: r.part_extra_count > 0 ? `${r.part_name} 외 ${r.part_extra_count}건` : r.part_name, })), [rows]); const GRID_COLUMNS: DataGridColumn[] = useMemo(() => ([ { key: "proposal_no", label: "품의서 No", width: "w-[140px]", align: "center" }, { key: "project_number", label: "프로젝트번호", width: "w-[150px]", align: "center" }, { key: "purchase_type_name", label: "구매유형", width: "w-[115px]", align: "center" }, { key: "order_type_name", label: "주문유형", width: "w-[115px]", align: "center" }, { key: "product_name_title", label: "제품구분", width: "w-[115px]", align: "center" }, { key: "part_display", label: "품번", width: "w-[160px]" }, { key: "part_name_display", label: "품명", minWidth: "min-w-[280px]" }, { key: "status_title", label: "결재상태", width: "w-[115px]", align: "center" }, { key: "regdate_title", label: "작성일", width: "w-[115px]", align: "center" }, { key: "writer_name", label: "작성자", width: "w-[115px]", align: "center" }, ]), []); const summary = useMemo(() => { const approved = gridRows.filter((r: any) => r.status === "approvalComplete").length; return [ { label: "전체 건수", value: total.toLocaleString(), suffix: "건" }, { label: "결재완료(페이지)", value: approved.toLocaleString(), suffix: "건" }, { label: "선택", value: checkedIds.length.toLocaleString(), suffix: "건" }, ]; }, [gridRows, total, checkedIds]); const handleSearch = () => { setFilter(f => ({ ...f, page: 1 })); fetchList({ page: 1 }); }; const handleReset = () => { setFilter(EMPTY_FILTER); fetchList(EMPTY_FILTER); }; return (
} /> 총 {total.toLocaleString()}건}> setFilter({ ...filter, proposal_no: e.target.value })} /> setFilter({ ...filter, project_no: e.target.value })} /> setFilter({ ...filter, search_status: v })} /> setFilter({ ...filter, regdate_start: v })} to={filter.regdate_end ?? ""} setTo={(v) => setFilter({ ...filter, regdate_end: v })} /> setFilter({ ...filter, purchase_type: v })} /> setFilter({ ...filter, writer: v })} /> setFilter({ ...filter, part_type: v })} /> { setFilter(f => ({ ...f, page: p })); fetchList({ page: p }); }} onPageSizeChange={(n) => { setFilter(f => ({ ...f, page: 1, page_size: n })); fetchList({ page: 1, page_size: n }); }} showColumnSettings summaryStats={summary} systemColumnKeys={["writer_name", "regdate_title"]} onRefresh={() => fetchList()} onDownload={() => { if (gridRows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } const exportRows = gridRows.map((r: any) => { const out: Record = {}; GRID_COLUMNS.forEach((c) => { out[c.label] = r[c.key] ?? ""; }); return out; }); exportToExcel(exportRows, "품의서관리.xlsx", "품의서"); }} showChart />
); }