import { NextResponse } from 'next/server'; import { createServiceClient, createServerSupabaseClient } from '@/lib/supabase/server'; import QRCode from 'qrcode'; import ExcelJS from 'exceljs'; const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'https://gemeindeportal.vercel.app'; export async function GET() { try { // Auth check const authClient = await createServerSupabaseClient(); const { data: { user } } = await authClient.auth.getUser(); if (!user) { return NextResponse.json({ error: 'Nicht authentifiziert.' }, { status: 401 }); } const supabase = createServiceClient(); const { data: zaehler, error } = await supabase .from('wasserzaehler') .select('*') .order('kundennummer', { ascending: true }); if (error || !zaehler || zaehler.length === 0) { return NextResponse.json( { error: 'Keine Wasserzähler gefunden.' }, { status: 404 } ); } // Create workbook const workbook = new ExcelJS.Workbook(); workbook.creator = 'Gemeindeportal'; workbook.created = new Date(); // --- Sheet 1: Übersicht (Tabelle mit allen Kunden) --- const listSheet = workbook.addWorksheet('Übersicht', { pageSetup: { paperSize: 9, // A4 orientation: 'landscape', fitToPage: true, fitToWidth: 1, fitToHeight: 0, }, }); listSheet.columns = [ { header: 'Kundennummer', key: 'kundennummer', width: 18 }, { header: 'Zählernummer', key: 'zaehlernummer', width: 18 }, { header: 'Name', key: 'name', width: 28 }, { header: 'Adresse', key: 'adresse', width: 30 }, { header: 'Letzter Stand (m³)', key: 'stand', width: 18 }, { header: 'QR-Code URL', key: 'url', width: 55 }, ]; // Header styling const headerRow = listSheet.getRow(1); headerRow.font = { bold: true, size: 11 }; headerRow.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF0D2B4E' }, }; headerRow.font = { bold: true, size: 11, color: { argb: 'FFFFFFFF' } }; headerRow.alignment = { vertical: 'middle' }; headerRow.height = 28; for (const z of zaehler) { const url = `${BASE_URL}/wasserzaehler?token=${z.access_token}`; listSheet.addRow({ kundennummer: z.kundennummer || '', zaehlernummer: z.zaehlernummer, name: z.haushalt_name, adresse: z.adresse || '', stand: z.alter_stand, url, }); } // Auto-filter listSheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: zaehler.length + 1, column: 6 }, }; // --- Sheet 2+: Ein Blatt pro Kunde mit QR-Code (Druckseiten) --- for (let i = 0; i < zaehler.length; i++) { const z = zaehler[i]; const url = `${BASE_URL}/wasserzaehler?token=${z.access_token}`; // Generate QR code as PNG buffer const qrBuffer = await QRCode.toBuffer(url, { width: 400, margin: 2, errorCorrectionLevel: 'M', type: 'png', }); const sheetName = `${z.kundennummer || `Kunde ${i + 1}`}`.slice(0, 31); const sheet = workbook.addWorksheet(sheetName, { pageSetup: { paperSize: 9, // A4 orientation: 'portrait', horizontalCentered: true, verticalCentered: true, margins: { left: 0.7, right: 0.7, top: 1.0, bottom: 1.0, header: 0.3, footer: 0.3, }, }, }); // Set column widths sheet.getColumn(1).width = 5; sheet.getColumn(2).width = 25; sheet.getColumn(3).width = 35; sheet.getColumn(4).width = 5; // Title sheet.mergeCells('B2:C2'); const titleCell = sheet.getCell('B2'); titleCell.value = 'Gemeindeamt Weißkirchen an der Traun'; titleCell.font = { bold: true, size: 16, color: { argb: 'FF0D2B4E' } }; titleCell.alignment = { horizontal: 'center', vertical: 'middle' }; sheet.getRow(2).height = 30; // Subtitle sheet.mergeCells('B3:C3'); const subtitleCell = sheet.getCell('B3'); subtitleCell.value = `Wasserzähler-Ablesung ${new Date().getFullYear()}`; subtitleCell.font = { size: 12, color: { argb: 'FF666666' } }; subtitleCell.alignment = { horizontal: 'center', vertical: 'middle' }; sheet.getRow(3).height = 22; // QR Code image — rows 5-20 const imageId = workbook.addImage({ buffer: qrBuffer as unknown as ExcelJS.Buffer, extension: 'png', }); sheet.addImage(imageId, { tl: { col: 1.5, row: 5 }, ext: { width: 250, height: 250 }, }); // Space for QR code for (let row = 5; row <= 20; row++) { sheet.getRow(row).height = 18; } // Customer info below QR code const infoStartRow = 22; // Kundennummer sheet.getCell(`B${infoStartRow}`).value = 'Kundennummer'; sheet.getCell(`B${infoStartRow}`).font = { size: 10, color: { argb: 'FF999999' } }; sheet.getCell(`B${infoStartRow}`).alignment = { horizontal: 'center' }; sheet.mergeCells(`B${infoStartRow}:C${infoStartRow}`); sheet.getCell(`B${infoStartRow + 1}`).value = z.kundennummer || '—'; sheet.getCell(`B${infoStartRow + 1}`).font = { bold: true, size: 22 }; sheet.getCell(`B${infoStartRow + 1}`).alignment = { horizontal: 'center' }; sheet.mergeCells(`B${infoStartRow + 1}:C${infoStartRow + 1}`); sheet.getRow(infoStartRow + 1).height = 32; // Zählernummer sheet.getCell(`B${infoStartRow + 3}`).value = 'Zählernummer'; sheet.getCell(`B${infoStartRow + 3}`).font = { size: 10, color: { argb: 'FF999999' } }; sheet.getCell(`B${infoStartRow + 3}`).alignment = { horizontal: 'center' }; sheet.mergeCells(`B${infoStartRow + 3}:C${infoStartRow + 3}`); sheet.getCell(`B${infoStartRow + 4}`).value = z.zaehlernummer; sheet.getCell(`B${infoStartRow + 4}`).font = { bold: true, size: 16 }; sheet.getCell(`B${infoStartRow + 4}`).alignment = { horizontal: 'center' }; sheet.mergeCells(`B${infoStartRow + 4}:C${infoStartRow + 4}`); sheet.getRow(infoStartRow + 4).height = 26; // Instructions sheet.mergeCells(`B${infoStartRow + 7}:C${infoStartRow + 7}`); const instrCell = sheet.getCell(`B${infoStartRow + 7}`); instrCell.value = 'Scannen Sie den QR-Code mit Ihrem Smartphone'; instrCell.font = { size: 11, color: { argb: 'FF666666' } }; instrCell.alignment = { horizontal: 'center', wrapText: true }; sheet.mergeCells(`B${infoStartRow + 8}:C${infoStartRow + 8}`); const instrCell2 = sheet.getCell(`B${infoStartRow + 8}`); instrCell2.value = 'um Ihren aktuellen Zählerstand zu melden.'; instrCell2.font = { size: 11, color: { argb: 'FF666666' } }; instrCell2.alignment = { horizontal: 'center', wrapText: true }; // Border box around content for (let row = 1; row <= infoStartRow + 9; row++) { const r = sheet.getRow(row); r.getCell(2).border = { left: { style: 'thin', color: { argb: 'FFE0E0E0' } } }; r.getCell(3).border = { right: { style: 'thin', color: { argb: 'FFE0E0E0' } } }; } } // Generate buffer const buffer = await workbook.xlsx.writeBuffer(); const filename = `wasserzaehler_qrcodes_${new Date().toISOString().split('T')[0]}.xlsx`; return new NextResponse(buffer, { status: 200, headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'Content-Disposition': `attachment; filename="${filename}"`, }, }); } catch (err) { console.error('QR-Code Excel Error:', err); return NextResponse.json( { error: 'Interner Serverfehler.' }, { status: 500 } ); } }