fix(reports): lange Projektnamen umbrechen statt abschneiden

Die Reporttabelle lief mit table-auto: ein langer Projektname wie
"HAYS - Globales Template und Implementierung: ... _Fresenius -
01.10.2025-31.07.2026" zog die Namensspalte auf und schob Stunden und
Betrag aus dem sichtbaren Bereich. Das truncate am Namen half nicht,
weil die Spalte keine Breite hatte, an der es haette greifen koennen.

Die Tabelle steht jetzt auf table-fixed mit festen Breiten fuer die
Zahlenspalten; der Name bekommt den Rest und bricht um (break-words).
Die Zahlen selbst bleiben einzeilig.

Dazu die Kennzahlenkarte: ihre vier Spalten mit den grossen Betraegen
passten ab lg nicht mehr in die Breite, der rechte Rand wurde von
overflow-x-hidden abgeschnitten. Sie bricht jetzt auf zwei Spalten um
und zeigt vier erst ab xl.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-21 17:16:13 +02:00
co-authored by Claude Opus 5
parent 80e46910fd
commit c3aed8a3d3
2 changed files with 16 additions and 16 deletions
+11 -11
View File
@@ -52,15 +52,15 @@ export const ReportTable: React.FC<{
return (
<div className="scrollbar-slim overflow-x-auto">
<table className="w-full min-w-[720px] text-sm">
<table className="w-full min-w-[900px] table-fixed text-sm">
<thead>
<tr className="border-b border-ink-200 text-[11px] text-ink-400">
{head('name', label, 'left', 'pl-4')}
{head('minutes', 'Stunden')}
<th className="w-[22%] px-3 py-3" />
{showUtil && head('utilization', 'Auslastung')}
{head('billable_minutes', 'Billable Std.')}
{showAmounts && head('amount', 'Billable Betrag', 'right', 'pr-4')}
{head('minutes', 'Stunden', 'right', 'w-[108px]')}
<th className="w-[20%] px-3 py-3" />
{showUtil && head('utilization', 'Auslastung', 'right', 'w-[116px]')}
{head('billable_minutes', 'Billable Std.', 'right', 'w-[160px]')}
{showAmounts && head('amount', 'Billable Betrag', 'right', 'w-[150px] pr-4')}
</tr>
</thead>
<tbody>
@@ -82,12 +82,12 @@ export const ReportTable: React.FC<{
<span className="h-2.5 w-2.5 shrink-0 rounded-full" style={{ background: colorFor(r.customer_id ?? r.project_id ?? r.name, r.color) }} />
) : null}
<span className="min-w-0">
<span className={`block truncate font-bold ${clickable ? 'text-ink-900 underline decoration-ink-200 underline-offset-4 group-hover:decoration-brand-400' : 'text-ink-800'}`}>{r.name}</span>
{r.customer_name && <span className="block truncate text-[11px] text-ink-400">{r.customer_name}{r.code ? ` · ${r.code}` : ''}</span>}
<span className={`block break-words font-bold ${clickable ? 'text-ink-900 underline decoration-ink-200 underline-offset-4 group-hover:decoration-brand-400' : 'text-ink-800'}`}>{r.name}</span>
{r.customer_name && <span className="block break-words text-[11px] text-ink-400">{r.customer_name}{r.code ? ` · ${r.code}` : ''}</span>}
</span>
</div>
</td>
<td className="tabular px-3 py-3 text-right font-extrabold text-ink-900">{formatHours(r.minutes)}</td>
<td className="tabular whitespace-nowrap px-3 py-3 text-right font-extrabold text-ink-900">{formatHours(r.minutes)}</td>
<td className="px-3 py-3">
<SplitBar a={r.billable_minutes} b={r.non_billable_minutes} max={max} delay={i * 30} />
</td>
@@ -96,10 +96,10 @@ export const ReportTable: React.FC<{
{r.utilization == null ? '–' : `${r.utilization} %`}
</td>
)}
<td className="tabular px-3 py-3 text-right text-ink-700">
<td className="tabular whitespace-nowrap px-3 py-3 text-right text-ink-700">
{formatHours(r.billable_minutes)} <span className="inline-block w-14 whitespace-nowrap text-ink-400">({share} %)</span>
</td>
{showAmounts && <td className="tabular py-3 pl-3 pr-4 text-right font-extrabold text-ink-900">{formatEuro(r.amount, 2)}</td>}
{showAmounts && <td className="tabular whitespace-nowrap py-3 pl-3 pr-4 text-right font-extrabold text-ink-900">{formatEuro(r.amount, 2)}</td>}
</tr>
);
})}
+5 -5
View File
@@ -187,13 +187,13 @@ const TimeReport: React.FC = () => {
<Spinner label="Lade Report…" />
) : (
<>
<Card className={`animate-rise grid gap-6 p-5 transition-opacity sm:p-6 lg:grid-cols-[1fr_1.2fr_1.3fr_1fr] ${report.loading ? 'opacity-60' : ''}`}>
<div>
<Card className={`animate-rise grid gap-6 p-5 transition-opacity sm:grid-cols-2 sm:p-6 xl:grid-cols-[1fr_1.2fr_1.3fr_1fr] ${report.loading ? 'opacity-60' : ''}`}>
<div className="min-w-0">
<p className="text-sm font-semibold text-ink-500">Gesamtstunden</p>
<CountUp value={s!.minutes / 60} format={(v) => v.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} className="text-4xl font-extrabold text-ink-900" />
<p className="mt-1 text-xs text-ink-400">{s!.entries} Buchungen</p>
</div>
<div className="flex items-center gap-4">
<div className="flex min-w-0 items-center gap-4">
<Donut percent={share} size={96} stroke={13} />
<div className="flex-1 space-y-1.5 text-sm">
<p className="flex items-center justify-between gap-3">
@@ -219,7 +219,7 @@ const TimeReport: React.FC = () => {
</label>
</div>
</div>
<div>
<div className="min-w-0">
<p className="text-sm font-semibold text-ink-500">Billable Betrag</p>
<CountUp value={s!.amount} format={(v) => formatEuro(v, 2)} className="text-4xl font-extrabold text-ink-900" />
<label className="mt-2 flex cursor-pointer items-center gap-2 text-sm font-semibold text-ink-700">
@@ -230,7 +230,7 @@ const TimeReport: React.FC = () => {
Festpreis-Beträge werden über den virtuellen Stundensatz (Budget ÷ gebuchte Stunden) verteilt – nur für den internen Gebrauch.
</p>
</div>
<div>
<div className="min-w-0">
<p className="text-sm font-semibold text-ink-500">Auslastung</p>
<CountUp value={capacity ? (s!.minutes_until_today / capacity) * 100 : 0} format={(v) => `${Math.round(v)} %`} className="text-4xl font-extrabold text-ink-900" />
<p className="mt-1 text-xs text-ink-400">Kapazität {formatHours(capacity, 0)} h bis heute</p>