MyTime: Initialstand (src/lib/year-pdf.ts)
Docker Image / build (push) Canceled after 0s

This commit is contained in:
loki committed 2026-10-05 20:39:44 +02:00
1 parent 0acd1b4da6
commit 194291bd70
1 file changed
+168
+168
View File
@@ -0,0 +1,168 @@
import { jsPDF } from "jspdf";
export interface YearPdfMonth {
title: string;
vacation: string;
overtime: string;
}
export interface YearPdfVacation {
range: string;
days: string;
}
export interface YearPdfOvertime {
date: string;
detail: string;
}
export interface YearPdfData {
year: number;
createdAt: string;
entitlement: string;
used: string;
remaining: string;
months: YearPdfMonth[];
vacations: YearPdfVacation[];
overtimeEntries: YearPdfOvertime[];
}
const HARBOR: [number, number, number] = [24, 56, 75];
const SEA: [number, number, number] = [22, 131, 123];
const MUTED: [number, number, number] = [90, 109, 118];
const LIGHT_LINE: [number, number, number] = [214, 225, 229];
const WHITE: [number, number, number] = [255, 255, 255];
const BLACK: [number, number, number] = [0, 0, 0];
const LEFT = 20;
const RIGHT = 190;
const TOP = 20;
const BOTTOM = 282;
const LINE = 8;
interface Layout {
doc: jsPDF;
data: YearPdfData;
page: number;
y: number;
}
export function downloadYearPdf(data: YearPdfData): void {
const layout: Layout = { doc: new jsPDF(), data, page: 1, y: 0 };
writeHeader(layout);
writeBalances(layout);
writeMonthTable(layout);
writeSection(
layout,
"Urlaubsbuchungen",
data.vacations.map((entry) => `${entry.range} · ${entry.days}`),
);
writeSection(
layout,
"Überstundenbuchungen",
data.overtimeEntries.map((entry) => `${entry.date} · ${entry.detail}`),
);
footer(layout);
layout.doc.save(`jahresuebersicht-${data.year}.pdf`);
}
function footer(layout: Layout): void {
const { doc, data, page } = layout;
doc.setFontSize(9);
doc.setTextColor(...MUTED);
doc.text(`Stand: ${data.createdAt} · Seite ${page}`, LEFT, 292);
doc.setTextColor(...BLACK);
}
function breakPage(layout: Layout): void {
footer(layout);
layout.doc.addPage();
layout.page += 1;
layout.y = TOP;
}
function writeHeader(layout: Layout): void {
const { doc, data } = layout;
doc.setFillColor(...HARBOR);
doc.rect(0, 0, 210, 30, "F");
doc.setTextColor(...WHITE);
doc.setFontSize(18);
doc.text(`Jahresübersicht ${data.year}`, LEFT, 18);
doc.setTextColor(...BLACK);
layout.y = 42;
}
function writeBalances(layout: Layout): void {
const { doc, data } = layout;
let { y } = layout;
doc.setFontSize(11);
doc.setDrawColor(...LIGHT_LINE);
doc.line(LEFT, y - 5, RIGHT, y - 5);
doc.text(`Anspruch: ${data.entitlement}`, LEFT, y);
doc.text(`Verbraucht: ${data.used}`, 80, y);
doc.setTextColor(...SEA);
doc.text(`Rest: ${data.remaining}`, 145, y);
doc.setTextColor(...BLACK);
layout.y = y + LINE * 2;
}
function writeMonthTable(layout: Layout): void {
const { doc, data } = layout;
let { y } = layout;
doc.setFontSize(12);
doc.text("Monat", LEFT, y);
doc.text("Urlaub", 90, y);
doc.text("Überstunden", 140, y);
y += 2;
doc.setDrawColor(...HARBOR);
doc.line(LEFT, y, RIGHT, y);
y += LINE;
doc.setFontSize(11);
doc.setDrawColor(...LIGHT_LINE);
for (const month of data.months) {
if (y > BOTTOM) {
layout.y = y;
breakPage(layout);
y = TOP;
}
doc.text(month.title, LEFT, y);
doc.text(month.vacation, 90, y);
doc.text(month.overtime, 140, y);
y += 2;
doc.line(LEFT, y, RIGHT, y);
y += LINE - 2;
}
layout.y = y + LINE;
}
function writeSection(
layout: Layout,
title: string,
lines: string[],
): void {
if (lines.length === 0) {
return;
}
const { doc } = layout;
let { y } = layout;
if (y > BOTTOM - 20) {
layout.y = y;
breakPage(layout);
y = TOP;
}
doc.setFontSize(13);
doc.text(title, LEFT, y);
y += LINE;
doc.setFontSize(10);
doc.setTextColor(...MUTED);
for (const line of lines) {
if (y > BOTTOM) {
layout.y = y;
breakPage(layout);
y = TOP;
}
doc.text(`• ${line}`, LEFT, y);
y += LINE - 2;
}
doc.setTextColor(...BLACK);
layout.y = y + LINE;
}