Modernize Destinataer detail page: tabbed UI with integrated timeline

Replaces the old multi-card layout (1368 lines) with a compact, modern
tabbed interface (Stammdaten | Nachweise | Zahlungen | Timeline | Dokumente | Notizen).
All information is now accessible from one page without excessive clicking.

- Add timeline events to destinataer_detail view (merged from timeline view logic)
- Compact profile header with avatar initials, status badges, key contact info
- Inline editing preserved with table-based layout for cleaner data display
- Tab state persisted in URL hash for bookmarkable deep links
- Dropdown menu for less-used actions (export, archive, delete)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-11 20:55:21 +00:00
parent 8e1db11f8d
commit c3c6755027
2 changed files with 811 additions and 1286 deletions

View File

@@ -316,6 +316,52 @@ def destinataer_detail(request, pk):
# Alle verfügbaren StiftungsKonten für das Select-Feld laden
stiftungskonten = StiftungsKonto.objects.all().order_by("kontoname")
# Timeline events (merged from destinataer_timeline view)
timeline_events = []
for u in destinataer.unterstuetzungen.select_related("konto", "ausgezahlt_von", "freigegeben_von").order_by("-faellig_am"):
timeline_events.append({
"datum": u.faellig_am,
"typ": "zahlung",
"icon": "fa-money-bill-wave",
"farbe": "success" if u.status == "ausgezahlt" else ("danger" if u.is_overdue() else "primary"),
"titel": f"Zahlung \u20ac{u.betrag}",
"beschreibung": u.beschreibung or u.get_status_display(),
"status": u.get_status_display(),
})
for n in destinataer.quartalseinreichungen.order_by("-jahr", "-quartal"):
datum = n.zahlung_faelligkeitsdatum or n.faelligkeitsdatum
if datum:
timeline_events.append({
"datum": datum,
"typ": "nachweis",
"icon": "fa-file-alt",
"farbe": "success" if n.status in ("geprueft", "auto_geprueft") else ("danger" if n.is_overdue() else "warning"),
"titel": f"Nachweis {n.jahr} Q{n.quartal}",
"beschreibung": n.get_status_display(),
"status": n.get_status_display(),
})
for e in destinataer.email_eingaenge.order_by("-eingangsdatum"):
timeline_events.append({
"datum": e.eingangsdatum.date() if hasattr(e.eingangsdatum, "date") else e.eingangsdatum,
"typ": "email",
"icon": "fa-envelope",
"farbe": "info",
"titel": e.betreff or "(kein Betreff)",
"beschreibung": e.absender_email,
"status": e.get_status_display(),
})
for n in destinataer.notizen_eintraege.order_by("-erstellt_am"):
timeline_events.append({
"datum": n.erstellt_am.date() if hasattr(n.erstellt_am, "date") else n.erstellt_am,
"typ": "notiz",
"icon": "fa-sticky-note",
"farbe": "secondary",
"titel": n.titel or "Notiz",
"beschreibung": (n.text[:100] + "\u2026") if n.text and len(n.text) > 100 else n.text,
"status": f"von {n.erstellt_von.get_full_name() or n.erstellt_von.username}" if n.erstellt_von else "",
})
timeline_events.sort(key=lambda e: e["datum"] if e["datum"] else date.min, reverse=True)
context = {
"destinataer": destinataer,
"verknuepfte_dokumente": verknuepfte_dokumente,
@@ -326,6 +372,7 @@ def destinataer_detail(request, pk):
"quarterly_confirmations": quarterly_confirmations,
"available_years": available_years,
"current_year": current_year,
"timeline_events": timeline_events,
}
return render(request, "stiftung/destinataer_detail.html", context)

File diff suppressed because it is too large Load Diff