From a6aefb087de60e347205cfe7ffa529b234b170f1 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:09:49 +0100 Subject: [PATCH 01/30] feat: initial bemestingsplan generating --- .../app/components/pdf/BemestingsplanPDF.tsx | 468 ++++++ fdm-app/app/components/pdf/styles.ts | 109 ++ fdm-app/app/components/pdf/ui/PdfBadge.tsx | 14 + fdm-app/app/components/pdf/ui/PdfCard.tsx | 10 + fdm-app/app/components/pdf/ui/PdfTable.tsx | 44 + ...id_farm.$calendar.bemestingsplan[.]pdf.tsx | 274 ++++ fdm-app/app/routes/farm.$b_id_farm._index.tsx | 15 + fdm-app/package.json | 1 + fdm-app/public/fonts/Inter-Bold.ttf | 1447 +++++++++++++++++ fdm-app/public/fonts/Inter-Medium.ttf | 1447 +++++++++++++++++ fdm-app/public/fonts/Inter-Regular.ttf | 1447 +++++++++++++++++ pnpm-lock.yaml | 782 ++++++--- 12 files changed, 5853 insertions(+), 205 deletions(-) create mode 100644 fdm-app/app/components/pdf/BemestingsplanPDF.tsx create mode 100644 fdm-app/app/components/pdf/styles.ts create mode 100644 fdm-app/app/components/pdf/ui/PdfBadge.tsx create mode 100644 fdm-app/app/components/pdf/ui/PdfCard.tsx create mode 100644 fdm-app/app/components/pdf/ui/PdfTable.tsx create mode 100644 fdm-app/app/routes/farm.$b_id_farm.$calendar.bemestingsplan[.]pdf.tsx create mode 100644 fdm-app/public/fonts/Inter-Bold.ttf create mode 100644 fdm-app/public/fonts/Inter-Medium.ttf create mode 100644 fdm-app/public/fonts/Inter-Regular.ttf diff --git a/fdm-app/app/components/pdf/BemestingsplanPDF.tsx b/fdm-app/app/components/pdf/BemestingsplanPDF.tsx new file mode 100644 index 000000000..1cd114d33 --- /dev/null +++ b/fdm-app/app/components/pdf/BemestingsplanPDF.tsx @@ -0,0 +1,468 @@ +import { Document, Page, Text, View } from "@react-pdf/renderer"; +import { pdfStyles } from "./styles"; +import { PdfCard } from "./ui/PdfCard"; +import { + PdfTable, + PdfTableCell, + PdfTableHeader, + PdfTableRow, +} from "./ui/PdfTable"; + +export interface BemestingsplanData { + farm: { + name: string; + kvk?: string; + }; + year: string; + totalArea: number; + norms: { + nitrogen: number; + manure: number; + phosphate: number; + }; + totalAdvice: { + n: number; + p2o5: number; + k2o: number; + om: number; + }; + plannedUsage: { + n: number; + nw: number; + p2o5: number; + k2o: number; + om: number; + }; + fields: Array<{ + id: string; + name: string; + area: number; + mainCrop: string; + catchCrop?: string; + soil: { + date?: string; + pAl?: number; + pCaCl?: number; + kCc?: number; + ph?: number; + om?: number; + soilTypeAgr?: string; + clay?: number; + sand?: number; + silt?: number; + }; + norms: { + nitrogen: number; + manure: number; + phosphate: number; + }; + advice: { + n: number; + p2o5: number; + k2o: number; + mg?: number; + s?: number; + om?: number; + }; + planned: { + n: number; + nw: number; + p2o5: number; + k2o: number; + om: number; + }; + omBalance?: { + balance: number; + supply: number; + supplyManure: number; + supplyCompost: number; + supplyCultivations: number; + supplyResidues: number; + degradation: number; + }; + applications: Array<{ + date: string; + product: string; + quantity: number; + n: number; + nw: number; + p2o5: number; + k2o: number; + om: number; + }>; + }>; +} + +const Footer = () => ( + + FDM - Gegenereerd op {new Date().toLocaleDateString("nl-NL")} + `Pagina ${pageNumber} / ${totalPages}`} /> + +); + +const SectionHeader = ({ children }: { children: string }) => ( + {children} +); + +/** + * Renders a chemical symbol with subscripts using nested Text components. + * This is the most reliable way to achieve subscripts in react-pdf with standard fonts. + */ +const Chemical = ({ symbol, style }: { symbol: string, style?: any }) => { + const parts = symbol.split(/(\d+)/); + return ( + + {parts.map((part, i) => ( + + {part} + + ))} + + ); +}; + +const soilTypeLabels: Record = { + moerige_klei: "Moerige klei", + rivierklei: "Rivierklei", + dekzand: "Dekzand", + zeeklei: "Zeeklei", + dalgrond: "Dalgrond", + veen: "Veen", + loess: "Löss", + duinzand: "Duinzand", + maasklei: "Maasklei", +}; + +export const BemestingsplanPDF = ({ data }: { data: BemestingsplanData }) => ( + + {/* Page 1: Cover & Farm Summary */} + + + + FDM + + + Bemestingsplan {data.year} + + + + + Bedrijfsgegevens + + + + Bedrijfsnaam + {data.farm.name} + + + kvk nummer + {data.farm.kvk || "-"} + + + totaal oppervlakte + {data.totalArea.toFixed(2)} ha + + + + + + + + + Gebruiksruimte (gepland / ruimte) + + + + Stikstof totaal + {Math.round(data.plannedUsage.nw)} / {Math.round(data.norms.nitrogen)} kg N + + + Dierlijke mest + {Math.round(data.plannedUsage.n)} / {Math.round(data.norms.manure)} kg N + + + Fosfaat + + {Math.round(data.plannedUsage.p2o5)} / {Math.round(data.norms.phosphate)} kg + + + + + + + Bemestingsadvies (gepland / advies) + + + + Stikstof (N) + {Math.round(data.plannedUsage.n)} / {Math.round(data.totalAdvice.n)} kg + + + Fosfaat () + {Math.round(data.plannedUsage.p2o5)} / {Math.round(data.totalAdvice.p2o5)} kg + + + Kali () + {Math.round(data.plannedUsage.k2o)} / {Math.round(data.totalAdvice.k2o)} kg + + + + + + +