Breite Tabellen (Kunden, Rechnungen, Ablauf-Kalender) werden unterhalb von 768 px zu Karten: jede Angabe mit ihrer Beschriftung, statt rechts abgeschnitten zu sein. Kartenkoepfe brechen um, die Jahresbahn bekommt eine schmalere Namensspalte und einen Hinweis aufs Wischen. Standort am Host (#036): Das Formular sendete nur den Kunden, die Aktion las aber auch standortId - jede Kundenzuweisung hat den Standort still geloescht. Jetzt stehen Kunde und Standort in einem Formular, die Liste richtet sich nach dem Kunden, und die Aktion prueft, dass der Standort zum Kunden gehoert. Nebenbefund: Das Kontrollkaestchen fuer den Rechnungsstatus trug pl-touch-target und wurde dadurch selbst zum 44 Pixel hohen leeren Kasten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
107 lines
4.5 KiB
TypeScript
107 lines
4.5 KiB
TypeScript
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 pl-karten-tabelle">
|
||
<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 className="pl-karten-kopf">
|
||
<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" data-label="Kundennummer">
|
||
{kunde.kundennummer}
|
||
</td>
|
||
<td data-label="Hosts">{kunde.hosts}</td>
|
||
<td data-label="Aktiv lizenziert">{kunde.aktiv}</td>
|
||
<td data-label="Nächster Ablauf">
|
||
<AblaufBadge datum={kunde.naechsterAblauf?.toISOString() ?? null} heute={heute} />
|
||
</td>
|
||
<td data-label="Warnungen">
|
||
{kunde.offeneAlerts > 0 ? (
|
||
<Badge farbe="warning">{kunde.offeneAlerts} offen</Badge>
|
||
) : (
|
||
<Badge farbe="success">keine</Badge>
|
||
)}
|
||
</td>
|
||
<td data-label="Rechnungen">
|
||
{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>
|
||
</>
|
||
);
|
||
}
|