feat: Anleitung-Button im Footer + Statistik-Effect-Fix
- Footer: 3-Spalten-Layout mit 'Anleitung'-Button in der Mitte (Link auf /anleitung.html) - anleitung.html: 'Zurück zum Logbuch'-Button oben (beim Drucken ausgeblendet) - Statistik: synchrones setState im Effect durch abgeleiteten loading/error-State ersetzt; Fetch-Abbruch mit cancelled-Flag; as any durch ArtFuehrung-Cast ersetzt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
### Zugriff zur Datenbank:
|
||||
|
||||
Die Remote-Datenbank auf logbuch.fuerst-.stuttgart.de wird zum lokalen entwickeln über einen SSH-Tunner erreicht:
|
||||
|
||||
ssh -L 3336:localhost:3336 rxf@logbuch.fuerst-stuttgart.de -N
|
||||
~~~
|
||||
ssh -L 3336:localhost:3336 rxf@logbuch.fuerst-stuttgart.de -N
|
||||
~~~
|
||||
|
||||
+9
-1
@@ -179,12 +179,20 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<footer className="mt-6 flex justify-between items-center text-xs sm:text-sm text-gray-600 px-1 sm:px-4 print:hidden">
|
||||
<footer className="mt-6 grid grid-cols-3 items-center text-xs sm:text-sm text-gray-600 px-1 sm:px-4 print:hidden">
|
||||
<div>
|
||||
<a href="mailto:rxf@gmx.de" className="text-blue-600 hover:underline">
|
||||
rxf@gmx.de
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<a
|
||||
href="/anleitung.html"
|
||||
className="px-3 py-1.5 bg-gray-200 hover:bg-gray-300 rounded-lg text-gray-700"
|
||||
>
|
||||
Anleitung
|
||||
</a>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
v{version} — {buildDate}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import type { Kuppel } from '@/types/logbuch';
|
||||
import type { ArtFuehrung, Kuppel } from '@/types/logbuch';
|
||||
import { artLabel } from '@/types/logbuch';
|
||||
|
||||
interface MonthlyRow {
|
||||
@@ -33,16 +33,20 @@ interface Props {
|
||||
export default function Statistik({ kuppel }: Props) {
|
||||
const [year, setYear] = useState(new Date().getFullYear());
|
||||
const [data, setData] = useState<StatsData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [fetchError, setFetchError] = useState<{ year: number; kuppel: Kuppel } | null>(null);
|
||||
|
||||
const error = fetchError?.year === year && fetchError?.kuppel === kuppel
|
||||
? 'Fehler beim Laden der Statistik.'
|
||||
: '';
|
||||
const loading = !error && (!data || data.year !== year || data.kuppel !== kuppel);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
let cancelled = false;
|
||||
fetch(`/api/statistik?kuppel=${encodeURIComponent(kuppel)}&year=${year}`)
|
||||
.then((r) => { if (!r.ok) throw new Error(); return r.json(); })
|
||||
.then((d: StatsData) => { setData(d); setLoading(false); })
|
||||
.catch(() => { setError('Fehler beim Laden der Statistik.'); setLoading(false); });
|
||||
.then((d: StatsData) => { if (!cancelled) { setData(d); setFetchError(null); } })
|
||||
.catch(() => { if (!cancelled) setFetchError({ year, kuppel }); });
|
||||
return () => { cancelled = true; };
|
||||
}, [kuppel, year]);
|
||||
|
||||
const { arten, matrix, monatTotal, artTotal, grandTotal, anzahlTotal } = useMemo(() => {
|
||||
@@ -111,7 +115,7 @@ export default function Statistik({ kuppel }: Props) {
|
||||
<th className={headCls}>Monat</th>
|
||||
<th className={headCls}>Führungen</th>
|
||||
{arten.map((art) => (
|
||||
<th key={art} className={headCls}>{artLabel(art as any) || art}</th>
|
||||
<th key={art} className={headCls}>{artLabel(art as ArtFuehrung)}</th>
|
||||
))}
|
||||
<th className={headCls}>Gesamt</th>
|
||||
</tr>
|
||||
|
||||
@@ -202,6 +202,23 @@
|
||||
section { padding: 1.2rem 1rem; }
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: #fff;
|
||||
border: 1px solid #d8e0f0;
|
||||
border-radius: 8px;
|
||||
color: #2e4e8a;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.back-btn:hover { background: #eef2fa; }
|
||||
|
||||
@media print {
|
||||
body { background: #fff; font-size: 12pt; }
|
||||
.page { max-width: none; padding: 0; }
|
||||
@@ -209,12 +226,15 @@
|
||||
section { border: 1px solid #ccc; break-inside: avoid; }
|
||||
nav.toc { break-after: page; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
.back-btn { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<a href="/" class="back-btn">← Zurück zum Logbuch</a>
|
||||
|
||||
<header>
|
||||
<div class="star">★</div>
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user