feat: Tab „Führungen" für ausgefallene und verschobene Sonderführungen — Version 1.14.0

Fällt eine Sonderführung aus oder wird sie verschoben, gibt es dafür keinen
Logbuch-Eintrag. Der neue Tab listet die kommenden zugesagten Führungen aus
SoFue2 und bietet je Zeile „Ausgefallen" und „Verschoben auf …" — offen für
jeden angemeldeten BEO.

Absage setzt status=3 und lässt den Termin stehen. Beim Verschieben wandert
der bisherige wtermin in die bis dahin ungenutzte Spalte 'verlegt'; beide
Aktionen hängen eine datierte Zeile mit dem Kürzel an 'bemerkung' an, statt
sie zu ersetzen. Damit ist die Terminhistorie erstmals nachvollziehbar.

Die Anleitung wird nun aus ANLEITUNG.md erzeugt (scripts/build-anleitung.mjs,
Design in scripts/anleitung.template.html), automatisch vor jedem Build.
public/anleitung.html ist deshalb nicht mehr in git.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 18:03:43 +02:00
parent 1945f1f8e0
commit b3fbc391b1
14 changed files with 674 additions and 497 deletions
+14 -3
View File
@@ -7,6 +7,7 @@ import LogbuchForm from '@/components/LogbuchForm';
import LogbuchList from '@/components/LogbuchList';
import Statistik from '@/components/Statistik';
import Fahrkosten from '@/components/Fahrkosten';
import Fuehrungen from '@/components/Fuehrungen';
import packageJson from '@/package.json';
function datumDe(iso?: string): string {
@@ -36,7 +37,7 @@ interface Props {
export default function MainClient({ kuerzel, beoId, beoName, role }: Props) {
const [activeKuppel, setActiveKuppel] = useState<Kuppel>('West');
const [activeTab, setActiveTab] = useState<'eingabe' | 'liste' | 'statistik' | 'fahrkosten'>('eingabe');
const [activeTab, setActiveTab] = useState<'eingabe' | 'liste' | 'fuehrungen' | 'statistik' | 'fahrkosten'>('eingabe');
const [refreshKey, setRefreshKey] = useState(0);
const [editEntry, setEditEntry] = useState<LogbuchEintrag | null>(null);
const [backupState, setBackupState] = useState<'idle' | 'running' | 'ok' | 'error'>('idle');
@@ -140,7 +141,7 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) {
{/* Eingabe/Liste/Statistik-Tabs */}
<div className="flex gap-1 mb-3 border-b border-gray-200 print:hidden">
{(['eingabe', 'liste', 'statistik', 'fahrkosten'] as const)
{(['eingabe', 'liste', 'statistik', 'fahrkosten', 'fuehrungen'] as const)
.filter((tab) => tab !== 'fahrkosten' || role?.includes('admin') || role?.includes('master'))
.map((tab) => (
<button
@@ -155,7 +156,7 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) {
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
{tab === 'eingabe' ? 'Eingabe' : tab === 'liste' ? 'Liste' : tab === 'statistik' ? 'Statistik' : 'Fahrkosten'}
{tab === 'eingabe' ? 'Eingabe' : tab === 'liste' ? 'Liste' : tab === 'fuehrungen' ? 'Führungen' : tab === 'statistik' ? 'Statistik' : 'Fahrkosten'}
</button>
))}
</div>
@@ -264,6 +265,16 @@ export default function MainClient({ kuerzel, beoId, beoName, role }: Props) {
</div>
)}
{/* Führungen-Tab: kommende Sonderführungen absagen oder verschieben */}
{activeTab === 'fuehrungen' && (
<div className="border-2 border-gray-400 rounded-xl bg-white p-3">
<div className="mb-2">
<span className="text-sm font-semibold text-gray-900">Kommende Sonderführungen</span>
</div>
<Fuehrungen />
</div>
)}
{/* Fahrkosten-Tab */}
{activeTab === 'fahrkosten' && (role?.includes('admin') || role?.includes('master')) && (
<div className="border-2 border-gray-400 rounded-xl bg-white p-3 print:border-0 print:rounded-none print:p-0">