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.
This commit is contained in:
DEls 2026-08-28 12:57:29 +02:00
parent c36eaeed51
commit 7b8fe71db3

View file

@ -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<ArrayBuffer>
// 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<ArrayBuffer>;
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 },
);
}
}