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:
@@ -35,6 +35,7 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.humanize",
|
"django.contrib.humanize",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"rest_framework.authtoken",
|
"rest_framework.authtoken",
|
||||||
|
"django_htmx",
|
||||||
"django_otp",
|
"django_otp",
|
||||||
"django_otp.plugins.otp_totp",
|
"django_otp.plugins.otp_totp",
|
||||||
"django_otp.plugins.otp_static",
|
"django_otp.plugins.otp_static",
|
||||||
@@ -48,6 +49,7 @@ MIDDLEWARE = [
|
|||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django_htmx.middleware.HtmxMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django_otp.middleware.OTPMiddleware",
|
"django_otp.middleware.OTPMiddleware",
|
||||||
@@ -131,7 +133,7 @@ CELERY_BEAT_SCHEDULE = {
|
|||||||
# IMAP-Konfiguration für E-Mail-Eingang (Destinatäre)
|
# IMAP-Konfiguration für E-Mail-Eingang (Destinatäre)
|
||||||
# Pflichtfelder: IMAP_HOST, IMAP_USER, IMAP_PASSWORD
|
# Pflichtfelder: IMAP_HOST, IMAP_USER, IMAP_PASSWORD
|
||||||
IMAP_HOST = os.getenv("IMAP_HOST", "")
|
IMAP_HOST = os.getenv("IMAP_HOST", "")
|
||||||
IMAP_PORT = int(os.getenv("IMAP_PORT", "993"))
|
IMAP_PORT = int(os.getenv("IMAP_PORT") or "993")
|
||||||
IMAP_USER = os.getenv("IMAP_USER", "paperless@vhtv-stiftung.de")
|
IMAP_USER = os.getenv("IMAP_USER", "paperless@vhtv-stiftung.de")
|
||||||
IMAP_PASSWORD = os.getenv("IMAP_PASSWORD", "")
|
IMAP_PASSWORD = os.getenv("IMAP_PASSWORD", "")
|
||||||
IMAP_FOLDER = os.getenv("IMAP_FOLDER", "INBOX")
|
IMAP_FOLDER = os.getenv("IMAP_FOLDER", "INBOX")
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ gunicorn==22.0.0
|
|||||||
python-dateutil==2.9.0
|
python-dateutil==2.9.0
|
||||||
markdown==3.6
|
markdown==3.6
|
||||||
django-otp==1.2.4
|
django-otp==1.2.4
|
||||||
|
django-htmx==1.19.0
|
||||||
qrcode[pil]==7.4.2
|
qrcode[pil]==7.4.2
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# views/dashboard.py
|
# views/dashboard.py
|
||||||
# Phase 0: Vision 2026 – Code-Refactoring
|
# Vision 2026 – Phase 1: Dashboard Cockpit
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
@@ -59,38 +59,86 @@ from stiftung.forms import (
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def home(request):
|
def home(request):
|
||||||
"""Home page for the Stiftungsverwaltung application"""
|
"""Vision 2026 Dashboard Cockpit"""
|
||||||
from stiftung.services.calendar_service import StiftungsKalenderService
|
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()
|
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)
|
end_date = today + timedelta(days=14)
|
||||||
all_events = calendar_service.get_all_events(today, end_date)
|
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)]
|
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)]
|
overdue_events = [e for e in all_events if getattr(e, 'overdue', False)]
|
||||||
|
|
||||||
# Get current month events for mini calendar
|
# ── Stats ──
|
||||||
from calendar import monthrange
|
destinataer_count = Destinataer.objects.count()
|
||||||
_, last_day = monthrange(today.year, today.month)
|
paechter_count = Paechter.objects.count()
|
||||||
month_start = today.replace(day=1)
|
land_count = Land.objects.count()
|
||||||
month_end = today.replace(day=last_day)
|
|
||||||
current_month_events = calendar_service.get_all_events(month_start, month_end)
|
# 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 = {
|
context = {
|
||||||
"title": "Stiftungsverwaltung",
|
"title": "Dashboard",
|
||||||
"description": "Foundation Management System",
|
# Stats
|
||||||
"upcoming_events": upcoming_events[:5], # Show only 5 upcoming events
|
"destinataer_count": destinataer_count,
|
||||||
"overdue_events": overdue_events[:3], # Show only 3 overdue events
|
"paechter_count": paechter_count,
|
||||||
"current_month_events": current_month_events,
|
"land_count": land_count,
|
||||||
|
"foerderung_active": foerderung_active,
|
||||||
|
# Calendar
|
||||||
|
"upcoming_events": upcoming_events[:5],
|
||||||
|
"overdue_events": overdue_events[:3],
|
||||||
"today": today,
|
"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)
|
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
|
# CSV Import Views
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def health(_request):
|
def health(_request):
|
||||||
return Response({"status": "ok"})
|
return Response({"status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,455 +1,301 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}{{ title }} - Foundation Management System{% endblock %}
|
{% block title %}Dashboard - Stiftungsverwaltung{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<!-- KPI Cards Row -->
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-left-primary">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<div class="stat-icon me-3" style="background: var(--racing-green);">
|
||||||
|
<i class="fas fa-users"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ destinataer_count }}</div>
|
||||||
|
<div class="stat-label">Destinataere</div>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'stiftung:destinataer_list' %}" class="stretched-link"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-left-success">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<div class="stat-icon me-3" style="background: var(--racing-green-light);">
|
||||||
|
<i class="fas fa-gift"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ foerderung_active }}</div>
|
||||||
|
<div class="stat-label">Aktive Foerderungen</div>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'stiftung:foerderung_list' %}" class="stretched-link"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-left-warning">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<div class="stat-icon me-3" style="background: var(--orange-accent);">
|
||||||
|
<i class="fas fa-euro-sign"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ pending_payment_total|floatformat:0 }}</div>
|
||||||
|
<div class="stat-label">Offene Zahlungen €</div>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'stiftung:unterstuetzungen_all' %}" class="stretched-link"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-3 col-md-6">
|
||||||
|
<div class="card stat-card border-left-info">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<div class="stat-icon me-3" style="background: var(--grey-medium);">
|
||||||
|
<i class="fas fa-map"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ land_count }}</div>
|
||||||
|
<div class="stat-label">Laendereien</div>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'stiftung:land_list' %}" class="stretched-link"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<!-- Main Action Row -->
|
||||||
<div class="col-12">
|
<div class="row g-3">
|
||||||
<div class="card shadow">
|
<!-- Left Column: Action Items -->
|
||||||
<div class="card-body text-center py-5">
|
<div class="col-lg-8">
|
||||||
<!-- Logo Placeholder -->
|
|
||||||
<div class="mb-4">
|
{% if overdue_events %}
|
||||||
<div class="logo-placeholder mx-auto mb-3" style="width: 150px; height: 150px; border: 3px dashed #dee2e6; border-radius: 50%; display: flex; align-items: center; justify-content: center; background-color: #f8f9fa;">
|
<!-- Overdue Events Alert -->
|
||||||
<div class="text-center text-muted">
|
<div class="card border-left-danger mb-3">
|
||||||
<i class="fas fa-image fa-2x mb-2"></i>
|
<div class="card-header bg-danger text-white">
|
||||||
<div style="font-size: 0.8rem;">Logo hier<br>einfügen</div>
|
<i class="fas fa-exclamation-triangle me-2"></i>Ueberfaellige Termine ({{ overdue_events|length }})
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="action-list">
|
||||||
|
{% for event in overdue_events %}
|
||||||
|
<div class="action-item">
|
||||||
|
<div class="action-icon bg-danger text-white">
|
||||||
|
<i class="{{ event.icon }}"></i>
|
||||||
</div>
|
</div>
|
||||||
<!-- Alternative: Replace above div with actual logo when available -->
|
<div class="action-text">
|
||||||
<!-- <img src="{% load static %}{% static 'images/stiftung-logo.png' %}" alt="Stiftung Logo" class="img-fluid mb-3" style="max-height: 150px;"> -->
|
<div class="action-title">{{ event.title }}</div>
|
||||||
</div>
|
<div class="action-desc">{{ event.description }}</div>
|
||||||
|
</div>
|
||||||
<h1 class="display-4 mb-4">
|
<span class="badge bg-danger">{{ event.date }}</span>
|
||||||
<i class="fas fa-landmark text-primary me-3"></i>van Hees-Theyssen-Vogel'sche Stiftung
|
|
||||||
</h1>
|
|
||||||
<p class="lead text-muted mb-5">Stiftungsverwaltung - Modern Foundation Management System</p>
|
|
||||||
<div class="row justify-content-center mb-4">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="card border-0 bg-light">
|
|
||||||
<div class="card-body text-center py-3">
|
|
||||||
<p class="mb-1"><i class="fas fa-map-marker-alt text-primary me-2"></i>Raesfelder Str. 3, 46499 Hamminkeln</p>
|
|
||||||
<p class="mb-0"><i class="fas fa-phone text-primary me-2"></i>+49 (0) 2852 12345</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-users fa-3x text-primary mb-3"></i>
|
|
||||||
<h5 class="card-title">👥 Destinatäre</h5>
|
|
||||||
<p class="card-text">Verwalten Sie Stiftungsmitglieder, Familienzweige und Kontaktdaten zentral und übersichtlich.</p>
|
|
||||||
<a href="{% url 'stiftung:destinataer_list' %}" class="btn btn-outline-primary btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-hand-holding-usd fa-3x text-success mb-3"></i>
|
|
||||||
<h5 class="card-title">💰 Unterstützungsverwaltung</h5>
|
|
||||||
<p class="card-text">Erfassen und verfolgen Sie Unterstützungen, Beträge und Verwendungsnachweise systematisch.</p>
|
|
||||||
<a href="{% url 'stiftung:unterstuetzungen_all' %}" class="btn btn-outline-success btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-file-alt fa-3x text-info mb-3"></i>
|
|
||||||
<h5 class="card-title">📄 Dokumentenverwaltung</h5>
|
|
||||||
<p class="card-text">Stiftungsdokumente und Verträge</p>
|
|
||||||
<a href="{% url 'stiftung:dokument_management' %}" class="btn btn-outline-info btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Calendar and Events Section -->
|
|
||||||
<div class="row g-4 mb-5">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-header bg-primary text-white">
|
|
||||||
<h5 class="mb-0">
|
|
||||||
<i class="fas fa-calendar-alt me-2"></i>Anstehende Termine & Ereignisse
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% if overdue_events %}
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
<h6><i class="fas fa-exclamation-triangle me-2"></i>Überfällige Termine ({{ overdue_events|length }})</h6>
|
|
||||||
{% for event in overdue_events %}
|
|
||||||
<div class="d-flex justify-content-between align-items-center border-bottom py-2">
|
|
||||||
<div>
|
|
||||||
<i class="{{ event.icon }} me-2"></i>
|
|
||||||
<strong>{{ event.title }}</strong>
|
|
||||||
<small class="text-muted d-block">{{ event.description }}</small>
|
|
||||||
</div>
|
|
||||||
<span class="badge bg-danger">{{ event.date }}</span>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if upcoming_events %}
|
|
||||||
<h6 class="text-muted mb-3">Nächste {{ upcoming_events|length }} Termine</h6>
|
|
||||||
{% for event in upcoming_events %}
|
|
||||||
<div class="d-flex justify-content-between align-items-center border-bottom py-2">
|
|
||||||
<div class="flex-grow-1">
|
|
||||||
<i class="{{ event.icon }} me-2 text-{{ event.color }}"></i>
|
|
||||||
<strong>{{ event.title }}</strong>
|
|
||||||
{% if event.description %}
|
|
||||||
<small class="text-muted d-block">{{ event.description }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="text-end">
|
|
||||||
<span class="badge bg-{{ event.color }}">{{ event.date }}</span>
|
|
||||||
{% if event.time %}
|
|
||||||
<small class="d-block text-muted">{{ event.time }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% empty %}
|
|
||||||
<div class="text-center py-4 text-muted">
|
|
||||||
<i class="fas fa-calendar-check fa-3x mb-3"></i>
|
|
||||||
<p>Keine anstehenden Termine in den nächsten 14 Tagen.</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<a href="{% url 'stiftung:kalender' %}" class="btn btn-outline-primary btn-sm">
|
|
||||||
<i class="fas fa-calendar me-1"></i>Vollständigen Kalender anzeigen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-4">
|
|
||||||
<!-- Mini Calendar -->
|
|
||||||
<div class="card border-0 shadow-sm mb-3">
|
|
||||||
<div class="card-header bg-info text-white">
|
|
||||||
<h6 class="mb-0">
|
|
||||||
<i class="fas fa-calendar me-2"></i>{{ today|date:"F Y" }}
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-2">
|
|
||||||
<div class="mini-calendar">
|
|
||||||
<!-- Calendar will be generated by JavaScript -->
|
|
||||||
<div id="mini-calendar" data-events="{{ current_month_events|length }}">
|
|
||||||
<!-- Mini calendar grid will be inserted here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
|
||||||
<div class="card border-0 shadow-sm">
|
|
||||||
<div class="card-header bg-success text-white">
|
|
||||||
<h6 class="mb-0">
|
|
||||||
<i class="fas fa-plus me-2"></i>Schnellzugriff
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="d-grid gap-2">
|
|
||||||
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-outline-primary btn-sm">
|
|
||||||
<i class="fas fa-calendar-plus me-1"></i>Termin hinzufügen
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:unterstuetzung_create' %}" class="btn btn-outline-success btn-sm">
|
|
||||||
<i class="fas fa-euro-sign me-1"></i>Zahlung planen
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:destinataer_create' %}" class="btn btn-outline-info btn-sm">
|
|
||||||
<i class="fas fa-user-plus me-1"></i>Destinatär anlegen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-chart-bar fa-3x text-warning mb-3"></i>
|
|
||||||
<h5 class="card-title">📊 Berichte & Auswertungen</h5>
|
|
||||||
<p class="card-text">Generieren Sie detaillierte Berichte und Auswertungen für Ihre Stiftungsarbeit.</p>
|
|
||||||
<a href="{% url 'stiftung:bericht_list' %}" class="btn btn-outline-warning btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-map fa-3x text-danger mb-3"></i>
|
|
||||||
<h5 class="card-title">🗺️ Ländereiverwaltung</h5>
|
|
||||||
<p class="card-text">Verwalten Sie Grundstücke, Flächen und Verpachtungen professionell.</p>
|
|
||||||
<a href="{% url 'stiftung:land_list' %}" class="btn btn-outline-danger btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-handshake fa-3x text-secondary mb-3"></i>
|
|
||||||
<h5 class="card-title">🤝 Verpachtungsverwaltung</h5>
|
|
||||||
<p class="card-text">Organisieren Sie Pachtverträge und deren Verwaltung effizient.</p>
|
|
||||||
<a href="{% url 'stiftung:verpachtung_list' %}" class="btn btn-outline-secondary btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-4 mb-5">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-link fa-3x text-purple mb-3"></i>
|
|
||||||
<h5 class="card-title">🔗 Dokumentenverknüpfung</h5>
|
|
||||||
<p class="card-text">Verknüpfen Sie Paperless-Dokumente direkt mit Destinatären, Ländereien und Verpachtungen.</p>
|
|
||||||
<a href="{% url 'stiftung:dokument_management' %}" class="btn btn-outline-purple btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card h-100 border-0 shadow-sm">
|
|
||||||
<div class="card-body text-center p-4">
|
|
||||||
<i class="fas fa-database fa-3x text-dark mb-3"></i>
|
|
||||||
<h5 class="card-title">🗃️ Dokumentenarchiv</h5>
|
|
||||||
<p class="card-text">Zentraler Zugriff auf alle verknüpften Dokumente und deren Metadaten.</p>
|
|
||||||
<a href="{% url 'stiftung:dokument_list' %}" class="btn btn-outline-dark btn-sm mt-2">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i>Öffnen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-center gap-3 flex-wrap">
|
|
||||||
<a href="{% url 'stiftung:destinataer_list' %}" class="btn btn-primary btn-lg">
|
|
||||||
<i class="fas fa-users me-2"></i>Destinatäre
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:land_list' %}" class="btn btn-success btn-lg">
|
|
||||||
<i class="fas fa-map me-2"></i>Ländereien
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:paechter_list' %}" class="btn btn-info btn-lg">
|
|
||||||
<i class="fas fa-user-tie me-2"></i>Pächter
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:land_list' %}" class="btn btn-secondary btn-lg">
|
|
||||||
<i class="fas fa-handshake me-2"></i>Verpachtungen
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:foerderung_list' %}" class="btn btn-warning btn-lg">
|
|
||||||
<i class="fas fa-gift me-2"></i>Förderungen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-center gap-3 flex-wrap mt-3">
|
|
||||||
<a href="{% url 'stiftung:dokument_management' %}" class="btn btn-info btn-lg">
|
|
||||||
<i class="fas fa-file-alt me-2"></i>Paperless Integration
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:dokument_management' %}" class="btn btn-purple btn-lg">
|
|
||||||
<i class="fas fa-link me-2"></i>Dokumente verknüpfen
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'stiftung:dokument_list' %}" class="btn btn-dark btn-lg">
|
|
||||||
<i class="fas fa-database me-2"></i>Dokumentenarchiv
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="text-center mt-4">
|
{% endif %}
|
||||||
<div class="alert alert-success d-inline-block">
|
|
||||||
<i class="fas fa-check-circle me-2"></i>
|
{% if overdue_nachweise %}
|
||||||
<span class="fw-bold">System läuft erfolgreich</span>
|
<!-- Overdue Nachweise -->
|
||||||
<br>
|
<div class="card border-left-warning mb-3">
|
||||||
<small class="text-muted">Entwickelt mit Django & Docker</small>
|
<div class="card-header">
|
||||||
|
<i class="fas fa-file-alt me-2 text-warning"></i>Ausstehende Nachweise Q{{ current_quarter }}/{{ current_year }}
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="action-list">
|
||||||
|
{% for nachweis in overdue_nachweise %}
|
||||||
|
<a class="action-item" href="{% url 'stiftung:destinataer_detail' nachweis.destinataer.pk %}">
|
||||||
|
<div class="action-icon" style="background: rgba(253,126,20,0.15); color: var(--orange-accent);">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
</div>
|
||||||
|
<div class="action-text">
|
||||||
|
<div class="action-title">{{ nachweis.destinataer.vorname }} {{ nachweis.destinataer.nachname }}</div>
|
||||||
|
<div class="action-desc">{{ nachweis.get_status_display }}</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-warning">{{ nachweis.get_status_display }}</span>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if pending_payments %}
|
||||||
|
<!-- Pending Payments -->
|
||||||
|
<div class="card border-left-primary mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-hand-holding-usd me-2 text-primary"></i>Offene Zahlungen
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="action-list">
|
||||||
|
{% for payment in pending_payments %}
|
||||||
|
<a class="action-item" href="{% url 'stiftung:destinataer_detail' payment.destinataer.pk %}">
|
||||||
|
<div class="action-icon" style="background: rgba(0,66,37,0.1); color: var(--racing-green);">
|
||||||
|
<i class="fas fa-euro-sign"></i>
|
||||||
|
</div>
|
||||||
|
<div class="action-text">
|
||||||
|
<div class="action-title">{{ payment.destinataer.vorname }} {{ payment.destinataer.nachname }}</div>
|
||||||
|
<div class="action-desc">{{ payment.beschreibung|default:"Unterstuetzung" }} · {{ payment.betrag|floatformat:2 }} €</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-{% if payment.faellig_am < today %}danger{% else %}primary{% endif %}">{{ payment.faellig_am|date:"d.m.Y" }}</span>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Upcoming Events -->
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-calendar-alt me-2 text-primary"></i>Anstehende Termine
|
||||||
|
<a href="{% url 'stiftung:kalender' %}" class="float-end text-decoration-none" style="font-size: 0.75rem;">Alle anzeigen →</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="action-list">
|
||||||
|
{% for event in upcoming_events %}
|
||||||
|
<div class="action-item">
|
||||||
|
<div class="action-icon" style="background: rgba(0,66,37,0.1); color: var(--racing-green);">
|
||||||
|
<i class="{{ event.icon }}"></i>
|
||||||
|
</div>
|
||||||
|
<div class="action-text">
|
||||||
|
<div class="action-title">{{ event.title }}</div>
|
||||||
|
{% if event.description %}
|
||||||
|
<div class="action-desc">{{ event.description }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-{{ event.color }}">{{ event.date }}</span>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="text-center py-4 text-muted">
|
||||||
|
<i class="fas fa-calendar-check fa-2x mb-2 d-block"></i>
|
||||||
|
Keine Termine in den naechsten 14 Tagen.
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
<!-- Right Column: Quick Actions & Info -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
|
||||||
{% block extra_css %}
|
<!-- Quick Actions -->
|
||||||
<style>
|
<div class="card mb-3">
|
||||||
.mini-calendar {
|
<div class="card-header bg-success text-white">
|
||||||
font-size: 0.8rem;
|
<i class="fas fa-bolt me-2"></i>Schnellzugriff
|
||||||
}
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
.mini-calendar .calendar-day {
|
<div class="d-grid gap-2">
|
||||||
width: 32px;
|
<a href="{% url 'stiftung:destinataer_create' %}" class="btn btn-outline-primary btn-sm">
|
||||||
height: 32px;
|
<i class="fas fa-user-plus me-1"></i>Neuer Destinataer
|
||||||
display: inline-flex;
|
</a>
|
||||||
align-items: center;
|
<a href="{% url 'stiftung:unterstuetzung_create' %}" class="btn btn-outline-primary btn-sm">
|
||||||
justify-content: center;
|
<i class="fas fa-euro-sign me-1"></i>Neue Zahlung
|
||||||
margin: 1px;
|
</a>
|
||||||
border-radius: 4px;
|
<a href="{% url 'stiftung:kalender_create' %}" class="btn btn-outline-primary btn-sm">
|
||||||
cursor: pointer;
|
<i class="fas fa-calendar-plus me-1"></i>Neuer Termin
|
||||||
}
|
</a>
|
||||||
|
<a href="{% url 'stiftung:foerderung_create' %}" class="btn btn-outline-primary btn-sm">
|
||||||
.mini-calendar .calendar-day.today {
|
<i class="fas fa-gift me-1"></i>Neue Foerderung
|
||||||
background-color: var(--bs-primary);
|
</a>
|
||||||
color: white;
|
</div>
|
||||||
font-weight: bold;
|
</div>
|
||||||
}
|
</div>
|
||||||
|
|
||||||
.mini-calendar .calendar-day.has-events {
|
|
||||||
background-color: var(--bs-success);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-calendar .calendar-day:hover {
|
|
||||||
background-color: var(--bs-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-calendar .calendar-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-calendar .calendar-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
gap: 1px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mini-calendar .calendar-weekday {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--bs-secondary);
|
|
||||||
padding: 4px;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
{% if new_emails %}
|
||||||
<script>
|
<!-- New Emails -->
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
<div class="card mb-3">
|
||||||
// Generate mini calendar for current month
|
<div class="card-header">
|
||||||
const today = new Date();
|
<i class="fas fa-envelope me-2 text-warning"></i>Neue E-Mails
|
||||||
const currentMonth = today.getMonth();
|
<span class="badge bg-warning float-end">{{ new_email_count }}</span>
|
||||||
const currentYear = today.getFullYear();
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
function generateMiniCalendar(year, month) {
|
<div class="action-list">
|
||||||
const firstDay = new Date(year, month, 1);
|
{% for email in new_emails %}
|
||||||
const lastDay = new Date(year, month + 1, 0);
|
<div class="action-item">
|
||||||
const startDate = new Date(firstDay);
|
<div class="action-icon" style="background: rgba(253,126,20,0.15); color: var(--orange-accent);">
|
||||||
startDate.setDate(startDate.getDate() - firstDay.getDay()); // Start from Sunday
|
<i class="fas fa-envelope"></i>
|
||||||
|
</div>
|
||||||
const calendarEl = document.getElementById('mini-calendar');
|
<div class="action-text">
|
||||||
|
<div class="action-title">{{ email.absender_name|default:email.absender_email|truncatechars:30 }}</div>
|
||||||
let html = '<div class="calendar-grid">';
|
<div class="action-desc">{{ email.betreff|truncatechars:40 }}</div>
|
||||||
|
</div>
|
||||||
// Weekday headers
|
</div>
|
||||||
const weekdays = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
|
{% endfor %}
|
||||||
weekdays.forEach(day => {
|
</div>
|
||||||
html += `<div class="calendar-weekday">${day}</div>`;
|
</div>
|
||||||
});
|
</div>
|
||||||
|
{% endif %}
|
||||||
// Generate calendar days
|
|
||||||
const currentDate = new Date(startDate);
|
|
||||||
for (let i = 0; i < 42; i++) { // 6 weeks
|
|
||||||
const isCurrentMonth = currentDate.getMonth() === month;
|
|
||||||
const isToday = currentDate.toDateString() === today.toDateString();
|
|
||||||
|
|
||||||
let classes = ['calendar-day'];
|
|
||||||
if (isToday) classes.push('today');
|
|
||||||
if (!isCurrentMonth) classes.push('text-muted');
|
|
||||||
|
|
||||||
html += `<div class="${classes.join(' ')}">${currentDate.getDate()}</div>`;
|
|
||||||
currentDate.setDate(currentDate.getDate() + 1);
|
|
||||||
|
|
||||||
if (currentDate > lastDay && currentDate.getDay() === 0) {
|
|
||||||
break; // Stop at end of month on Sunday
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
calendarEl.innerHTML = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the current month calendar
|
|
||||||
generateMiniCalendar(currentYear, currentMonth);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
{% if expiring_leases %}
|
||||||
/* Mini Calendar Styles */
|
<!-- Expiring Leases -->
|
||||||
.mini-calendar .calendar-day {
|
<div class="card mb-3">
|
||||||
width: 32px;
|
<div class="card-header">
|
||||||
height: 32px;
|
<i class="fas fa-handshake me-2 text-info"></i>Auslaufende Pachtvertraege
|
||||||
display: flex;
|
</div>
|
||||||
align-items: center;
|
<div class="card-body p-0">
|
||||||
justify-content: center;
|
<div class="action-list">
|
||||||
font-size: 0.8rem;
|
{% for lease in expiring_leases %}
|
||||||
border-radius: 3px;
|
<a class="action-item" href="{% url 'stiftung:verpachtung_detail' lease.pk %}">
|
||||||
cursor: pointer;
|
<div class="action-icon" style="background: rgba(108,117,125,0.15); color: var(--grey-medium);">
|
||||||
transition: background-color 0.2s;
|
<i class="fas fa-file-contract"></i>
|
||||||
}
|
</div>
|
||||||
|
<div class="action-text">
|
||||||
|
<div class="action-title">{{ lease.paechter.vorname }} {{ lease.paechter.nachname }}</div>
|
||||||
|
<div class="action-desc">{{ lease.land.bezeichnung|truncatechars:30 }}</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-info">{{ lease.pachtende|date:"d.m.Y" }}</span>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
.mini-calendar .calendar-day:hover {
|
<!-- Stats Overview -->
|
||||||
background-color: var(--bs-primary-bg-subtle);
|
<div class="card mb-3">
|
||||||
}
|
<div class="card-header">
|
||||||
|
<i class="fas fa-chart-pie me-2 text-primary"></i>Uebersicht
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span class="text-muted" style="font-size: 0.8rem;">Destinataere</span>
|
||||||
|
<strong>{{ destinataer_count }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span class="text-muted" style="font-size: 0.8rem;">Paechter</span>
|
||||||
|
<strong>{{ paechter_count }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-2">
|
||||||
|
<span class="text-muted" style="font-size: 0.8rem;">Laendereien</span>
|
||||||
|
<strong>{{ land_count }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span class="text-muted" style="font-size: 0.8rem;">Aktive Foerderungen</span>
|
||||||
|
<strong>{{ foerderung_active }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
.mini-calendar .calendar-day.today {
|
{% if recent_audit %}
|
||||||
background-color: var(--bs-primary);
|
<!-- Recent Activity -->
|
||||||
color: white;
|
<div class="card">
|
||||||
font-weight: bold;
|
<div class="card-header">
|
||||||
}
|
<i class="fas fa-history me-2"></i>Letzte Aktivitaeten
|
||||||
|
</div>
|
||||||
.mini-calendar .calendar-header {
|
<div class="card-body p-0">
|
||||||
font-weight: 600;
|
<div class="action-list">
|
||||||
color: var(--bs-secondary);
|
{% for entry in recent_audit %}
|
||||||
font-size: 0.75rem;
|
<div class="action-item" style="padding: 0.4rem 0.75rem;">
|
||||||
}
|
<div class="action-text">
|
||||||
|
<div class="action-title">{{ entry.get_action_display }} - {{ entry.get_entity_type_display }}</div>
|
||||||
/* Calendar event indicators */
|
<div class="action-desc">{{ entry.username }} · {{ entry.timestamp|timesince }} her</div>
|
||||||
.calendar-events-indicator {
|
</div>
|
||||||
position: absolute;
|
</div>
|
||||||
bottom: 2px;
|
{% endfor %}
|
||||||
right: 2px;
|
</div>
|
||||||
width: 6px;
|
</div>
|
||||||
height: 6px;
|
</div>
|
||||||
background-color: var(--bs-warning);
|
{% endif %}
|
||||||
border-radius: 50%;
|
</div>
|
||||||
}
|
</div>
|
||||||
|
{% endblock %}
|
||||||
/* Overdue events styling */
|
|
||||||
.alert-danger .border-bottom:last-child {
|
|
||||||
border-bottom: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calendar card hover effects */
|
|
||||||
.card.border-left-primary { border-left-color: var(--bs-primary) !important; }
|
|
||||||
.card.border-left-success { border-left-color: var(--bs-success) !important; }
|
|
||||||
.card.border-left-warning { border-left-color: var(--bs-warning) !important; }
|
|
||||||
.card.border-left-danger { border-left-color: var(--bs-danger) !important; }
|
|
||||||
.card.border-left-info { border-left-color: var(--bs-info) !important; }
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
Reference in New Issue
Block a user