Files
ProxLedger/src/app/(dashboard)/kunden/page.tsx
T
florian.wildeandClaude Opus 5 cb009efd7d feat(export): CSV- und PDF-Ausgaben fuer Hosts, Kunden und Rechnungen (#010)
PDFs entstehen serverseitig mit @react-pdf/renderer, mit einfarbigem Logo,
wiederholtem Spaltenkopf und Summenzeile. Alle Exporte uebernehmen die Filter
der jeweiligen Ansicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:12:58 +02:00

105 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { ContentHeader } from "@/components/layout/ContentHeader";
import { AblaufBadge, Badge } from "@/components/ui/Badges";
import { Card } from "@/components/ui/Card";
import { kundenListe, offeneRechnungspunkteProKunde } from "@/server/queries/kunden";
export const metadata = { title: "Kunden" };
export default async function KundenSeite() {
const [kunden, rechnungspunkte] = await Promise.all([kundenListe(), offeneRechnungspunkteProKunde()]);
const heute = new Date();
return (
<>
<ContentHeader titel="Kunden" brotkrumen={[{ titel: "Kunden" }]} />
<div className="app-content">
<div className="container-fluid">
<Card
titel={`${kunden.length} Kunden`}
rahmen="primary"
tabelle
werkzeuge={
<>
{/* Downloads: bewusst kein <Link>, sonst würde Next.js die Datei vorab laden. */}
<a href="/kunden/export" download className="btn btn-sm btn-outline-secondary">
<i className="bi bi-filetype-csv me-1" aria-hidden="true" />
CSV
</a>
<a href="/kunden/export?format=pdf" download className="btn btn-sm btn-outline-secondary">
<i className="bi bi-filetype-pdf me-1" aria-hidden="true" />
PDF
</a>
<Link href="/kunden/neu" className="btn btn-sm btn-primary">
<i className="bi bi-plus-lg me-1" aria-hidden="true" />
Kunde anlegen
</Link>
</>
}
>
<div className="table-responsive">
<table className="table table-striped align-middle">
<thead>
<tr>
<th scope="col">Kunde</th>
<th scope="col">Kundennummer</th>
<th scope="col">Hosts</th>
<th scope="col">Aktiv</th>
<th scope="col">Nächster Ablauf</th>
<th scope="col">Warnungen</th>
<th scope="col">Rechnungen</th>
</tr>
</thead>
<tbody>
{kunden.map((kunde) => {
const punkte = rechnungspunkte.get(kunde.id);
return (
<tr key={kunde.id}>
<td>
<Link href={`/kunden/${kunde.id}`} className="fw-semibold text-decoration-none">
{kunde.name}
</Link>
<div className="small text-body-secondary">{kunde.ansprechpartner ?? "–"}</div>
</td>
<td className="pl-mono">{kunde.kundennummer}</td>
<td>{kunde.hosts}</td>
<td>{kunde.aktiv}</td>
<td>
<AblaufBadge datum={kunde.naechsterAblauf?.toISOString() ?? null} heute={heute} />
</td>
<td>
{kunde.offeneAlerts > 0 ? (
<Badge farbe="warning">{kunde.offeneAlerts} offen</Badge>
) : (
<Badge farbe="success">keine</Badge>
)}
</td>
<td>
{punkte && punkte.offen > 0 ? (
<Badge farbe="danger">{punkte.offen} offen</Badge>
) : (
<Badge farbe="success">erledigt</Badge>
)}
</td>
</tr>
);
})}
{kunden.length === 0 ? (
<tr>
<td colSpan={7} className="text-center text-body-secondary py-4">
Noch keine Kunden angelegt.{" "}
<Link href="/kunden/neu">Jetzt den ersten Kunden anlegen.</Link>
</td>
</tr>
) : null}
</tbody>
</table>
</div>
</Card>
</div>
</div>
</>
);
}