From fc959d8872b0a2ad897fb0491e0b89edacce1a62 Mon Sep 17 00:00:00 2001 From: hjjeong Date: Thu, 14 May 2026 14:53:07 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=81=EC=97=85=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=E2=80=94=20=EC=A3=BC=EB=AC=B8/=ED=8C=90=EB=A7=A4/=EB=A7=A4?= =?UTF-8?q?=EC=B6=9C=EC=97=90=20logicstudio=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=20DataGrid=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 견적관리(36c1f357)와 동일한 패턴을 나머지 3개 메뉴로 확장: 공통 DataGrid props: - gridId (sales-order / sales-sale / sales-revenue) 로 컬럼 visibility·순서·너비·차트 영속 - showColumnSettings, paginationStyle="range", pageSizeOptions=[10,15,20,50,100] - onRefresh = fetchList, onDownload = exportToExcel(GRID_COLUMNS 라벨 매핑) - showChart 도메인별 summaryStats (하단 통계 행): - 주문: 수주 건수 / 수주수량·수주취소 합계 / 공급가액·부가세·총액·원화총액 합계 - 판매: 판매 라인 / 수주·판매·잔량 수량 합계 / 판매공급가액·판매원화·잔량원화 합계 - 매출: 매출 이력 / 수량 합계 / 공급가액·부가세·총액·원화총액 합계 컬럼 폭 보정: - ⋮⋮ 드래그 핸들 추가로 좁아진 4글자 한국어 라벨이 잘리지 않도록 95~135px로 확대 - 시스템 컬럼: 주문관리 writer_name (systemColumnKeys 분리) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../(main)/COMPANY_16/sales/order/page.tsx | 73 ++++++++++++++---- .../(main)/COMPANY_16/sales/revenue/page.tsx | 70 +++++++++++++---- .../app/(main)/COMPANY_16/sales/sale/page.tsx | 76 ++++++++++++++----- 3 files changed, 169 insertions(+), 50 deletions(-) diff --git a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx index ce218529..804615d0 100644 --- a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx @@ -26,33 +26,34 @@ import { AttachmentDialog } from "@/components/common/AttachmentDialog"; import { OrderFormViewDialog } from "@/components/sales/OrderFormViewDialog"; import { OrderRegistDialog } from "@/components/sales/OrderRegistDialog"; import { salesOrderMgmtApi, OrderRow, OrderBody, OrderItem } from "@/lib/api/salesOrderMgmt"; +import { exportToExcel } from "@/lib/utils/excelExport"; // wace_plm orderMgmtList.jsp 컬럼 순서/라벨에 맞춤 const GRID_COLUMNS: DataGridColumn[] = [ { key: "contract_no", label: "영업번호", width: "w-[125px]", frozen: true }, - { key: "category_name", label: "주문유형", width: "w-[90px]", align: "center" }, - { key: "order_date", label: "발주일", width: "w-[120px]", align: "center" }, - { key: "po_no", label: "발주번호", width: "w-[130px]" }, + { key: "category_name", label: "주문유형", width: "w-[115px]", align: "center" }, + { key: "order_date", label: "발주일", width: "w-[115px]", align: "center" }, + { key: "po_no", label: "발주번호", width: "w-[140px]" }, { key: "earliest_due_date_label", label: "요청납기", width: "w-[160px]", align: "center" }, { key: "customer_name", label: "고객사", width: "w-[160px]" }, { key: "item_summary", label: "품명", width: "w-[200px]" }, - { key: "order_quantity", label: "수주수량", width: "w-[90px]", align: "right", formatNumber: true }, - { key: "cancel_qty_sum", label: "수주취소", width: "w-[90px]", align: "right", formatNumber: true }, - { key: "paid_type_name", label: "유/무상", width: "w-[80px]", align: "center" }, - { key: "contract_result_name", label: "수주상태", width: "w-[100px]", align: "center" }, - { key: "order_supply_price_sum", label: "공급가액", width: "w-[120px]", formatMoney: true }, - { key: "order_vat_sum", label: "부가세", width: "w-[100px]", formatMoney: true }, + { key: "order_quantity", label: "수주수량", width: "w-[115px]", align: "right", formatNumber: true }, + { key: "cancel_qty_sum", label: "수주취소", width: "w-[115px]", align: "right", formatNumber: true }, + { key: "paid_type_name", label: "유/무상", width: "w-[100px]", align: "center" }, + { key: "contract_result_name", label: "수주상태", width: "w-[115px]", align: "center" }, + { key: "order_supply_price_sum", label: "공급가액", width: "w-[130px]", formatMoney: true }, + { key: "order_vat_sum", label: "부가세", width: "w-[115px]", formatMoney: true }, { key: "order_total_amount_sum", label: "총액", width: "w-[120px]", formatMoney: true }, - { key: "order_total_amount_krw", label: "원화총액", width: "w-[120px]", formatMoney: true }, - { key: "cu01_cnt", label: "주문서첨부", width: "w-[90px]", align: "center", renderType: "clip" }, - { key: "has_order_data", label: "주문서", width: "w-[80px]", align: "center", renderType: "folder" }, + { key: "order_total_amount_krw", label: "원화총액", width: "w-[130px]", formatMoney: true }, + { key: "cu01_cnt", label: "주문서첨부", width: "w-[115px]", align: "center", renderType: "clip" }, + { key: "has_order_data", label: "주문서", width: "w-[100px]", align: "center", renderType: "folder" }, { key: "customer_request", label: "고객사요청사항", width: "w-[180px]" }, - { key: "order_appr_status", label: "결재상태", width: "w-[90px]", align: "center" }, - { key: "contract_currency_name", label: "환종", width: "w-[70px]", align: "center" }, - { key: "exchange_rate", label: "환율", width: "w-[80px]", formatMoney: true }, + { key: "order_appr_status", label: "결재상태", width: "w-[115px]", align: "center" }, + { key: "contract_currency_name", label: "환종", width: "w-[95px]", align: "center" }, + { key: "exchange_rate", label: "환율", width: "w-[95px]", formatMoney: true }, { key: "serial_no", label: "S/N", width: "w-[140px]" }, { key: "part_no", label: "품번", width: "w-[120px]" }, - { key: "writer_name", label: "작성자", width: "w-[110px]" }, + { key: "writer_name", label: "작성자", width: "w-[115px]" }, /* wace orderMgmtList.jsp 429~434 — 비활성(주석) 컬럼. 활성화 시 아래 주석 해제. { key: "product_name", label: "제품구분", width: "w-[90px]", align: "center" }, { key: "area_name", label: "국내/해외", width: "w-[90px]", align: "center" }, @@ -209,6 +210,29 @@ export default function SalesOrderPage() { useEffect(() => { fetchList(); }, [fetchList]); + // ─── 하단 통계 ────────────────────────────────────────────── + // 수주 건수 / 수주수량·취소 합계 / 공급가액·부가세·총액·원화총액 합계 + const orderSummary = useMemo(() => { + const count = rows.length; + const qtySum = rows.reduce((acc, r) => acc + Number(r.order_quantity || 0), 0); + const cancelSum = rows.reduce((acc, r) => acc + Number(r.cancel_qty_sum || 0), 0); + const supplySum = rows.reduce((acc, r) => acc + Number(r.order_supply_price_sum || 0), 0); + const vatSum = rows.reduce((acc, r) => acc + Number(r.order_vat_sum || 0), 0); + const totalSum = rows.reduce((acc, r) => acc + Number(r.order_total_amount_sum || 0), 0); + const krwSum = rows.reduce((acc, r) => acc + Number(r.order_total_amount_krw || 0), 0); + const money = (n: number) => n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + const intFmt = (n: number) => n.toLocaleString(); + return [ + { label: "수주 건수", value: intFmt(count), suffix: "건" }, + { label: "수주수량 합계", value: intFmt(qtySum) }, + { label: "수주취소 합계", value: intFmt(cancelSum) }, + { label: "공급가액 합계", value: money(supplySum) }, + { label: "부가세 합계", value: money(vatSum) }, + { label: "총액 합계", value: money(totalSum) }, + { label: "원화총액 합계", value: money(krwSum) }, + ]; + }, [rows]); + const openCreate = async () => { setDialogMode("create"); const contractNo = await salesOrderMgmtApi.generateNumber().catch(() => ""); @@ -619,6 +643,7 @@ export default function SalesOrderPage() { { setSelected(row); openEdit(); }} emptyMessage={loading ? "조회 중..." : "데이터가 없습니다"} loading={loading} + showColumnSettings + paginationStyle="range" + pageSizeOptions={[10, 15, 20, 50, 100]} + summaryStats={orderSummary} + systemColumnKeys={["writer_name"]} + onRefresh={fetchList} + onDownload={() => { + if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } + const exportRows = rows.map((r) => { + const out: Record = {}; + GRID_COLUMNS.forEach((col) => { out[col.label] = r[col.key] ?? ""; }); + return out; + }); + exportToExcel(exportRows, "주문관리.xlsx", "주문관리"); + }} + showChart /> diff --git a/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx b/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx index e93d3dea..99dd5c19 100644 --- a/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx @@ -23,6 +23,7 @@ import { PageHeader } from "@/components/common/PageHeader"; import { CompactFilterBar, CompactFilterField, CompactDateRange } from "@/components/common/CompactFilterBar"; import { ProjectInfoDialog, ProjectInfoData } from "@/components/project/ProjectInfoDialog"; import { salesSaleApi, RevenueListRow, DeadlineInfoBody } from "@/lib/api/salesSale"; +import { exportToExcel } from "@/lib/utils/excelExport"; // RevenueListRow → 정규화된 ProjectInfoData (wace 운영판 다이얼로그 1:1) const toProjectInfo = (r: RevenueListRow): ProjectInfoData => ({ @@ -45,31 +46,31 @@ const toProjectInfo = (r: RevenueListRow): ProjectInfoData => ({ // wace_plm revenueMgmtList.jsp 컬럼 순서/라벨에 맞춤 const GRID_COLUMNS: DataGridColumn[] = [ { key: "project_no", label: "프로젝트번호", width: "w-[170px]", frozen: true }, - { key: "order_type_name", label: "주문유형", width: "w-[90px]", align: "center" }, - { key: "sales_deadline_date", label: "매출마감", width: "w-[110px]", align: "center" }, + { key: "order_type_name", label: "주문유형", width: "w-[115px]", align: "center" }, + { key: "sales_deadline_date", label: "매출마감", width: "w-[115px]", align: "center" }, { key: "order_date", label: "발주일", width: "w-[115px]", align: "center" }, { key: "po_no", label: "발주번호", width: "w-[140px]" }, { key: "customer", label: "고객사", width: "w-[160px]" }, - { key: "product_type_name", label: "제품구분", width: "w-[90px]", align: "center" }, + { key: "product_type_name", label: "제품구분", width: "w-[115px]", align: "center" }, { key: "product_name", label: "품명", width: "w-[180px]" }, - { key: "sales_quantity", label: "수량", width: "w-[80px]", align: "right", formatNumber: true }, - { key: "sales_unit_price", label: "단가", width: "w-[110px]", formatMoney: true }, - { key: "sales_supply_price", label: "공급가액", width: "w-[120px]", formatMoney: true }, - { key: "sales_vat", label: "부가세", width: "w-[100px]", formatMoney: true }, + { key: "sales_quantity", label: "수량", width: "w-[95px]", align: "right", formatNumber: true }, + { key: "sales_unit_price", label: "단가", width: "w-[115px]", formatMoney: true }, + { key: "sales_supply_price", label: "공급가액", width: "w-[130px]", formatMoney: true }, + { key: "sales_vat", label: "부가세", width: "w-[115px]", formatMoney: true }, { key: "sales_total_amount", label: "총액", width: "w-[120px]", formatMoney: true }, - { key: "sales_total_amount_krw", label: "원화총액", width: "w-[120px]", formatMoney: true }, + { key: "sales_total_amount_krw", label: "원화총액", width: "w-[130px]", formatMoney: true }, { key: "shipping_date", label: "출하일", width: "w-[115px]", align: "center" }, - { key: "nation_name", label: "국내/해외", width: "w-[90px]", align: "center" }, - { key: "sales_currency_name", label: "환종", width: "w-[70px]", align: "center" }, - { key: "sales_exchange_rate", label: "환율", width: "w-[80px]", formatMoney: true }, + { key: "nation_name", label: "국내/해외", width: "w-[115px]", align: "center" }, + { key: "sales_currency_name", label: "환종", width: "w-[95px]", align: "center" }, + { key: "sales_exchange_rate", label: "환율", width: "w-[95px]", formatMoney: true }, { key: "serial_no", label: "S/N", width: "w-[140px]" }, { key: "split_serial_no", label: "분할S/N", width: "w-[140px]" }, { key: "product_no", label: "품번", width: "w-[120px]" }, - { key: "tax_type", label: "과세구분", width: "w-[100px]", align: "center" }, - { key: "tax_invoice_date", label: "세금계산서발행일", width: "w-[140px]", align: "center" }, - { key: "export_decl_no", label: "수출신고필증신고번호", width: "w-[160px]" }, - { key: "loading_date", label: "선적일자", width: "w-[100px]", align: "center" }, - { key: "has_transaction_statement", label: "거래명세서", width: "w-[100px]", align: "center" }, + { key: "tax_type", label: "과세구분", width: "w-[115px]", align: "center" }, + { key: "tax_invoice_date", label: "세금계산서발행일", width: "w-[160px]", align: "center" }, + { key: "export_decl_no", label: "수출신고필증신고번호", width: "w-[180px]" }, + { key: "loading_date", label: "선적일자", width: "w-[115px]", align: "center" }, + { key: "has_transaction_statement", label: "거래명세서", width: "w-[115px]", align: "center" }, /* wace revenueMgmtList.jsp 615~632 — 비활성(주석) 컬럼. 활성화 시 아래 주석 해제. { key: "receipt_date", label: "접수일", width: "w-[115px]", align: "center" }, { key: "payment_type_name", label: "유/무상", width: "w-[80px]", align: "center" }, @@ -134,6 +135,27 @@ export default function SalesRevenuePage() { useEffect(() => { fetchList(); }, [fetchList]); + // ─── 하단 통계 ────────────────────────────────────────────── + // 매출 이력 건수 / 수량 합계 / 공급가액·부가세·총액·원화총액 합계 + const revenueSummary = useMemo(() => { + const count = rows.length; + const qtySum = rows.reduce((acc, r) => acc + Number(r.sales_quantity || 0), 0); + const supplySum = rows.reduce((acc, r) => acc + Number(r.sales_supply_price || 0), 0); + const vatSum = rows.reduce((acc, r) => acc + Number(r.sales_vat || 0), 0); + const totalSum = rows.reduce((acc, r) => acc + Number(r.sales_total_amount || 0), 0); + const krwSum = rows.reduce((acc, r) => acc + Number(r.sales_total_amount_krw || 0), 0); + const money = (n: number) => n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + const intFmt = (n: number) => n.toLocaleString(); + return [ + { label: "매출 이력", value: intFmt(count), suffix: "건" }, + { label: "수량 합계", value: intFmt(qtySum) }, + { label: "공급가액 합계", value: money(supplySum) }, + { label: "부가세 합계", value: money(vatSum) }, + { label: "총액 합계", value: money(totalSum) }, + { label: "원화총액 합계", value: money(krwSum) }, + ]; + }, [rows]); + const openDeadline = () => { if (!selected) { toast.warning("마감정보를 입력할 행을 선택하세요."); return; } setForm({ @@ -273,6 +295,7 @@ export default function SalesRevenuePage() { { setSelected(row); openDeadline(); }} emptyMessage={loading ? "조회 중..." : "데이터가 없습니다"} loading={loading} + showColumnSettings + paginationStyle="range" + pageSizeOptions={[10, 15, 20, 50, 100]} + summaryStats={revenueSummary} + onRefresh={fetchList} + onDownload={() => { + if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } + const exportRows = rows.map((r) => { + const out: Record = {}; + GRID_COLUMNS.forEach((col) => { out[col.label] = r[col.key] ?? ""; }); + return out; + }); + exportToExcel(exportRows, "매출관리.xlsx", "매출관리"); + }} + showChart /> diff --git a/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx b/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx index 8035d5e7..6f76e4ac 100644 --- a/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx @@ -22,6 +22,7 @@ import { PageHeader } from "@/components/common/PageHeader"; import { CompactFilterBar, CompactFilterField, CompactDateRange } from "@/components/common/CompactFilterBar"; import { ProjectInfoDialog, ProjectInfoData } from "@/components/project/ProjectInfoDialog"; import { salesSaleApi, SaleListRow, SaleRegisterBody } from "@/lib/api/salesSale"; +import { exportToExcel } from "@/lib/utils/excelExport"; // SaleListRow → 정규화된 ProjectInfoData (wace 운영판 다이얼로그 1:1) const toProjectInfo = (r: SaleListRow): ProjectInfoData => ({ @@ -44,29 +45,29 @@ const toProjectInfo = (r: SaleListRow): ProjectInfoData => ({ // wace_plm salesMgmtList.jsp 컬럼 순서/라벨에 맞춤 const GRID_COLUMNS: DataGridColumn[] = [ { key: "project_no", label: "프로젝트번호", width: "w-[170px]", frozen: true }, - { key: "order_type_name", label: "주문유형", width: "w-[90px]", align: "center" }, + { key: "order_type_name", label: "주문유형", width: "w-[115px]", align: "center" }, { key: "order_date", label: "발주일", width: "w-[115px]", align: "center" }, { key: "po_no", label: "발주번호", width: "w-[140px]" }, { key: "request_date", label: "요청납기", width: "w-[115px]", align: "center" }, { key: "shipping_date", label: "출하일", width: "w-[115px]", align: "center" }, { key: "customer", label: "고객사", width: "w-[160px]" }, { key: "product_name", label: "품명", width: "w-[180px]" }, - { key: "order_quantity", label: "수주수량", width: "w-[90px]", align: "right", formatNumber: true }, - { key: "sales_quantity", label: "판매수량", width: "w-[90px]", align: "right", formatNumber: true }, - { key: "remaining_quantity", label: "잔량", width: "w-[80px]", align: "right", formatNumber: true }, - { key: "sales_unit_price", label: "판매단가", width: "w-[110px]", formatMoney: true }, - { key: "sales_supply_price", label: "판매공급가액", width: "w-[130px]", formatMoney: true }, - { key: "sales_vat", label: "부가세", width: "w-[100px]", formatMoney: true }, - { key: "sales_total_amount", label: "판매총액", width: "w-[120px]", formatMoney: true }, - { key: "sales_total_amount_krw", label: "판매원화총액", width: "w-[130px]", formatMoney: true }, - { key: "remaining_amount_krw", label: "잔량원화총액", width: "w-[130px]", formatMoney: true }, - { key: "order_status_name", label: "수주상태", width: "w-[90px]", align: "center" }, - { key: "sales_status", label: "판매상태", width: "w-[100px]", align: "center" }, - { key: "production_status", label: "생산상태", width: "w-[100px]", align: "center" }, - { key: "shipping_order_status", label: "출하지시상태", width: "w-[110px]", align: "center" }, - { key: "payment_type_name", label: "유/무상", width: "w-[80px]", align: "center" }, - { key: "sales_currency_name", label: "환종", width: "w-[70px]", align: "center" }, - { key: "sales_exchange_rate", label: "환율", width: "w-[80px]", formatMoney: true }, + { key: "order_quantity", label: "수주수량", width: "w-[115px]", align: "right", formatNumber: true }, + { key: "sales_quantity", label: "판매수량", width: "w-[115px]", align: "right", formatNumber: true }, + { key: "remaining_quantity", label: "잔량", width: "w-[95px]", align: "right", formatNumber: true }, + { key: "sales_unit_price", label: "판매단가", width: "w-[115px]", formatMoney: true }, + { key: "sales_supply_price", label: "판매공급가액", width: "w-[140px]", formatMoney: true }, + { key: "sales_vat", label: "부가세", width: "w-[115px]", formatMoney: true }, + { key: "sales_total_amount", label: "판매총액", width: "w-[130px]", formatMoney: true }, + { key: "sales_total_amount_krw", label: "판매원화총액", width: "w-[140px]", formatMoney: true }, + { key: "remaining_amount_krw", label: "잔량원화총액", width: "w-[140px]", formatMoney: true }, + { key: "order_status_name", label: "수주상태", width: "w-[115px]", align: "center" }, + { key: "sales_status", label: "판매상태", width: "w-[115px]", align: "center" }, + { key: "production_status", label: "생산상태", width: "w-[115px]", align: "center" }, + { key: "shipping_order_status", label: "출하지시상태", width: "w-[135px]", align: "center" }, + { key: "payment_type_name", label: "유/무상", width: "w-[100px]", align: "center" }, + { key: "sales_currency_name", label: "환종", width: "w-[95px]", align: "center" }, + { key: "sales_exchange_rate", label: "환율", width: "w-[95px]", formatMoney: true }, { key: "serial_no", label: "S/N", width: "w-[140px]" }, { key: "split_serial_no", label: "분할S/N", width: "w-[140px]" }, { key: "product_no", label: "품번", width: "w-[120px]" }, @@ -80,7 +81,7 @@ const GRID_COLUMNS: DataGridColumn[] = [ { key: "manager_name", label: "담당자", width: "w-[100px]", align: "center" }, { key: "incoterms", label: "인도조건", width: "w-[90px]", align: "center" }, */ - { key: "has_transaction_statement", label: "거래명세서", width: "w-[100px]", align: "center" }, + { key: "has_transaction_statement", label: "거래명세서", width: "w-[115px]", align: "center" }, ]; export default function SalesSalePage() { @@ -130,6 +131,29 @@ export default function SalesSalePage() { useEffect(() => { fetchList(); }, [fetchList]); + // ─── 하단 통계 ────────────────────────────────────────────── + // 판매 라인 수 / 수주·판매·잔량 수량 합계 / 판매공급가액·판매원화총액·잔량원화총액 합계 + const saleSummary = useMemo(() => { + const count = rows.length; + const ordQty = rows.reduce((acc, r) => acc + Number(r.order_quantity || 0), 0); + const salQty = rows.reduce((acc, r) => acc + Number(r.sales_quantity || 0), 0); + const remQty = rows.reduce((acc, r) => acc + Number(r.remaining_quantity || 0), 0); + const supplySum = rows.reduce((acc, r) => acc + Number(r.sales_supply_price || 0), 0); + const totalKrw = rows.reduce((acc, r) => acc + Number(r.sales_total_amount_krw || 0), 0); + const remKrw = rows.reduce((acc, r) => acc + Number(r.remaining_amount_krw || 0), 0); + const money = (n: number) => n.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + const intFmt = (n: number) => n.toLocaleString(); + return [ + { label: "판매 라인", value: intFmt(count), suffix: "건" }, + { label: "수주수량 합계", value: intFmt(ordQty) }, + { label: "판매수량 합계", value: intFmt(salQty) }, + { label: "잔량 합계", value: intFmt(remQty) }, + { label: "판매공급가액 합계", value: money(supplySum) }, + { label: "판매원화총액 합계", value: money(totalKrw) }, + { label: "잔량원화총액 합계", value: money(remKrw) }, + ]; + }, [rows]); + const openRegister = () => { if (!selected) { toast.warning("판매등록할 행을 선택하세요."); return; } setForm({ @@ -257,6 +281,7 @@ export default function SalesSalePage() { { setSelected(row); openRegister(); }} emptyMessage={loading ? "조회 중..." : "데이터가 없습니다"} loading={loading} + showColumnSettings + paginationStyle="range" + pageSizeOptions={[10, 15, 20, 50, 100]} + summaryStats={saleSummary} + onRefresh={fetchList} + onDownload={() => { + if (rows.length === 0) { toast.info("내보낼 데이터가 없습니다."); return; } + const exportRows = rows.map((r) => { + const out: Record = {}; + GRID_COLUMNS.forEach((col) => { out[col.label] = r[col.key] ?? ""; }); + return out; + }); + exportToExcel(exportRows, "판매관리.xlsx", "판매관리"); + }} + showChart />