Phase 1: Sidebar-Navigation, Dashboard-Cockpit & HTMX-Integration

- New sidebar layout (6 sections: Dashboard, Personen, Land, Finanzen, Dokumente, System)
- Collapsible sidebar with localStorage persistence
- Top bar with user dropdown and breadcrumbs
- Dashboard cockpit with live KPI cards (Destinataere, Foerderungen, Zahlungen, Laendereien)
- Action items: overdue Nachweise, pending payments, upcoming events, new emails, expiring leases
- Quick actions panel and recent audit log
- HTMX (2.0.4) and Alpine.js (3.14.8) integration via CDN
- django-htmx middleware and CSRF token setup
- Fix IMAP_PORT empty string handling in settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-11 10:22:42 +00:00
parent 3ca2706e5d
commit bf47ba11c9
5 changed files with 945 additions and 1062 deletions

View File

@@ -1,5 +1,5 @@
# views/dashboard.py
# Phase 0: Vision 2026 Code-Refactoring
# Vision 2026 Phase 1: Dashboard Cockpit
import csv
import io
@@ -59,38 +59,86 @@ from stiftung.forms import (
@login_required
def home(request):
"""Home page for the Stiftungsverwaltung application"""
"""Vision 2026 Dashboard Cockpit"""
from stiftung.services.calendar_service import StiftungsKalenderService
# Get upcoming events for the calendar widget
calendar_service = StiftungsKalenderService()
# Get all events for the next 14 days
from datetime import timedelta
today = timezone.now().date()
current_quarter = (today.month - 1) // 3 + 1
current_year = today.year
# ── Calendar events ──
calendar_service = StiftungsKalenderService()
end_date = today + timedelta(days=14)
all_events = calendar_service.get_all_events(today, end_date)
# Filter for upcoming and overdue
upcoming_events = [e for e in all_events if not getattr(e, 'overdue', False)]
overdue_events = [e for e in all_events if getattr(e, 'overdue', False)]
# Get current month events for mini calendar
from calendar import monthrange
_, last_day = monthrange(today.year, today.month)
month_start = today.replace(day=1)
month_end = today.replace(day=last_day)
current_month_events = calendar_service.get_all_events(month_start, month_end)
# ── Stats ──
destinataer_count = Destinataer.objects.count()
paechter_count = Paechter.objects.count()
land_count = Land.objects.count()
# Active Foerderungen (not rejected/cancelled, current or future year)
foerderung_active = Foerderung.objects.filter(
jahr__gte=current_year,
status__in=['beantragt', 'genehmigt'],
).count()
# ── Overdue Nachweise (current quarter) ──
overdue_nachweise = VierteljahresNachweis.objects.filter(
quartal=current_quarter,
jahr=current_year,
status__in=['offen', 'teilweise', 'nachbesserung'],
).select_related('destinataer').order_by('jahr', 'quartal')[:10]
# ── Pending payments (not yet paid out) ──
pending_payments = DestinataerUnterstuetzung.objects.filter(
status__in=['geplant', 'faellig', 'in_bearbeitung'],
).select_related('destinataer').order_by('faellig_am')[:10]
pending_payment_total = DestinataerUnterstuetzung.objects.filter(
status__in=['geplant', 'faellig', 'in_bearbeitung'],
).aggregate(total=Coalesce(Sum('betrag'), Decimal('0')))['total']
# ── New emails ──
new_emails = DestinataerEmailEingang.objects.filter(
status='neu',
).order_by('-eingangsdatum')[:5]
new_email_count = DestinataerEmailEingang.objects.filter(status='neu').count()
# ── Expiring leases (next 90 days) ──
lease_cutoff = today + timedelta(days=90)
expiring_leases = LandVerpachtung.objects.filter(
pachtende__lte=lease_cutoff,
pachtende__gte=today,
).select_related('paechter', 'land').order_by('pachtende')[:5]
# ── Recent audit log ──
recent_audit = AuditLog.objects.order_by('-timestamp')[:5]
context = {
"title": "Stiftungsverwaltung",
"description": "Foundation Management System",
"upcoming_events": upcoming_events[:5], # Show only 5 upcoming events
"overdue_events": overdue_events[:3], # Show only 3 overdue events
"current_month_events": current_month_events,
"title": "Dashboard",
# Stats
"destinataer_count": destinataer_count,
"paechter_count": paechter_count,
"land_count": land_count,
"foerderung_active": foerderung_active,
# Calendar
"upcoming_events": upcoming_events[:5],
"overdue_events": overdue_events[:3],
"today": today,
# Action items
"overdue_nachweise": overdue_nachweise,
"pending_payments": pending_payments,
"pending_payment_total": pending_payment_total,
"new_emails": new_emails,
"new_email_count": new_email_count,
"expiring_leases": expiring_leases,
"recent_audit": recent_audit,
"current_quarter": current_quarter,
"current_year": current_year,
}
return render(request, "stiftung/home.html", context)
@@ -106,12 +154,7 @@ def health_check(request):
)
## Removed duplicate paperless_ping referencing non-existent PAPERLESS_URL
# CSV Import Views
@api_view(["GET"])
def health(_request):
return Response({"status": "ok"})