diff --git a/src/app/(main)/m/admin/orders/page.tsx b/src/app/(main)/m/admin/orders/page.tsx index 73d4d83..9444ec0 100644 --- a/src/app/(main)/m/admin/orders/page.tsx +++ b/src/app/(main)/m/admin/orders/page.tsx @@ -298,16 +298,8 @@ export default function AdminOrdersPage() { {/* 우측: 거래명세표 미리보기 */}
-
- 거래명세표 미리보기 - {detail && ( - - 엑셀 다운로드 - - )} +
+ 거래명세표 미리보기
{!detail ? ( @@ -316,7 +308,7 @@ export default function AdminOrdersPage() {
왼쪽에서 발주를 선택하세요.
) : ( - + )}
@@ -332,6 +324,7 @@ function StatementPreview({ onCancel, busy, onReload, + onReloadList, }: { order: DetailOrder; items: DetailLine[]; @@ -339,7 +332,60 @@ function StatementPreview({ onCancel: (o: Order) => void; busy: boolean; onReload: () => void; + onReloadList: () => void; }) { + const [shipping, setShipping] = useState(false); + + const shipNow = async () => { + const lack = items.filter((it) => it.KIND === "ITEM" && Number(it.STOCK_QTY) < Number(it.QTY)); + if (lack.length > 0) { + const ok = await Swal.fire({ + icon: "warning", + title: "재고 부족 항목이 있습니다.", + html: `
${lack.map((it) => `· ${it.ITEM_NAME} (요청 ${fmt(it.QTY)} / 현재고 ${fmt(it.STOCK_QTY)})`).join("
")}

그래도 출고를 시도하시겠습니까?`, + showCancelButton: true, + confirmButtonText: "출고 시도", + cancelButtonText: "취소", + confirmButtonColor: "#0f766e", + }); + if (!ok.isConfirmed) return; + } else { + const ok = await Swal.fire({ + icon: "question", + title: "출고 처리하시겠습니까?", + html: `발주 ${order.ORDER_NO}
거래처: ${order.COMPANY_NAME}
합계: ₩${fmt(order.TOTAL_AMOUNT)}

재고가 차감되고, 거래명세표가 메일로 발송됩니다.`, + showCancelButton: true, + confirmButtonText: "출고", + cancelButtonText: "취소", + confirmButtonColor: "#0f766e", + }); + if (!ok.isConfirmed) return; + } + setShipping(true); + try { + const res = await fetch("/api/m/orders/approve", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ objid: order.OBJID }), + }); + const j = await res.json(); + if (j.success) { + await Swal.fire({ + icon: "success", + title: "출고 완료", + html: j.mailSent ? "거래명세표 메일이 발송되었습니다." : `메일 발송에 실패했습니다.`, + timer: 1800, + showConfirmButton: false, + }); + onReloadList(); + onReload(); + } else { + Swal.fire({ icon: "error", title: "출고 실패", text: j.message ?? "오류" }); + } + } finally { + setShipping(false); + } + }; + const statementRef = useRef(null); const lowStock = items.filter((it) => it.KIND === "ITEM" && Number(it.STOCK_QTY) < Number(it.QTY)); const editable = order.STATUS === "REQUESTED"; @@ -434,8 +480,8 @@ function StatementPreview({ }; return (
- {/* 공유/캡처 버튼 — 캡처 영역 밖에 배치 */} -
+ {/* 공유/캡처/엑셀/출고 버튼 — 캡처 영역 밖에 배치 */} +
+ + 엑셀 다운로드 + + {editable && ( + + )}
@@ -615,17 +679,17 @@ function StatementPreview({
{/* /statementRef capture area */} {order.STATUS === "REQUESTED" && ( -
+
+ + ※ 다중 발주를 일괄 처리하려면 왼쪽 리스트에서 체크박스 선택 후 상단 [출고] 버튼을 사용하세요. + - - ※ 출고는 왼쪽 리스트에서 체크박스 선택 후 상단 [출고] 버튼으로 처리하세요. -
)} {(order.STATUS === "APPROVED" || order.STATUS === "SHIPPED" || order.STATUS === "PAID") && ( diff --git a/src/app/(main)/m/admin/procurements/page.tsx b/src/app/(main)/m/admin/procurements/page.tsx index e64af6d..6e9fc25 100644 --- a/src/app/(main)/m/admin/procurements/page.tsx +++ b/src/app/(main)/m/admin/procurements/page.tsx @@ -314,8 +314,8 @@ function ProcurementForm({ detail, vendors, onSetVendor, onSetMemo, onSetTerm, o return (
- {/* 공유/엑셀 버튼 — 캡처 영역 밖 */} -
+ {/* 공유/인쇄/엑셀 버튼 — 캡처 영역 밖 */} +
+ ("user"); + const searchParams = useSearchParams(); + const tabParam = searchParams.get("tab"); + const initialTab: AdminTab = tabParam && (VALID_TABS as string[]).includes(tabParam) + ? (tabParam as AdminTab) + : "user"; + const [activeTab, setActiveTab] = useState(initialTab); const [groups, setGroups] = useState([]); const [openSections, setOpenSections] = useState>(new Set(["권한 및 사용자 관리"])); + // 사이드바에서 ?tab= 으로 다른 탭 클릭 시 동기화 + useEffect(() => { + if (tabParam && (VALID_TABS as string[]).includes(tabParam)) { + setActiveTab(tabParam as AdminTab); + } + }, [tabParam]); + const toggleSection = (label: string) => { setOpenSections((prev) => { const next = new Set(prev); @@ -124,11 +144,18 @@ export default function AdminPanelPage() {
{/* 좌측 메뉴 (adminMenu.jsp 대응) */}