From 7b8fe71db3ed8e9c5065cfd74c59dd4ca64e8dc7 Mon Sep 17 00:00:00 2001 From: DEls Date: Fri, 28 Aug 2026 12:57:29 +0200 Subject: [PATCH] feat(plant-hours): GET /api/plant-hours/template route Emits the canonical xlsx with a sample row derived from the first vehicle in PocketBase. Falls back to a hardcoded sample if the vehicles collection is empty. --- src/app/api/plant-hours/template/route.ts | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/app/api/plant-hours/template/route.ts diff --git a/src/app/api/plant-hours/template/route.ts b/src/app/api/plant-hours/template/route.ts new file mode 100644 index 0000000..8016fa0 --- /dev/null +++ b/src/app/api/plant-hours/template/route.ts @@ -0,0 +1,57 @@ +import { NextResponse } from "next/server"; +import { pbAdmin } from "@/lib/pb"; +import { buildPlantHoursTemplateWorkbook } from "@/lib/plant-hours-template"; + +/** + * GET /api/plant-hours/template + * + * Emits the canonical Plant_Hours_Template_2.xlsx workbook. The + * sample row's vehicle QR / description / cost-per-hour are pulled + * from the first vehicle in PocketBase (plant or fuel — doesn't + * matter, the template just needs plausible real data). + */ +export async function GET() { + const start = Date.now(); + try { + const pb = await pbAdmin(); + const first = await pb + .collection("vehicles") + .getList(1, 1, { sort: "description" }) + .then((r) => r.items[0]) + .catch(() => null); + + const sample = first + ? { + qr_code_id: first.qr_code_id || "QR-A", + description: first.description || "Generator X", + cost_per_hour: first.cost_per_hour || 185, + } + : { qr_code_id: "QR-A", description: "Generator X", cost_per_hour: 185 }; + + // SheetJS returns a Uint8Array whose underlying buffer TS can't + // narrow to ArrayBuffer (the lib.dom Uint8Array + // generic is stricter than what XLSX.write emits). The xlsx bytes + // are unambiguously an ArrayBuffer-backed view at runtime, so a + // single cast is enough to satisfy NextResponse's BodyInit type. + const bytes = buildPlantHoursTemplateWorkbook(sample) as Uint8Array; + console.log( + `[plant-hours-template] size=${bytes.byteLength} duration_ms=${Date.now() - start}`, + ); + return new NextResponse(bytes, { + status: 200, + headers: { + "Content-Type": + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Content-Disposition": + 'attachment; filename="Plant_Hours_Template_2.xlsx"', + "Cache-Control": "no-store", + }, + }); + } catch (err) { + console.error(`[plant-hours-template] error:`, err); + return NextResponse.json( + { error: err instanceof Error ? err.message : "template_failed" }, + { status: 500 }, + ); + } +}