snapshot: preserve bilingual core UI and gettext workflow state
This commit is contained in:
@@ -6,7 +6,7 @@ ENV PYTHONUNBUFFERED=1
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends build-essential netcat-openbsd \
|
||||
&& apt-get install -y --no-install-recommends build-essential netcat-openbsd gettext \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd -g 1000 app && useradd -u 1000 -g app -m app
|
||||
|
||||
@@ -38,6 +38,7 @@ INSTALLED_APPS = [
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
@@ -83,7 +84,12 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = 'de-de'
|
||||
LANGUAGE_CODE = os.getenv('DJANGO_LANGUAGE_CODE', 'de')
|
||||
LANGUAGES = [
|
||||
('de', 'Deutsch'),
|
||||
('en', 'English'),
|
||||
]
|
||||
LOCALE_PATHS = [BASE_DIR / 'locale']
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
path('accounts/', include('django.contrib.auth.urls')),
|
||||
path('', include('workflows.urls')),
|
||||
]
|
||||
|
||||
BIN
backend/locale/en/LC_MESSAGES/django.mo
Normal file
BIN
backend/locale/en/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
403
backend/locale/en/LC_MESSAGES/django.po
Normal file
403
backend/locale/en/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,403 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: tubco-portal\n"
|
||||
"POT-Creation-Date: 2026-03-24 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-03-24 00:00+0000\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "%(count)s Auswahl(en) konnten nicht verarbeitet werden."
|
||||
msgstr "%(count)s selection(s) could not be processed."
|
||||
|
||||
msgid "%(count)s Eintrag/Einträge gelöscht."
|
||||
msgstr "%(count)s entry/entries deleted."
|
||||
|
||||
msgid "%(count)s Einträge sichtbar"
|
||||
msgstr "%(count)s entries visible"
|
||||
|
||||
msgid "14 Tage"
|
||||
msgstr "14 days"
|
||||
|
||||
msgid "Abmelden"
|
||||
msgstr "Log out"
|
||||
|
||||
msgid "Abschluss"
|
||||
msgstr "Finish"
|
||||
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
msgid "Admin Apps"
|
||||
msgstr "Admin Apps"
|
||||
|
||||
msgid "Aktion"
|
||||
msgstr "Action"
|
||||
|
||||
msgid "Aktivität 14 Tage"
|
||||
msgstr "Activity 14 Days"
|
||||
|
||||
msgid "Aktivitätsverlauf"
|
||||
msgstr "Activity Timeline"
|
||||
|
||||
msgid "Alle Vorgänge, durchsuchbar und mit Dokumenten verknüpft."
|
||||
msgstr "All requests, searchable and linked with documents."
|
||||
|
||||
msgid "Alle erfassten Onboarding-Vorgänge im aktuellen System."
|
||||
msgstr "All onboarding requests captured in the current system."
|
||||
|
||||
msgid "Anfrage gespeichert"
|
||||
msgstr "Request saved"
|
||||
|
||||
msgid "Anfragen Dashboard"
|
||||
msgstr "Requests Dashboard"
|
||||
|
||||
msgid "Anmelden"
|
||||
msgstr "Sign in"
|
||||
|
||||
msgid "Anmeldung"
|
||||
msgstr "Sign in"
|
||||
|
||||
msgid "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen."
|
||||
msgstr "Login failed. Please check your credentials."
|
||||
|
||||
msgid "Apps"
|
||||
msgstr "Apps"
|
||||
|
||||
msgid "Austritte und Rückgaben in derselben Prozessübersicht."
|
||||
msgstr "Departures and returns in the same process overview."
|
||||
|
||||
msgid "Auswahl löschen"
|
||||
msgstr "Delete selection"
|
||||
|
||||
msgid "Beschäftigung und Termine"
|
||||
msgstr "Employment and dates"
|
||||
|
||||
msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an."
|
||||
msgstr "Please sign in with your user account."
|
||||
|
||||
msgid "Bitte prüfen Sie die markierten Felder. Ungültige Eingaben wurden erkannt."
|
||||
msgstr "Please check the highlighted fields. Invalid input was detected."
|
||||
|
||||
msgid "Checklistenpunkte für das Einweisungsprotokoll konfigurieren."
|
||||
msgstr "Configure checklist items for the introduction protocol."
|
||||
|
||||
msgid "Dashboard öffnen"
|
||||
msgstr "Open dashboard"
|
||||
|
||||
msgid "Datensätze können direkt in der Tabelle gefiltert, geöffnet, geprüft oder gelöscht werden."
|
||||
msgstr "Records can be filtered, opened, checked, or deleted directly in the table."
|
||||
|
||||
msgid "Die letzten 14 Tage in einer kompakten Ansicht über alle Onboarding- und Offboarding-Vorgänge."
|
||||
msgstr "The last 14 days in a compact view across all onboarding and offboarding requests."
|
||||
|
||||
msgid "Django Admin"
|
||||
msgstr "Django Admin"
|
||||
|
||||
msgid "Dokument"
|
||||
msgstr "Document"
|
||||
|
||||
msgid "Dokumentation, Architektur und Runbook."
|
||||
msgstr "Documentation, architecture, and runbook."
|
||||
|
||||
msgid "Dokumente, Status und Einweisungsaktionen in einer verdichteten Arbeitsansicht."
|
||||
msgstr "Documents, status, and introduction actions in a condensed work view."
|
||||
|
||||
msgid "E-Mail"
|
||||
msgstr "Email"
|
||||
|
||||
msgid "E-Mail Routing"
|
||||
msgstr "Email routing"
|
||||
|
||||
msgid "E-Mail:"
|
||||
msgstr "Email:"
|
||||
|
||||
msgid "Einweisung"
|
||||
msgstr "Introduction"
|
||||
|
||||
msgid "Einweisung wurde als Entwurf gespeichert."
|
||||
msgstr "Introduction was saved as draft."
|
||||
|
||||
msgid "Einweisung wurde als abgeschlossen gespeichert."
|
||||
msgstr "Introduction was saved as completed."
|
||||
|
||||
msgid "Einweisung wurde zurückgesetzt."
|
||||
msgstr "Introduction was reset."
|
||||
|
||||
msgid "Einweisung öffnen"
|
||||
msgstr "Open introduction"
|
||||
|
||||
msgid "Einweisungs- und Übergabeprotokoll wurde erzeugt."
|
||||
msgstr "Introduction and handover protocol was generated."
|
||||
|
||||
msgid "Einweisungs-Builder"
|
||||
msgstr "Introduction Builder"
|
||||
|
||||
msgid "Einweisungsprotokoll aus Live-Status wurde erzeugt."
|
||||
msgstr "Introduction protocol from live status was generated."
|
||||
|
||||
msgid "Erstellt"
|
||||
msgstr "Created"
|
||||
|
||||
msgid "Fast geschafft. Bitte Abschlussdaten prüfen und die Anfrage absenden."
|
||||
msgstr "Almost done. Please review the final details and submit the request."
|
||||
|
||||
msgid "Felder, Schritte und Optionen verwalten."
|
||||
msgstr "Manage fields, steps, and options."
|
||||
|
||||
msgid "Form Builder"
|
||||
msgstr "Form Builder"
|
||||
|
||||
msgid "Geplante Welcome Mails verwalten."
|
||||
msgstr "Manage scheduled welcome emails."
|
||||
|
||||
msgid "Geräte, Software und Zugänge"
|
||||
msgstr "Devices, software, and access"
|
||||
|
||||
msgid "Gesamtbestand"
|
||||
msgstr "Total records"
|
||||
|
||||
msgid "Hardware-Liste"
|
||||
msgstr "Hardware list"
|
||||
|
||||
msgid "IT-Rückgabe"
|
||||
msgstr "IT return"
|
||||
|
||||
msgid "IT-Setup"
|
||||
msgstr "IT setup"
|
||||
|
||||
msgid "Integrationen"
|
||||
msgstr "Integrations"
|
||||
|
||||
msgid "Keine Einträge ausgewählt."
|
||||
msgstr "No entries selected."
|
||||
|
||||
msgid "Keine konfigurierten Felder in diesem Schritt."
|
||||
msgstr "No configured fields in this step."
|
||||
|
||||
msgid "Keine passenden Einträge gefunden."
|
||||
msgstr "No matching entries found."
|
||||
|
||||
msgid "Konfiguration, Tests und Steuerung."
|
||||
msgstr "Configuration, tests, and controls."
|
||||
|
||||
msgid "Live-Protokoll"
|
||||
msgstr "Live protocol"
|
||||
|
||||
msgid "Live-Protokoll öffnen"
|
||||
msgstr "Open live protocol"
|
||||
|
||||
msgid "Löschen"
|
||||
msgstr "Delete"
|
||||
|
||||
msgid "Mehrschritt-Formular"
|
||||
msgstr "Multi-step form"
|
||||
|
||||
msgid "Mehrseitiges Formular mit konfigurierbaren Feldern aus dem Admin."
|
||||
msgstr "Multi-page form with configurable fields from the admin."
|
||||
|
||||
msgid "Mitarbeitende suchen (Name oder E-Mail)"
|
||||
msgstr "Search employees (name or email)"
|
||||
|
||||
msgid "Mitarbeitende suchen, Daten vorbefüllen, Offboarding-Dokumente erzeugen und Rückgabe-Prozess starten."
|
||||
msgstr "Search employees, prefill data, generate offboarding documents, and start the return process."
|
||||
|
||||
msgid "Mitarbeiter"
|
||||
msgstr "Staff"
|
||||
|
||||
msgid "Nach Name oder E-Mail suchen"
|
||||
msgstr "Search by name or email"
|
||||
|
||||
msgid "Neu erzeugen"
|
||||
msgstr "Regenerate"
|
||||
|
||||
msgid "Neue Mitarbeitende erfassen, PDF mit Briefkopf erstellen, Benachrichtigungen senden und in Nextcloud ablegen."
|
||||
msgstr "Capture new employees, generate a PDF with letterhead, send notifications, and store it in Nextcloud."
|
||||
|
||||
msgid "Nextcloud- und E-Mail-Setup."
|
||||
msgstr "Nextcloud and email setup."
|
||||
|
||||
msgid "Nextcloud:"
|
||||
msgstr "Nextcloud:"
|
||||
|
||||
msgid "Nicht relevant"
|
||||
msgstr "Not relevant"
|
||||
|
||||
msgid "Noch keine Vorgänge vorhanden."
|
||||
msgstr "No requests available yet."
|
||||
|
||||
msgid "Noch nicht verfügbar"
|
||||
msgstr "Not available yet"
|
||||
|
||||
msgid "Notizen und Freigabe"
|
||||
msgstr "Notes and approval"
|
||||
|
||||
msgid "Offboarding"
|
||||
msgstr "Offboarding"
|
||||
|
||||
msgid "Offboarding starten"
|
||||
msgstr "Start offboarding"
|
||||
|
||||
msgid "Offboarding wurde erfolgreich gespeichert (ID: %(request_id)s). Das PDF wird im Hintergrund erzeugt."
|
||||
msgstr "Offboarding was saved successfully (ID: %(request_id)s). The PDF is being generated in the background."
|
||||
|
||||
msgid "Offboarding-Anfrage"
|
||||
msgstr "Offboarding request"
|
||||
|
||||
msgid "Offboarding-Anfrage speichern"
|
||||
msgstr "Save offboarding request"
|
||||
|
||||
msgid "Onboarding"
|
||||
msgstr "Onboarding"
|
||||
|
||||
msgid "Onboarding + Offboarding"
|
||||
msgstr "Onboarding + Offboarding"
|
||||
|
||||
msgid "Onboarding starten"
|
||||
msgstr "Start onboarding"
|
||||
|
||||
msgid "Onboarding wurde erfolgreich gespeichert (ID: %(request_id)s). Das PDF wird im Hintergrund erzeugt."
|
||||
msgstr "Onboarding was saved successfully (ID: %(request_id)s). The PDF is being generated in the background."
|
||||
|
||||
msgid "Onboarding-Anfrage"
|
||||
msgstr "Onboarding request"
|
||||
|
||||
msgid "Onboarding-Anfrage absenden"
|
||||
msgstr "Submit onboarding request"
|
||||
|
||||
msgid "Operations Console"
|
||||
msgstr "Operations Console"
|
||||
|
||||
msgid "PDF + E-Mail Workflow bereit"
|
||||
msgstr "PDF + Email Workflow Ready"
|
||||
|
||||
msgid "PDF Zugriff"
|
||||
msgstr "PDF access"
|
||||
|
||||
msgid "PDF erzeugen"
|
||||
msgstr "Generate PDF"
|
||||
|
||||
msgid "PDF öffnen"
|
||||
msgstr "Open PDF"
|
||||
|
||||
msgid "PDFs + Live-Protokolle"
|
||||
msgstr "PDFs + live protocols"
|
||||
|
||||
msgid "Person"
|
||||
msgstr "Person"
|
||||
|
||||
msgid "Person, Rolle, Abteilung"
|
||||
msgstr "Person, role, department"
|
||||
|
||||
msgid "Produktion"
|
||||
msgstr "Production"
|
||||
|
||||
msgid "Profile-Suche"
|
||||
msgstr "Profile search"
|
||||
|
||||
msgid "Projekt Wiki"
|
||||
msgstr "Project Wiki"
|
||||
|
||||
msgid "Rolle:"
|
||||
msgstr "Role:"
|
||||
|
||||
msgid "Schließen"
|
||||
msgstr "Close"
|
||||
|
||||
msgid "Sie haben keine Berechtigung für diese Aktion."
|
||||
msgstr "You do not have permission for this action."
|
||||
|
||||
msgid "Stammdaten"
|
||||
msgstr "Master data"
|
||||
|
||||
msgid "Standard-Einweisungs-PDF"
|
||||
msgstr "Standard introduction PDF"
|
||||
|
||||
msgid "Standard-PDF öffnen"
|
||||
msgstr "Open standard PDF"
|
||||
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
msgid "Status, Suchfunktion, PDF-Links und Verlauf aller Onboarding-/Offboarding-Anfragen."
|
||||
msgstr "Status, search, PDF links, and history of all onboarding/offboarding requests."
|
||||
|
||||
msgid "Status:"
|
||||
msgstr "Status:"
|
||||
|
||||
msgid "Steuert Onboarding- und Offboarding-Prozesse an einem Ort. Die Oberfläche priorisiert Kennzahlen, Aktivität und direkte Aktionen in der Vorgangsliste."
|
||||
msgstr "Controls onboarding and offboarding processes in one place. The interface prioritizes metrics, activity, and direct actions in the request list."
|
||||
|
||||
msgid "Suche"
|
||||
msgstr "Search"
|
||||
|
||||
msgid "Suche + Bulk-Aktionen"
|
||||
msgstr "Search + bulk actions"
|
||||
|
||||
msgid "Suchen"
|
||||
msgstr "Search"
|
||||
|
||||
msgid "TUBCO Onboarding & Offboarding Portal"
|
||||
msgstr "TUBCO Onboarding & Offboarding Portal"
|
||||
|
||||
msgid "Testmodus"
|
||||
msgstr "Test mode"
|
||||
|
||||
msgid "Tipp: Die letzten Vorgänge sehen Sie jederzeit im Anfragen Dashboard."
|
||||
msgstr "Tip: You can always see the latest requests in the Requests Dashboard."
|
||||
|
||||
msgid "Typ"
|
||||
msgstr "Type"
|
||||
|
||||
msgid "Vertrag"
|
||||
msgstr "Contract"
|
||||
|
||||
msgid "Vollständige Datenverwaltung."
|
||||
msgstr "Full data management."
|
||||
|
||||
msgid "Vorbefüllt aus:"
|
||||
msgstr "Prefilled from:"
|
||||
|
||||
msgid "Vorgänge"
|
||||
msgstr "Requests"
|
||||
|
||||
msgid "Weiter"
|
||||
msgstr "Next"
|
||||
|
||||
msgid "Welcome E-Mails"
|
||||
msgstr "Welcome Emails"
|
||||
|
||||
msgid "Wählen Sie den gewünschten Prozess."
|
||||
msgstr "Choose the desired process."
|
||||
|
||||
msgid "Zeitraum des visuellen Aktivitätsverlaufs in dieser Übersicht."
|
||||
msgstr "Time span of the visual activity timeline in this overview."
|
||||
|
||||
msgid "Zentrale Arbeitsfläche für Anfragen, PDF-Generierung, E-Mail-Workflows und Ablage in Nextcloud."
|
||||
msgstr "Central workspace for requests, PDF generation, email workflows, and storage in Nextcloud."
|
||||
|
||||
msgid "Zur Startseite"
|
||||
msgstr "Back to home"
|
||||
|
||||
msgid "Zurück"
|
||||
msgstr "Back"
|
||||
|
||||
msgid "Zurücksetzen"
|
||||
msgstr "Reset"
|
||||
|
||||
msgid "aktiv"
|
||||
msgstr "active"
|
||||
|
||||
msgid "ausgewählt"
|
||||
msgstr "selected"
|
||||
|
||||
msgid "inaktiv"
|
||||
msgstr "inactive"
|
||||
|
||||
msgid "z. B. max.mustermann@tub.co"
|
||||
msgstr "e.g. john.doe@tub.co"
|
||||
|
||||
msgid "Öffnen"
|
||||
msgstr "Open"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{% load static %}
|
||||
{% load static i18n %}
|
||||
{% get_current_language as CURRENT_LANGUAGE %}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<html lang="{{ CURRENT_LANGUAGE }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Anmeldung</title>
|
||||
<title>{% trans "Anmeldung" %}</title>
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
|
||||
<style>
|
||||
body { margin: 0; font-family: Arial, sans-serif; min-height: 100vh; display: grid; place-items: center; background: linear-gradient(160deg, #eef6ff, #fff3f3); }
|
||||
@@ -17,22 +18,34 @@
|
||||
input { width: 100%; padding: 10px; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; }
|
||||
.btn { width: 100%; }
|
||||
.errorlist { color: #b91c1c; margin: 6px 0; }
|
||||
.card-head { display:flex; justify-content:space-between; gap:12px; align-items:flex-start; }
|
||||
.lang-switch { display:flex; gap:6px; }
|
||||
.lang-btn { border:1px solid #d9e3f0; background:#f8fbff; color:#1f3a5f; border-radius:999px; padding:6px 10px; font-size:12px; font-weight:700; cursor:pointer; }
|
||||
.lang-btn.active { background:#000078; border-color:#000078; color:#fff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<img class="logo" src="https://tub.co/media/site/0856bfc615-1750234287/tubco-wortbildmarke.svg" alt="TUB/CO Logo" />
|
||||
<h1>Anmeldung</h1>
|
||||
<p>Bitte melden Sie sich mit Ihrem Benutzerkonto an.</p>
|
||||
<div class="card-head">
|
||||
<img class="logo" src="https://tub.co/media/site/0856bfc615-1750234287/tubco-wortbildmarke.svg" alt="TUB/CO Logo" />
|
||||
<form method="post" action="{% url 'set_language' %}" class="lang-switch">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
|
||||
<button class="lang-btn {% if CURRENT_LANGUAGE == 'de' %}active{% endif %}" type="submit" name="language" value="de">DE</button>
|
||||
<button class="lang-btn {% if CURRENT_LANGUAGE == 'en' %}active{% endif %}" type="submit" name="language" value="en">EN</button>
|
||||
</form>
|
||||
</div>
|
||||
<h1>{% trans "Anmeldung" %}</h1>
|
||||
<p>{% trans "Bitte melden Sie sich mit Ihrem Benutzerkonto an." %}</p>
|
||||
|
||||
<form method="post" action="/accounts/login/">
|
||||
{% csrf_token %}
|
||||
{% if form.errors %}
|
||||
<div class="errorlist">Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen.</div>
|
||||
<div class="errorlist">{% trans "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen." %}</div>
|
||||
{% endif %}
|
||||
<div class="field">{{ form.username.label_tag }}{{ form.username }}</div>
|
||||
<div class="field">{{ form.password.label_tag }}{{ form.password }}</div>
|
||||
<button class="btn btn-primary" type="submit">Anmelden</button>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Anmelden" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{% load static %}
|
||||
{% load static i18n %}
|
||||
{% get_current_language as CURRENT_LANGUAGE %}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<html lang="{{ CURRENT_LANGUAGE }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>TUBCO Onboarding & Offboarding Portal</title>
|
||||
<title>{% trans "TUBCO Onboarding & Offboarding Portal" %}</title>
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
|
||||
<style>
|
||||
:root {
|
||||
@@ -68,7 +69,11 @@
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
.lang-switch { display:flex; gap:6px; }
|
||||
.lang-btn { border:1px solid var(--line); background:#f8fbff; color:#1f3a5f; border-radius:999px; padding:6px 10px; font-size:12px; font-weight:700; cursor:pointer; }
|
||||
.lang-btn.active { background:var(--brand-blue); border-color:var(--brand-blue); color:#fff; }
|
||||
|
||||
.hero {
|
||||
padding: 24px;
|
||||
@@ -433,9 +438,15 @@
|
||||
<div class="topbar">
|
||||
<img class="brand-logo" src="https://tub.co/media/site/0856bfc615-1750234287/tubco-wortbildmarke.svg" alt="TUB/CO Logo" />
|
||||
<div class="quick-actions">
|
||||
<form method="post" action="{% url 'set_language' %}" class="lang-switch">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
|
||||
<button class="lang-btn {% if CURRENT_LANGUAGE == 'de' %}active{% endif %}" type="submit" name="language" value="de">DE</button>
|
||||
<button class="lang-btn {% if CURRENT_LANGUAGE == 'en' %}active{% endif %}" type="submit" name="language" value="en">EN</button>
|
||||
</form>
|
||||
<form method="post" action="/accounts/logout/" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-secondary" type="submit">Abmelden</button>
|
||||
<button class="btn btn-secondary" type="submit">{% trans "Abmelden" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -443,18 +454,18 @@
|
||||
<div class="hero">
|
||||
<div class="hero-grid">
|
||||
<div class="hero-card">
|
||||
<span class="eyebrow">Operations Console</span>
|
||||
<h1>TUBCO Onboarding & Offboarding Portal</h1>
|
||||
<p>Zentrale Arbeitsfläche für Anfragen, PDF-Generierung, E-Mail-Workflows und Ablage in Nextcloud.</p>
|
||||
<span class="eyebrow">{% trans "Operations Console" %}</span>
|
||||
<h1>{% trans "TUBCO Onboarding & Offboarding Portal" %}</h1>
|
||||
<p>{% trans "Zentrale Arbeitsfläche für Anfragen, PDF-Generierung, E-Mail-Workflows und Ablage in Nextcloud." %}</p>
|
||||
<div class="status-row">
|
||||
<span class="status-pill">Rolle: {% if request.user.is_staff %}Admin{% else %}Mitarbeiter{% endif %}</span>
|
||||
<span class="status-pill">{% trans "Rolle:" %} {% if request.user.is_staff %}{% trans "Admin" %}{% else %}{% trans "Mitarbeiter" %}{% endif %}</span>
|
||||
<span class="status-pill {% if nextcloud_enabled %}ok{% else %}warn{% endif %}">
|
||||
Nextcloud: {% if nextcloud_enabled %}aktiv{% else %}inaktiv{% endif %}
|
||||
{% trans "Nextcloud:" %} {% if nextcloud_enabled %}{% trans "aktiv" %}{% else %}{% trans "inaktiv" %}{% endif %}
|
||||
</span>
|
||||
<span class="status-pill {% if email_test_mode %}warn{% else %}ok{% endif %}">
|
||||
E-Mail: {% if email_test_mode %}Testmodus{% else %}Produktion{% endif %}
|
||||
{% trans "E-Mail:" %} {% if email_test_mode %}{% trans "Testmodus" %}{% else %}{% trans "Produktion" %}{% endif %}
|
||||
</span>
|
||||
<span class="status-pill">PDF + Email Workflow Ready</span>
|
||||
<span class="status-pill">{% trans "PDF + E-Mail Workflow bereit" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -468,94 +479,94 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="section-head">
|
||||
<h2>Apps</h2>
|
||||
<p>Wählen Sie den gewünschten Prozess.</p>
|
||||
<h2>{% trans "Apps" %}</h2>
|
||||
<p>{% trans "Wählen Sie den gewünschten Prozess." %}</p>
|
||||
</div>
|
||||
<div class="apps-grid">
|
||||
<section class="app-card primary">
|
||||
<div>
|
||||
<div class="top-line"><div class="accent">ON</div></div>
|
||||
<h3 class="app-title">Onboarding</h3>
|
||||
<p class="app-text">Neue Mitarbeitende erfassen, PDF mit Briefkopf erstellen, Benachrichtigungen senden und in Nextcloud ablegen.</p>
|
||||
<h3 class="app-title">{% trans "Onboarding" %}</h3>
|
||||
<p class="app-text">{% trans "Neue Mitarbeitende erfassen, PDF mit Briefkopf erstellen, Benachrichtigungen senden und in Nextcloud ablegen." %}</p>
|
||||
<div class="tag-row">
|
||||
<span class="tag">Mehrschritt-Formular</span>
|
||||
<span class="tag">{% trans "Mehrschritt-Formular" %}</span>
|
||||
<span class="tag">PDF</span>
|
||||
<span class="tag">E-Mail Routing</span>
|
||||
<span class="tag">{% trans "E-Mail Routing" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a class="btn btn-primary" href="/onboarding/new/">Onboarding starten</a>
|
||||
<a class="btn btn-primary" href="/onboarding/new/">{% trans "Onboarding starten" %}</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="app-card red">
|
||||
<div>
|
||||
<div class="top-line"><div class="accent red">OFF</div></div>
|
||||
<h3 class="app-title">Offboarding</h3>
|
||||
<p class="app-text">Mitarbeitende suchen, Daten vorbefüllen, Offboarding-Dokumente erzeugen und Rückgabe-Prozess starten.</p>
|
||||
<h3 class="app-title">{% trans "Offboarding" %}</h3>
|
||||
<p class="app-text">{% trans "Mitarbeitende suchen, Daten vorbefüllen, Offboarding-Dokumente erzeugen und Rückgabe-Prozess starten." %}</p>
|
||||
<div class="tag-row">
|
||||
<span class="tag">Profile-Suche</span>
|
||||
<span class="tag">Hardware-Liste</span>
|
||||
<span class="tag">IT-Rückgabe</span>
|
||||
<span class="tag">{% trans "Profile-Suche" %}</span>
|
||||
<span class="tag">{% trans "Hardware-Liste" %}</span>
|
||||
<span class="tag">{% trans "IT-Rückgabe" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a class="btn btn-primary" href="/offboarding/new/">Offboarding starten</a>
|
||||
<a class="btn btn-primary" href="/offboarding/new/">{% trans "Offboarding starten" %}</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="app-card">
|
||||
<div>
|
||||
<div class="top-line"><div class="accent">APP</div></div>
|
||||
<h3 class="app-title">Anfragen Dashboard</h3>
|
||||
<p class="app-text">Status, Suchfunktion, PDF-Links und Verlauf aller Onboarding-/Offboarding-Anfragen.</p>
|
||||
<h3 class="app-title">{% trans "Anfragen Dashboard" %}</h3>
|
||||
<p class="app-text">{% trans "Status, Suchfunktion, PDF-Links und Verlauf aller Onboarding-/Offboarding-Anfragen." %}</p>
|
||||
<div class="tag-row">
|
||||
<span class="tag">Suche</span>
|
||||
<span class="tag">Status</span>
|
||||
<span class="tag">PDF Zugriff</span>
|
||||
<span class="tag">{% trans "Suche" %}</span>
|
||||
<span class="tag">{% trans "Status" %}</span>
|
||||
<span class="tag">{% trans "PDF Zugriff" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<a class="btn btn-secondary" href="/requests/">Dashboard öffnen</a>
|
||||
<a class="btn btn-secondary" href="/requests/">{% trans "Dashboard öffnen" %}</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{% if request.user.is_staff %}
|
||||
<div class="section-head">
|
||||
<h2>Admin Apps</h2>
|
||||
<p>Konfiguration, Tests und Steuerung.</p>
|
||||
<h2>{% trans "Admin Apps" %}</h2>
|
||||
<p>{% trans "Konfiguration, Tests und Steuerung." %}</p>
|
||||
</div>
|
||||
<div class="admin-grid">
|
||||
<section class="admin-card">
|
||||
<h3>Form Builder</h3>
|
||||
<p>Felder, Schritte und Optionen verwalten.</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/form-builder/">Öffnen</a>
|
||||
<h3>{% trans "Form Builder" %}</h3>
|
||||
<p>{% trans "Felder, Schritte und Optionen verwalten." %}</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/form-builder/">{% trans "Öffnen" %}</a>
|
||||
</section>
|
||||
<section class="admin-card">
|
||||
<h3>Einweisungs-Builder</h3>
|
||||
<p>Checklistenpunkte für das Einweisungsprotokoll konfigurieren.</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/intro-builder/">Öffnen</a>
|
||||
<h3>{% trans "Einweisungs-Builder" %}</h3>
|
||||
<p>{% trans "Checklistenpunkte für das Einweisungsprotokoll konfigurieren." %}</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/intro-builder/">{% trans "Öffnen" %}</a>
|
||||
</section>
|
||||
<section class="admin-card">
|
||||
<h3>Projekt Wiki</h3>
|
||||
<p>Dokumentation, Architektur und Runbook.</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/wiki/">Öffnen</a>
|
||||
<h3>{% trans "Projekt Wiki" %}</h3>
|
||||
<p>{% trans "Dokumentation, Architektur und Runbook." %}</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/wiki/">{% trans "Öffnen" %}</a>
|
||||
</section>
|
||||
<section class="admin-card">
|
||||
<h3>Integrationen</h3>
|
||||
<p>Nextcloud- und E-Mail-Setup.</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/integrations/?kind=nextcloud">Öffnen</a>
|
||||
<h3>{% trans "Integrationen" %}</h3>
|
||||
<p>{% trans "Nextcloud- und E-Mail-Setup." %}</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/integrations/?kind=nextcloud">{% trans "Öffnen" %}</a>
|
||||
</section>
|
||||
<section class="admin-card">
|
||||
<h3>Welcome E-Mails</h3>
|
||||
<p>Geplante Welcome Mails verwalten.</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/welcome-emails/">Öffnen</a>
|
||||
<h3>{% trans "Welcome E-Mails" %}</h3>
|
||||
<p>{% trans "Geplante Welcome Mails verwalten." %}</p>
|
||||
<a class="btn btn-secondary" href="/admin-tools/welcome-emails/">{% trans "Öffnen" %}</a>
|
||||
</section>
|
||||
<section class="admin-card">
|
||||
<h3>Django Admin</h3>
|
||||
<p>Vollständige Datenverwaltung.</p>
|
||||
<a class="btn btn-secondary" href="/admin/">Öffnen</a>
|
||||
<h3>{% trans "Django Admin" %}</h3>
|
||||
<p>{% trans "Vollständige Datenverwaltung." %}</p>
|
||||
<a class="btn btn-secondary" href="/admin/">{% trans "Öffnen" %}</a>
|
||||
</section>
|
||||
<section class="admin-card">
|
||||
<h3>SMTP Einstellungen</h3>
|
||||
@@ -600,7 +611,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="footer-note">
|
||||
Tipp: Die letzten Vorgänge sehen Sie jederzeit im Anfragen Dashboard.
|
||||
{% trans "Tipp: Die letzten Vorgänge sehen Sie jederzeit im Anfragen Dashboard." %}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
{% load static %}
|
||||
{% load static i18n %}
|
||||
{% get_current_language as CURRENT_LANGUAGE %}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<html lang="{{ CURRENT_LANGUAGE }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Offboarding-Anfrage</title>
|
||||
<title>{% trans "Offboarding-Anfrage" %}</title>
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/offboarding_form.css' %}" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="saved-popup" class="popup-backdrop {% if saved %}show{% endif %}">
|
||||
<div class="popup">
|
||||
<h3>Anfrage gespeichert</h3>
|
||||
<p>Offboarding wurde erfolgreich gespeichert (ID: {{ saved_request_id }}). Das PDF wird im Hintergrund erzeugt.</p>
|
||||
<button class="btn btn-secondary" type="button" onclick="document.getElementById('saved-popup').classList.remove('show')">Schließen</button>
|
||||
<h3>{% trans "Anfrage gespeichert" %}</h3>
|
||||
<p>{% blocktrans trimmed with request_id=saved_request_id %}Offboarding wurde erfolgreich gespeichert (ID: {{ request_id }}). Das PDF wird im Hintergrund erzeugt.{% endblocktrans %}</p>
|
||||
<button class="btn btn-secondary" type="button" onclick="document.getElementById('saved-popup').classList.remove('show')">{% trans "Schließen" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrap">
|
||||
<img class="brand-logo" src="https://tub.co/media/site/0856bfc615-1750234287/tubco-wortbildmarke.svg" alt="TUB/CO Logo" />
|
||||
<div class="top-link"><a class="btn btn-secondary" href="/">Zur Startseite</a></div>
|
||||
<div class="top-link" style="display:flex; gap:8px; align-items:center;">
|
||||
<form method="post" action="{% url 'set_language' %}" class="lang-switch" style="display:flex; gap:6px;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
|
||||
<button class="btn btn-secondary" type="submit" name="language" value="de">DE</button>
|
||||
<button class="btn btn-secondary" type="submit" name="language" value="en">EN</button>
|
||||
</form>
|
||||
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a></div>
|
||||
<div class="card">
|
||||
<h1>Offboarding-Anfrage</h1>
|
||||
<h1>{% trans "Offboarding-Anfrage" %}</h1>
|
||||
<form method="get" action="/offboarding/new/">
|
||||
<div class="field">
|
||||
<label for="q">Mitarbeitende suchen (Name oder E-Mail)</label>
|
||||
<input id="q" name="q" value="{{ search_query }}" placeholder="z. B. max.mustermann@tub.co" />
|
||||
<label for="q">{% trans "Mitarbeitende suchen (Name oder E-Mail)" %}</label>
|
||||
<input id="q" name="q" value="{{ search_query }}" placeholder="{% trans "z. B. max.mustermann@tub.co" %}" />
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Suchen</button>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Suchen" %}</button>
|
||||
</form>
|
||||
|
||||
{% if search_results %}
|
||||
@@ -39,7 +47,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if selected_profile %}
|
||||
<p style="margin-top:10px; color:#2563eb;">Vorbefüllt aus: <strong>{{ selected_profile.full_name }}</strong> ({{ selected_profile.work_email }})</p>
|
||||
<p style="margin-top:10px; color:#2563eb;">{% trans "Vorbefüllt aus:" %} <strong>{{ selected_profile.full_name }}</strong> ({{ selected_profile.work_email }})</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -58,7 +66,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit">Offboarding-Anfrage speichern</button>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Offboarding-Anfrage speichern" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,40 @@
|
||||
{% load static %}
|
||||
{% load static i18n %}
|
||||
{% get_current_language as CURRENT_LANGUAGE %}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<html lang="{{ CURRENT_LANGUAGE }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Onboarding-Anfrage</title>
|
||||
<title>{% trans "Onboarding-Anfrage" %}</title>
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/onboarding_form.css' %}" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="saved-popup" class="popup-backdrop {% if saved %}show{% endif %}">
|
||||
<div class="popup">
|
||||
<h3>Anfrage gespeichert</h3>
|
||||
<p>Onboarding wurde erfolgreich gespeichert (ID: {{ saved_request_id }}). Das PDF wird im Hintergrund erzeugt.</p>
|
||||
<button class="btn btn-secondary" type="button" onclick="document.getElementById('saved-popup').classList.remove('show')">Schließen</button>
|
||||
<h3>{% trans "Anfrage gespeichert" %}</h3>
|
||||
<p>{% blocktrans trimmed with request_id=saved_request_id %}Onboarding wurde erfolgreich gespeichert (ID: {{ request_id }}). Das PDF wird im Hintergrund erzeugt.{% endblocktrans %}</p>
|
||||
<button class="btn btn-secondary" type="button" onclick="document.getElementById('saved-popup').classList.remove('show')">{% trans "Schließen" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="top-wrap">
|
||||
<img class="brand-logo" src="https://tub.co/media/site/0856bfc615-1750234287/tubco-wortbildmarke.svg" alt="TUB/CO Logo" />
|
||||
<div class="top-link"><a class="btn btn-secondary" href="/">Zur Startseite</a></div>
|
||||
<div class="top-link" style="display:flex; gap:8px; align-items:center;">
|
||||
<form method="post" action="{% url 'set_language' %}" class="lang-switch" style="display:flex; gap:6px;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
|
||||
<button class="btn btn-secondary" type="submit" name="language" value="de">DE</button>
|
||||
<button class="btn btn-secondary" type="submit" name="language" value="en">EN</button>
|
||||
</form>
|
||||
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shell">
|
||||
<aside class="panel">
|
||||
<h1>Onboarding</h1>
|
||||
<p class="sub">Mehrseitiges Formular mit konfigurierbaren Feldern aus dem Admin.</p>
|
||||
<h1>{% trans "Onboarding" %}</h1>
|
||||
<p class="sub">{% trans "Mehrseitiges Formular mit konfigurierbaren Feldern aus dem Admin." %}</p>
|
||||
<ol class="step-list">
|
||||
{% for section in onboarding_sections %}
|
||||
<li class="step-item {% if forloop.first %}active{% endif %}" data-nav-step="{{ forloop.counter }}" role="button" tabindex="0" aria-label="{{ section.title }}">
|
||||
@@ -41,7 +50,7 @@
|
||||
|
||||
<main class="main">
|
||||
{% if form.errors %}
|
||||
<div class="error-banner">Bitte prüfen Sie die markierten Felder. Ungültige Eingaben wurden erkannt.</div>
|
||||
<div class="error-banner">{% trans "Bitte prüfen Sie die markierten Felder. Ungültige Eingaben wurden erkannt." %}</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" id="onboarding-form" enctype="multipart/form-data">
|
||||
@@ -101,12 +110,12 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not section.blocks %}
|
||||
<div class="field field-full empty-step">Keine konfigurierten Felder in diesem Schritt.</div>
|
||||
<div class="field field-full empty-step">{% trans "Keine konfigurierten Felder in diesem Schritt." %}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if section.key == 'abschluss' %}
|
||||
<div class="field-full finish-note">
|
||||
Fast geschafft. Bitte Abschlussdaten prüfen und die Anfrage absenden.
|
||||
{% trans "Fast geschafft. Bitte Abschlussdaten prüfen und die Anfrage absenden." %}
|
||||
</div>
|
||||
<div class="field-full">
|
||||
<div class="legal">{{ legal_text }}</div>
|
||||
@@ -118,9 +127,9 @@
|
||||
{% endfor %}
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" type="button" id="btn-prev">Zurück</button>
|
||||
<button class="btn btn-primary" type="button" id="btn-next">Weiter</button>
|
||||
<button type="submit" id="btn-submit" class="btn btn-primary hidden">Onboarding-Anfrage absenden</button>
|
||||
<button class="btn btn-secondary" type="button" id="btn-prev">{% trans "Zurück" %}</button>
|
||||
<button class="btn btn-primary" type="button" id="btn-next">{% trans "Weiter" %}</button>
|
||||
<button type="submit" id="btn-submit" class="btn btn-primary hidden">{% trans "Onboarding-Anfrage absenden" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
@@ -161,6 +161,22 @@
|
||||
<li>Use SMTP test action before switching to production mode.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="bilingual">8b) Bilingual Core UI</h2>
|
||||
<ul>
|
||||
<li><strong>Current scope:</strong> the core user interface supports German and English switching for the main fixed UI pages.</li>
|
||||
<li><strong>Covered now:</strong> login, home, requests dashboard, onboarding form shell, offboarding form shell, and common status/messages in views.</li>
|
||||
<li><strong>Not fully bilingual yet:</strong> dynamic Form Builder content, intro-builder item labels, admin-configured email templates, and most generated PDF/business text remain primarily single-language.</li>
|
||||
<li><strong>Implementation:</strong> Django i18n with locale middleware, translation catalogs, and a DE/EN language switch in the main UI.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Translation Workflow</h3>
|
||||
<ul>
|
||||
<li>The long-term translation path uses Django's standard <code>makemessages</code> and <code>compilemessages</code> workflow.</li>
|
||||
<li><code>gettext</code> is installed in the Docker image so translations can be compiled inside the running container.</li>
|
||||
<li>Translation catalogs live under <code>/backend/locale/</code>.</li>
|
||||
<li>The earlier ad hoc Python-based <code>.mo</code> compilation path should no longer be used for ongoing maintenance.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="admin">9) Admin Apps (Home)</h2>
|
||||
<ul>
|
||||
<li><strong>Form Builder:</strong> manage field visibility/order/options.</li>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{% load static %}
|
||||
{% load static i18n %}
|
||||
{% get_current_language as CURRENT_LANGUAGE %}
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<html lang="{{ CURRENT_LANGUAGE }}">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Anfragen Dashboard</title>
|
||||
<title>{% trans "Anfragen Dashboard" %}</title>
|
||||
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
|
||||
<style>
|
||||
:root {
|
||||
@@ -81,7 +82,11 @@
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
.lang-switch { display:flex; gap:6px; }
|
||||
.lang-btn { border:1px solid var(--line); background:#f8fbff; color:#1f3a5f; border-radius:999px; padding:6px 10px; font-size:12px; font-weight:700; cursor:pointer; }
|
||||
.lang-btn.active { background:var(--brand-blue); border-color:var(--brand-blue); color:#fff; }
|
||||
|
||||
.hero {
|
||||
padding: 0;
|
||||
@@ -875,7 +880,13 @@
|
||||
<img class="brand-logo" src="https://tub.co/media/site/0856bfc615-1750234287/tubco-wortbildmarke.svg" alt="TUB/CO Logo" />
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<a class="btn btn-secondary" href="/">Zur Startseite</a>
|
||||
<form method="post" action="{% url 'set_language' %}" class="lang-switch">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
|
||||
<button class="lang-btn {% if CURRENT_LANGUAGE == 'de' %}active{% endif %}" type="submit" name="language" value="de">DE</button>
|
||||
<button class="lang-btn {% if CURRENT_LANGUAGE == 'en' %}active{% endif %}" type="submit" name="language" value="en">EN</button>
|
||||
</form>
|
||||
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -883,22 +894,22 @@
|
||||
<section class="hero">
|
||||
<div class="hero-grid">
|
||||
<div class="hero-card">
|
||||
<span class="eyebrow">Operations Console</span>
|
||||
<h1>Anfragen Dashboard</h1>
|
||||
<p>Steuert Onboarding- und Offboarding-Prozesse an einem Ort. Die Oberfläche priorisiert Kennzahlen, Aktivität und direkte Aktionen in der Vorgangsliste.</p>
|
||||
<span class="eyebrow">{% trans "Operations Console" %}</span>
|
||||
<h1>{% trans "Anfragen Dashboard" %}</h1>
|
||||
<p>{% trans "Steuert Onboarding- und Offboarding-Prozesse an einem Ort. Die Oberfläche priorisiert Kennzahlen, Aktivität und direkte Aktionen in der Vorgangsliste." %}</p>
|
||||
<div class="hero-pills">
|
||||
<span class="hero-pill">Onboarding + Offboarding</span>
|
||||
<span class="hero-pill">PDFs + Live-Protokolle</span>
|
||||
<span class="hero-pill">Suche + Bulk-Aktionen</span>
|
||||
<span class="hero-pill">{% trans "Onboarding + Offboarding" %}</span>
|
||||
<span class="hero-pill">{% trans "PDFs + Live-Protokolle" %}</span>
|
||||
<span class="hero-pill">{% trans "Suche + Bulk-Aktionen" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<article class="chart-card activity-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>Aktivitätsverlauf</h2>
|
||||
<p>Die letzten 14 Tage in einer kompakten Ansicht über alle Onboarding- und Offboarding-Vorgänge.</p>
|
||||
<h2>{% trans "Aktivitätsverlauf" %}</h2>
|
||||
<p>{% trans "Die letzten 14 Tage in einer kompakten Ansicht über alle Onboarding- und Offboarding-Vorgänge." %}</p>
|
||||
</div>
|
||||
<span class="eyebrow">14 Tage</span>
|
||||
<span class="eyebrow">{% trans "14 Tage" %}</span>
|
||||
</div>
|
||||
<div class="chart" aria-label="Aktivitätsverlauf der letzten 14 Tage">
|
||||
{% for point in chart_points %}
|
||||
@@ -919,42 +930,42 @@
|
||||
<article class="stat-card">
|
||||
<div class="stat-head">
|
||||
<div>
|
||||
<p class="stat-title">Onboarding</p>
|
||||
<p class="stat-title">{% trans "Onboarding" %}</p>
|
||||
<div class="stat-value">{{ onboarding_total }}</div>
|
||||
</div>
|
||||
<div class="stat-kicker">ON</div>
|
||||
</div>
|
||||
<div class="stat-foot">Alle erfassten Onboarding-Vorgänge im aktuellen System.</div>
|
||||
<div class="stat-foot">{% trans "Alle erfassten Onboarding-Vorgänge im aktuellen System." %}</div>
|
||||
</article>
|
||||
<article class="stat-card red">
|
||||
<div class="stat-head">
|
||||
<div>
|
||||
<p class="stat-title">Offboarding</p>
|
||||
<p class="stat-title">{% trans "Offboarding" %}</p>
|
||||
<div class="stat-value">{{ offboarding_total }}</div>
|
||||
</div>
|
||||
<div class="stat-kicker">OFF</div>
|
||||
</div>
|
||||
<div class="stat-foot">Austritte und Rückgaben in derselben Prozessübersicht.</div>
|
||||
<div class="stat-foot">{% trans "Austritte und Rückgaben in derselben Prozessübersicht." %}</div>
|
||||
</article>
|
||||
<article class="stat-card gold">
|
||||
<div class="stat-head">
|
||||
<div>
|
||||
<p class="stat-title">Gesamtbestand</p>
|
||||
<p class="stat-title">{% trans "Gesamtbestand" %}</p>
|
||||
<div class="stat-value">{{ combined_total }}</div>
|
||||
</div>
|
||||
<div class="stat-kicker">Σ</div>
|
||||
</div>
|
||||
<div class="stat-foot">Alle Vorgänge, durchsuchbar und mit Dokumenten verknüpft.</div>
|
||||
<div class="stat-foot">{% trans "Alle Vorgänge, durchsuchbar und mit Dokumenten verknüpft." %}</div>
|
||||
</article>
|
||||
<article class="stat-card ink">
|
||||
<div class="stat-head">
|
||||
<div>
|
||||
<p class="stat-title">Aktivität 14 Tage</p>
|
||||
<p class="stat-title">{% trans "Aktivität 14 Tage" %}</p>
|
||||
<div class="stat-value">{{ chart_points|length }}</div>
|
||||
</div>
|
||||
<div class="stat-kicker">D</div>
|
||||
</div>
|
||||
<div class="stat-foot">Zeitraum des visuellen Aktivitätsverlaufs in dieser Übersicht.</div>
|
||||
<div class="stat-foot">{% trans "Zeitraum des visuellen Aktivitätsverlaufs in dieser Übersicht." %}</div>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
@@ -969,34 +980,34 @@
|
||||
<section class="table-card">
|
||||
<div class="table-head">
|
||||
<div>
|
||||
<h2>Vorgänge</h2>
|
||||
<p>Dokumente, Status und Einweisungsaktionen in einer verdichteten Arbeitsansicht.</p>
|
||||
<h2>{% trans "Vorgänge" %}</h2>
|
||||
<p>{% trans "Dokumente, Status und Einweisungsaktionen in einer verdichteten Arbeitsansicht." %}</p>
|
||||
</div>
|
||||
<div class="table-head-meta">{{ rows|length }} Einträge sichtbar</div>
|
||||
<div class="table-head-meta">{% blocktrans with count=rows|length trimmed %}{{ count }} Einträge sichtbar{% endblocktrans %}</div>
|
||||
</div>
|
||||
<div class="table-controls">
|
||||
<div class="table-controls-grid">
|
||||
<div class="control-stack">
|
||||
<form method="get" action="/requests/" class="search-form">
|
||||
<div class="search-box">
|
||||
<input type="search" name="q" value="{{ search_query }}" placeholder="Nach Name oder E-Mail suchen" aria-label="Nach Name oder E-Mail suchen" />
|
||||
<input type="search" name="q" value="{{ search_query }}" placeholder="{% trans "Nach Name oder E-Mail suchen" %}" aria-label="{% trans "Nach Name oder E-Mail suchen" %}" />
|
||||
</div>
|
||||
<div class="intro-actions">
|
||||
<button class="btn btn-primary" type="submit">Suchen</button>
|
||||
<button class="btn btn-primary" type="submit">{% trans "Suchen" %}</button>
|
||||
{% if search_query %}
|
||||
<a class="btn btn-secondary" href="/requests/">Zurücksetzen</a>
|
||||
<a class="btn btn-secondary" href="/requests/">{% trans "Zurücksetzen" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<div class="search-help">Datensätze können direkt in der Tabelle gefiltert, geöffnet, geprüft oder gelöscht werden.</div>
|
||||
<div class="search-help">{% trans "Datensätze können direkt in der Tabelle gefiltert, geöffnet, geprüft oder gelöscht werden." %}</div>
|
||||
</div>
|
||||
{% if request.user.is_staff %}
|
||||
<div class="control-stack">
|
||||
<form method="post" action="/requests/" id="bulk-delete-form" onsubmit="return confirm('Ausgewählte Einträge wirklich löschen?');">
|
||||
{% csrf_token %}
|
||||
<div class="bulk-toolbar">
|
||||
<span class="bulk-info"><span id="selected-count">0</span> ausgewählt</span>
|
||||
<button class="btn btn-secondary" type="submit">Auswahl löschen</button>
|
||||
<span class="bulk-info"><span id="selected-count">0</span> {% trans "ausgewählt" %}</span>
|
||||
<button class="btn btn-secondary" type="submit">{% trans "Auswahl löschen" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1008,13 +1019,13 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{% if request.user.is_staff %}<th class="select-col"><input type="checkbox" id="select-all" aria-label="Alle auswählen" /></th>{% endif %}
|
||||
<th>Typ</th>
|
||||
<th>Person</th>
|
||||
<th>E-Mail</th>
|
||||
<th>Erstellt</th>
|
||||
<th>Dokument</th>
|
||||
{% if request.user.is_staff %}<th>Einweisung</th>{% endif %}
|
||||
{% if request.user.is_staff %}<th>Aktion</th>{% endif %}
|
||||
<th>{% trans "Typ" %}</th>
|
||||
<th>{% trans "Person" %}</th>
|
||||
<th>{% trans "E-Mail" %}</th>
|
||||
<th>{% trans "Erstellt" %}</th>
|
||||
<th>{% trans "Dokument" %}</th>
|
||||
{% if request.user.is_staff %}<th>{% trans "Einweisung" %}</th>{% endif %}
|
||||
{% if request.user.is_staff %}<th>{% trans "Aktion" %}</th>{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -1039,63 +1050,63 @@
|
||||
<td>{{ row.created_at|date:"Y-m-d H:i" }}</td>
|
||||
<td>
|
||||
{% if row.pdf_url %}
|
||||
<a class="doc-link" href="{{ row.pdf_url }}" target="_blank" rel="noopener">PDF öffnen</a>
|
||||
<a class="doc-link" href="{{ row.pdf_url }}" target="_blank" rel="noopener">{% trans "PDF öffnen" %}</a>
|
||||
{% else %}
|
||||
<span class="person-meta">Noch nicht verfügbar</span>
|
||||
<span class="person-meta">{% trans "Noch nicht verfügbar" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if request.user.is_staff %}
|
||||
<td class="actions-cell intro-panel">
|
||||
{% if row.kind_slug == 'onboarding' %}
|
||||
<details>
|
||||
<summary class="intro-toggle">Einweisung</summary>
|
||||
<summary class="intro-toggle">{% trans "Einweisung" %}</summary>
|
||||
<div class="intro-menu">
|
||||
<div class="intro-group">
|
||||
<div class="intro-group-title">Live-Protokoll</div>
|
||||
<div class="intro-group-title">{% trans "Live-Protokoll" %}</div>
|
||||
<div class="intro-actions">
|
||||
<a class="btn btn-secondary" href="/requests/onboarding/{{ row.id }}/intro-session/">Einweisung öffnen</a>
|
||||
<a class="btn btn-secondary" href="/requests/onboarding/{{ row.id }}/intro-session/">{% trans "Einweisung öffnen" %}</a>
|
||||
{% if row.intro_session and row.intro_session.exported_pdf_url %}
|
||||
<a class="btn btn-secondary" href="{{ row.intro_session.exported_pdf_url }}" target="_blank" rel="noopener">Live-Protokoll öffnen</a>
|
||||
<a class="btn btn-secondary" href="{{ row.intro_session.exported_pdf_url }}" target="_blank" rel="noopener">{% trans "Live-Protokoll öffnen" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="intro-group">
|
||||
<div class="intro-group-title">Standard-Einweisungs-PDF</div>
|
||||
<div class="intro-group-title">{% trans "Standard-Einweisungs-PDF" %}</div>
|
||||
<div class="intro-actions">
|
||||
{% if row.intro_pdf_url %}
|
||||
<form method="post" action="/requests/onboarding/{{ row.id }}/intro-pdf/generate/" class="inline-form">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-secondary" type="submit">Neu erzeugen</button>
|
||||
<button class="btn btn-secondary" type="submit">{% trans "Neu erzeugen" %}</button>
|
||||
</form>
|
||||
<a class="btn btn-secondary" href="{{ row.intro_pdf_url }}" target="_blank" rel="noopener">Standard-PDF öffnen</a>
|
||||
<a class="btn btn-secondary" href="{{ row.intro_pdf_url }}" target="_blank" rel="noopener">{% trans "Standard-PDF öffnen" %}</a>
|
||||
{% else %}
|
||||
<form method="post" action="/requests/onboarding/{{ row.id }}/intro-pdf/generate/" class="inline-form">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-secondary" type="submit">PDF erzeugen</button>
|
||||
<button class="btn btn-secondary" type="submit">{% trans "PDF erzeugen" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if row.intro_session %}
|
||||
<div class="intro-meta">Status: {{ row.intro_session.get_status_display }}</div>
|
||||
<div class="intro-meta">{% trans "Status:" %} {{ row.intro_session.get_status_display }}</div>
|
||||
{% endif %}
|
||||
</details>
|
||||
{% else %}
|
||||
<span class="person-meta">Nicht relevant</span>
|
||||
<span class="person-meta">{% trans "Nicht relevant" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="actions-cell">
|
||||
<form method="post" action="/requests/" class="inline-delete" onsubmit="return confirm('Eintrag wirklich löschen?');">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-secondary" type="submit" name="single_delete" value="{{ row.kind_slug }}:{{ row.id }}">Löschen</button>
|
||||
<button class="btn btn-secondary" type="submit" name="single_delete" value="{{ row.kind_slug }}:{{ row.id }}">{% trans "Löschen" %}</button>
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="{% if request.user.is_staff %}8{% else %}5{% endif %}" class="empty-state">Noch keine Vorgänge vorhanden.</td>
|
||||
<td colspan="{% if request.user.is_staff %}8{% else %}5{% endif %}" class="empty-state">{% trans "Noch keine Vorgänge vorhanden." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -1104,8 +1115,8 @@
|
||||
</section>
|
||||
</section>
|
||||
<div class="footer-bar">
|
||||
<div class="footer-note">TUBCO Onboarding & Offboarding Portal</div>
|
||||
<a class="btn btn-secondary" href="/">Zur Startseite</a>
|
||||
<div class="footer-note">{% trans "TUBCO Onboarding & Offboarding Portal" %}</div>
|
||||
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% if request.user.is_staff %}
|
||||
|
||||
@@ -15,6 +15,7 @@ from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _, gettext_lazy
|
||||
|
||||
from .forms import OffboardingRequestForm, OnboardingRequestForm
|
||||
from .form_builder import (
|
||||
@@ -71,10 +72,10 @@ ONBOARDING_CHECKBOX_LISTS = {
|
||||
}
|
||||
ONBOARDING_SECTION_ORDER = ['stammdaten', 'vertrag', 'itsetup', 'abschluss']
|
||||
ONBOARDING_SECTION_META = {
|
||||
'stammdaten': {'title': 'Stammdaten', 'subtitle': 'Person, Rolle, Abteilung'},
|
||||
'vertrag': {'title': 'Vertrag', 'subtitle': 'Beschäftigung und Termine'},
|
||||
'itsetup': {'title': 'IT-Setup', 'subtitle': 'Geräte, Software und Zugänge'},
|
||||
'abschluss': {'title': 'Abschluss', 'subtitle': 'Notizen und Freigabe'},
|
||||
'stammdaten': {'title': gettext_lazy('Stammdaten'), 'subtitle': gettext_lazy('Person, Rolle, Abteilung')},
|
||||
'vertrag': {'title': gettext_lazy('Vertrag'), 'subtitle': gettext_lazy('Beschäftigung und Termine')},
|
||||
'itsetup': {'title': gettext_lazy('IT-Setup'), 'subtitle': gettext_lazy('Geräte, Software und Zugänge')},
|
||||
'abschluss': {'title': gettext_lazy('Abschluss'), 'subtitle': gettext_lazy('Notizen und Freigabe')},
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +219,7 @@ def project_wiki_page(request):
|
||||
def requests_dashboard(request):
|
||||
if request.method == 'POST':
|
||||
if not request.user.is_staff:
|
||||
messages.error(request, 'Sie haben keine Berechtigung für diese Aktion.')
|
||||
messages.error(request, _('Sie haben keine Berechtigung für diese Aktion.'))
|
||||
return redirect('requests_dashboard')
|
||||
|
||||
selected = request.POST.getlist('selected_requests')
|
||||
@@ -227,7 +228,7 @@ def requests_dashboard(request):
|
||||
selected = [single_delete]
|
||||
|
||||
if not selected:
|
||||
messages.warning(request, 'Keine Einträge ausgewählt.')
|
||||
messages.warning(request, _('Keine Einträge ausgewählt.'))
|
||||
return redirect('requests_dashboard')
|
||||
|
||||
deleted_count = 0
|
||||
@@ -256,11 +257,11 @@ def requests_dashboard(request):
|
||||
deleted_count += 1
|
||||
|
||||
if deleted_count:
|
||||
messages.success(request, f'{deleted_count} Eintrag/Einträge gelöscht.')
|
||||
messages.success(request, _('%(count)s Eintrag/Einträge gelöscht.') % {'count': deleted_count})
|
||||
if invalid_count:
|
||||
messages.warning(request, f'{invalid_count} Auswahl(en) konnten nicht verarbeitet werden.')
|
||||
messages.warning(request, _('%(count)s Auswahl(en) konnten nicht verarbeitet werden.') % {'count': invalid_count})
|
||||
if not deleted_count and not invalid_count:
|
||||
messages.info(request, 'Keine passenden Einträge gefunden.')
|
||||
messages.info(request, _('Keine passenden Einträge gefunden.'))
|
||||
return redirect('requests_dashboard')
|
||||
|
||||
search_query = request.GET.get('q', '').strip()
|
||||
@@ -418,7 +419,7 @@ def generate_onboarding_intro_pdf(request, request_id: int):
|
||||
pdf_path = _generate_onboarding_intro_pdf(obj)
|
||||
obj.intro_pdf_path = str(pdf_path)
|
||||
obj.save(update_fields=['intro_pdf_path'])
|
||||
messages.success(request, 'Einweisungs- und Übergabeprotokoll wurde erzeugt.')
|
||||
messages.success(request, _('Einweisungs- und Übergabeprotokoll wurde erzeugt.'))
|
||||
return redirect('requests_dashboard')
|
||||
|
||||
|
||||
@@ -431,7 +432,7 @@ def generate_onboarding_intro_session_pdf(request, request_id: int):
|
||||
pdf_path = _generate_onboarding_intro_session_pdf(session, admin_signature_name=_display_user_name(request.user))
|
||||
session.exported_pdf_path = str(pdf_path)
|
||||
session.save(update_fields=['exported_pdf_path'])
|
||||
messages.success(request, 'Einweisungsprotokoll aus Live-Status wurde erzeugt.')
|
||||
messages.success(request, _('Einweisungsprotokoll aus Live-Status wurde erzeugt.'))
|
||||
return redirect('onboarding_intro_session_page', request_id=request_id)
|
||||
|
||||
|
||||
@@ -460,18 +461,18 @@ def onboarding_intro_session_page(request, request_id: int):
|
||||
session.completed_by_name = ''
|
||||
session.exported_pdf_path = ''
|
||||
session.save(update_fields=['checklist_state', 'notes', 'status', 'completed_at', 'completed_by_name', 'exported_pdf_path'])
|
||||
messages.success(request, 'Einweisung wurde zurückgesetzt.')
|
||||
messages.success(request, _('Einweisung wurde zurückgesetzt.'))
|
||||
return redirect('onboarding_intro_session_page', request_id=request_id)
|
||||
if action == 'complete':
|
||||
session.status = 'completed'
|
||||
session.completed_at = timezone.now()
|
||||
session.completed_by_name = _display_user_name(request.user)
|
||||
messages.success(request, 'Einweisung wurde als abgeschlossen gespeichert.')
|
||||
messages.success(request, _('Einweisung wurde als abgeschlossen gespeichert.'))
|
||||
else:
|
||||
session.status = 'draft'
|
||||
session.completed_at = None
|
||||
session.completed_by_name = ''
|
||||
messages.success(request, 'Einweisung wurde als Entwurf gespeichert.')
|
||||
messages.success(request, _('Einweisung wurde als Entwurf gespeichert.'))
|
||||
session.save()
|
||||
return redirect('onboarding_intro_session_page', request_id=request_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user