"use client"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; export interface JobOrderListItem { id: number; code: string | null; planStart: string | null; itemCode: string | null; itemName: string | null; reqQty: number | null; stockInLineId: number | null; itemId: number | null; lotNo: string | null; } export interface PrinterStatusRequest { printerType: "dataflex" | "laser"; printerIp?: string; printerPort?: number; } export interface PrinterStatusResponse { connected: boolean; message: string; } export interface OnPackQrDownloadRequest { jobOrders: { jobOrderId: number; itemCode: string; }[]; } /** Readable message when ZIP download returns non-OK (plain text, JSON error body, or generic). */ async function zipDownloadError(res: Response): Promise { const text = await res.text(); const ct = res.headers.get("content-type") ?? ""; if (ct.includes("application/json")) { try { const j = JSON.parse(text) as { message?: string; error?: string }; if (typeof j.message === "string" && j.message.length > 0) { return new Error(j.message); } if (typeof j.error === "string" && j.error.length > 0) { return new Error(j.error); } } catch { /* ignore parse */ } } if (text && text.length > 0 && text.length < 800 && !text.trim().startsWith("{")) { return new Error(text); } return new Error(`下載失敗(HTTP ${res.status})。請查看後端日誌或確認資料庫已執行 Liquibase 更新。`); } /** * Fetch job orders by plan date from GET /py/job-orders. * Client-side only; uses auth token from localStorage. */ export async function fetchJobOrders(planStart: string): Promise { const url = `${NEXT_PUBLIC_API_URL}/py/job-orders?planStart=${encodeURIComponent(planStart)}`; const res = await clientAuthFetch(url, { method: "GET" }); if (!res.ok) { throw new Error(`Failed to fetch job orders: ${res.status}`); } return res.json(); } export async function checkPrinterStatus( request: PrinterStatusRequest, ): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/check-printer`; const res = await clientAuthFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); const data = (await res.json()) as PrinterStatusResponse; if (!res.ok) { return data; } return data; } export async function downloadOnPackQrZip( request: OnPackQrDownloadRequest, ): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/download-onpack-qr`; const res = await clientAuthFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); if (!res.ok) { throw await zipDownloadError(res); } return res.blob(); } /** OnPack2023 檸檬機 — text QR template (`onpack2030_2`), no separate .bmp */ export async function downloadOnPackTextQrZip( request: OnPackQrDownloadRequest, ): Promise { const url = `${NEXT_PUBLIC_API_URL}/plastic/download-onpack-qr-text`; const res = await clientAuthFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); if (!res.ok) { throw await zipDownloadError(res); } return res.blob(); }