snapshot: preserve shared base shell foundation

This commit is contained in:
Md Bayazid Bostame
2026-03-24 17:43:01 +01:00
parent 815c805b08
commit 5c6d300c4e
22 changed files with 615 additions and 176 deletions

View File

@@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: tubco-portal\n" "Project-Id-Version: tubco-portal\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-24 13:05+0000\n" "POT-Creation-Date: 2026-03-24 15:40+0000\n"
"PO-Revision-Date: 2026-03-24 00:00+0000\n" "PO-Revision-Date: 2026-03-24 00:00+0000\n"
"Language: en\n" "Language: en\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -262,40 +262,40 @@ msgstr "deployment, security, and maintenance notes"
msgid "Open Developer Handbook" msgid "Open Developer Handbook"
msgstr "Open Developer Handbook" msgstr "Open Developer Handbook"
#: workflows/templates/workflows/handbook.html:67 #: workflows/templates/workflows/handbook.html:68
#: workflows/templates/workflows/release_checklist.html:36 #: workflows/templates/workflows/release_checklist.html:36
msgid "Release" msgid "Release"
msgstr "" msgstr ""
#: workflows/templates/workflows/handbook.html:68 #: workflows/templates/workflows/handbook.html:69
#: workflows/templates/workflows/release_checklist.html:7 #: workflows/templates/workflows/release_checklist.html:7
#: workflows/templates/workflows/release_checklist.html:38 #: workflows/templates/workflows/release_checklist.html:38
msgid "Release Checklist" msgid "Release Checklist"
msgstr "" msgstr ""
#: workflows/templates/workflows/handbook.html:69 #: workflows/templates/workflows/handbook.html:70
msgid "" msgid ""
"Step-by-step release runbook for rebuilds, migrations, translations, static " "Step-by-step release runbook for rebuilds, migrations, translations, static "
"assets, smoke checks, and rollout verification." "assets, smoke checks, and rollout verification."
msgstr "" msgstr ""
#: workflows/templates/workflows/handbook.html:71 #: workflows/templates/workflows/handbook.html:72
msgid "pre-release validation commands" msgid "pre-release validation commands"
msgstr "" msgstr ""
#: workflows/templates/workflows/handbook.html:72 #: workflows/templates/workflows/handbook.html:73
msgid "translation, static, and migration steps" msgid "translation, static, and migration steps"
msgstr "" msgstr ""
#: workflows/templates/workflows/handbook.html:73 #: workflows/templates/workflows/handbook.html:74
msgid "post-release smoke checks" msgid "post-release smoke checks"
msgstr "" msgstr ""
#: workflows/templates/workflows/handbook.html:74 #: workflows/templates/workflows/handbook.html:75
msgid "rollback and evidence checklist" msgid "rollback and evidence checklist"
msgstr "" msgstr ""
#: workflows/templates/workflows/handbook.html:77 #: workflows/templates/workflows/handbook.html:78
msgid "Open Release Checklist" msgid "Open Release Checklist"
msgstr "" msgstr ""
@@ -823,23 +823,38 @@ msgid ""
"Bitte prüfen Sie die markierten Felder. Ungültige Eingaben wurden erkannt." "Bitte prüfen Sie die markierten Felder. Ungültige Eingaben wurden erkannt."
msgstr "Please check the highlighted fields. Invalid input was detected." msgstr "Please check the highlighted fields. Invalid input was detected."
#: workflows/templates/workflows/onboarding_form.html:113 #: workflows/templates/workflows/onboarding_form.html:86
#: workflows/templates/workflows/onboarding_form.html:88
#: workflows/templates/workflows/onboarding_form.html:125
#: workflows/templates/workflows/onboarding_form.html:127
#: workflows/templates/workflows/welcome_emails.html:105
msgid "Alle auswählen"
msgstr "Select all"
#: workflows/templates/workflows/onboarding_form.html:87
#: workflows/templates/workflows/onboarding_form.html:126
#, fuzzy
#| msgid "Auswahl löschen"
msgid "Auswahl aufheben"
msgstr "Delete selection"
#: workflows/templates/workflows/onboarding_form.html:149
msgid "Keine konfigurierten Felder in diesem Schritt." msgid "Keine konfigurierten Felder in diesem Schritt."
msgstr "No configured fields in this step." msgstr "No configured fields in this step."
#: workflows/templates/workflows/onboarding_form.html:118 #: workflows/templates/workflows/onboarding_form.html:154
msgid "Fast geschafft. Bitte Abschlussdaten prüfen und die Anfrage absenden." msgid "Fast geschafft. Bitte Abschlussdaten prüfen und die Anfrage absenden."
msgstr "Almost done. Please review the final details and submit the request." msgstr "Almost done. Please review the final details and submit the request."
#: workflows/templates/workflows/onboarding_form.html:130 #: workflows/templates/workflows/onboarding_form.html:166
msgid "Zurück" msgid "Zurück"
msgstr "Back" msgstr "Back"
#: workflows/templates/workflows/onboarding_form.html:131 #: workflows/templates/workflows/onboarding_form.html:167
msgid "Weiter" msgid "Weiter"
msgstr "Next" msgstr "Next"
#: workflows/templates/workflows/onboarding_form.html:132 #: workflows/templates/workflows/onboarding_form.html:168
msgid "Onboarding-Anfrage absenden" msgid "Onboarding-Anfrage absenden"
msgstr "Submit onboarding request" msgstr "Submit onboarding request"
@@ -1318,10 +1333,6 @@ msgstr "Available keywords:"
msgid "Welcome-Einstellungen speichern" msgid "Welcome-Einstellungen speichern"
msgstr "Save welcome settings" msgstr "Save welcome settings"
#: workflows/templates/workflows/welcome_emails.html:105
msgid "Alle auswählen"
msgstr "Select all"
#: workflows/templates/workflows/welcome_emails.html:108 #: workflows/templates/workflows/welcome_emails.html:108
#: workflows/templates/workflows/welcome_emails.html:163 #: workflows/templates/workflows/welcome_emails.html:163
msgid "Pausieren" msgid "Pausieren"

View File

@@ -0,0 +1,101 @@
:root {
--app-shell-width: 1380px;
--app-line: #d9e3ee;
--app-brand-blue: #000078;
--app-panel: rgba(255, 255, 255, 0.9);
--app-shadow: 0 22px 48px rgba(18, 34, 56, 0.14);
}
.app-header {
box-sizing: border-box;
width: min(var(--app-shell-width), 100%);
margin: 0 auto 12px;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 18px;
padding: 22px 24px 18px;
border: 1px solid rgba(217, 227, 238, 0.9);
border-radius: 28px;
background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(248,251,255,0.84));
box-shadow: var(--app-shadow);
}
.app-header-in-shell {
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 22px 24px 18px;
border: 0;
border-bottom: 1px solid rgba(217, 227, 238, 0.9);
border-radius: 0;
background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(248,251,255,0.84));
box-shadow: none;
}
.app-brand {
display: inline-flex;
min-width: 0;
align-items: center;
text-decoration: none;
}
.app-brand-logo {
width: 186px;
max-width: 100%;
height: auto;
display: block;
}
.app-header-actions {
display: flex;
margin-left: auto;
flex: 0 0 auto;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
align-items: center;
}
.app-lang-switch {
display: flex;
gap: 6px;
}
.app-lang-btn {
border: 1px solid var(--app-line);
background: #f8fbff;
color: #1f3a5f;
border-radius: 999px;
padding: 6px 10px;
font-size: 12px;
font-weight: 700;
cursor: pointer;
}
.app-lang-btn.active {
background: var(--app-brand-blue);
border-color: var(--app-brand-blue);
color: #fff;
}
.shell,
.wrap,
.top-wrap {
width: min(var(--app-shell-width), 100%) !important;
max-width: none !important;
margin-left: auto !important;
margin-right: auto !important;
}
@media (max-width: 900px) {
.app-header,
.app-header-in-shell {
flex-direction: column;
align-items: stretch;
}
.app-header-actions {
justify-content: flex-start;
}
}

View File

@@ -7,10 +7,12 @@ body {
radial-gradient(900px 520px at 92% 0%, #eef4ff, transparent), radial-gradient(900px 520px at 92% 0%, #eef4ff, transparent),
#edf2fb; #edf2fb;
} }
.wrap { max-width: 920px; margin: 0 auto; } .wrap { width: min(var(--app-shell-width), 100%); margin: 0 auto; background: #ffffff; border: 1px solid #d8e1ee; border-radius: 20px; box-shadow: 0 20px 44px rgba(16, 32, 57, 0.13); overflow: hidden; }
.wrap-body { padding: 18px; }
.brand-logo { width: 180px; max-width: 100%; height: auto; margin: 0 0 10px; display: block; } .brand-logo { width: 180px; max-width: 100%; height: auto; margin: 0 0 10px; display: block; }
.top-link { margin-bottom: 10px; } .top-link { margin-bottom: 10px; }
.card { background: linear-gradient(180deg, #ffffff, #fbfcff); border: 1px solid #d9dcf3; border-radius: 14px; padding: 18px; margin-bottom: 14px; box-shadow: 0 10px 24px rgba(0, 0, 120, 0.08); } .card { background: linear-gradient(180deg, #ffffff, #fbfcff); border: 1px solid #d9dcf3; border-radius: 14px; padding: 18px; margin-bottom: 14px; box-shadow: 0 10px 24px rgba(0, 0, 120, 0.08); }
.wrap-body .card:last-child { margin-bottom: 0; }
h1 { margin-top: 0; color: #000078; } h1 { margin-top: 0; color: #000078; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.field { margin-bottom: 12px; } .field { margin-bottom: 12px; }

View File

@@ -25,15 +25,24 @@ body {
} }
.shell { .shell {
max-width: 1120px; width: min(var(--app-shell-width), 100%);
margin: 0 auto; margin: 0 auto;
background: var(--card);
border: 1px solid var(--line);
border-radius: 20px;
box-shadow: 0 20px 44px rgba(16, 32, 57, 0.13);
overflow: hidden;
}
.shell-body {
display: grid; display: grid;
grid-template-columns: 290px 1fr; grid-template-columns: 290px 1fr;
gap: 16px; gap: 16px;
padding: 18px;
} }
.top-wrap { .top-wrap {
max-width: 1120px; width: min(var(--app-shell-width), 100%);
margin: 0 auto 10px; margin: 0 auto 10px;
} }
@@ -172,7 +181,9 @@ h1 {
} }
.section-itsetup { .section-itsetup {
background: linear-gradient(160deg, #ffffff, #f7faff); background: linear-gradient(180deg, #ffffff 0%, #f9fbff 100%);
border-color: #d5e2f9;
box-shadow: 0 10px 24px rgba(15, 42, 84, 0.08);
} }
.section-abschluss { .section-abschluss {
@@ -198,6 +209,23 @@ h1 {
font-size: 13px; font-size: 13px;
} }
.section-itsetup .section-head {
margin: -14px -14px 16px;
padding: 12px 14px;
border-bottom: 1px solid #d5e2f9;
background: #eef4ff;
border-top-left-radius: 14px;
border-top-right-radius: 14px;
}
.section-itsetup .section-head h2 {
color: #1f376b;
}
.section-itsetup .section-head p {
color: #5e7088;
}
.grid-2 { .grid-2 {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@@ -224,6 +252,107 @@ h1 {
margin-bottom: 0; margin-bottom: 0;
} }
.section-itsetup .field-group {
border: 1px solid #d7e0ea;
border-radius: 14px;
background: #ffffff;
padding: 14px;
}
.section-itsetup .field-group.hidden {
display: none;
}
.section-itsetup .field:not(.field-full) {
border: 1px solid #d7e0ea;
border-radius: 14px;
background: #ffffff;
padding: 12px;
min-height: 118px;
display: flex;
flex-direction: column;
box-shadow: 0 4px 14px rgba(17, 52, 95, 0.04);
}
.section-itsetup .field:not(.field-full) input,
.section-itsetup .field:not(.field-full) select,
.section-itsetup .field:not(.field-full) textarea {
margin-top: auto;
}
.section-itsetup .field.field-full:not(.checkbox-list):not(.inline-check) {
border: 1px solid #d7e0ea;
border-radius: 14px;
background: #ffffff;
padding: 12px;
box-shadow: 0 4px 14px rgba(17, 52, 95, 0.04);
}
.itsetup-checklist-panel {
border: 1px solid #d7e0ea;
border-radius: 12px;
overflow: hidden;
background: #ffffff;
padding: 0;
box-shadow: 0 4px 10px rgba(17, 52, 95, 0.04);
}
.itsetup-checklist-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 12px 14px;
background: linear-gradient(180deg, #edf3ff, #e5eeff);
border-bottom: 1px solid #cad8f3;
}
.itsetup-checklist-head h3 {
margin: 0;
font-size: 21px;
line-height: 1.2;
font-weight: 700;
color: #1f376b;
}
.checklist-toggle-btn {
appearance: none;
border: 1px solid #6d86ca;
background: linear-gradient(180deg, #ffffff, #e9f0ff);
color: #14315f;
border-radius: 10px;
padding: 8px 12px;
font: inherit;
font-size: 12px;
font-weight: 700;
cursor: pointer;
line-height: 1;
min-height: 36px;
white-space: nowrap;
box-shadow: 0 4px 10px rgba(23, 54, 109, 0.10);
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.08s ease;
}
.checklist-toggle-btn:hover {
background: linear-gradient(180deg, #ffffff, #dfe9ff);
border-color: #5d79c3;
box-shadow: 0 6px 14px rgba(23, 54, 109, 0.14);
}
.checklist-toggle-btn:focus-visible {
outline: 3px solid rgba(0, 0, 120, 0.16);
outline-offset: 2px;
}
.checklist-toggle-btn:active {
transform: translateY(1px);
}
.itsetup-checklist-body {
padding: 0;
background: #ffffff;
}
label { label {
display: block; display: block;
font-weight: 600; font-weight: 600;
@@ -305,6 +434,95 @@ select:focus {
break-inside: avoid; break-inside: avoid;
} }
.itsetup-checklist-body > div {
margin: 0;
padding: 0;
border: 0;
border-radius: 0;
background: #ffffff;
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 0;
row-gap: 0;
}
.itsetup-checklist-body > [id^="id_"] {
display: grid !important;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
column-gap: 0 !important;
row-gap: 0 !important;
}
.itsetup-checklist-body > [id^="id_"].cols-3 {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) !important;
}
.itsetup-checklist-body > div > div {
margin: 0;
border-bottom: 1px solid #e6edf7;
min-width: 0;
}
.itsetup-checklist-body > div > div label {
display: flex;
align-items: flex-start;
gap: 10px;
margin: 0;
padding: 8px 10px;
border: 0;
border-radius: 0;
background: #ffffff;
font-weight: 500;
font-size: 14px;
line-height: 1.25;
min-height: 100%;
min-width: 0;
overflow-wrap: anywhere;
}
.itsetup-checklist-body > div > div:nth-child(odd) {
border-right: 1px solid #e6edf7;
}
.itsetup-checklist-body > [id^="id_"].cols-3 > div:nth-child(odd) {
border-right: 0;
}
.itsetup-checklist-body > [id^="id_"].cols-3 > div:not(:nth-child(3n)) {
border-right: 1px solid #e6edf7;
}
.itsetup-checklist-body > div > div:last-child,
.itsetup-checklist-body > div > div:nth-last-child(2):nth-child(odd) {
border-bottom: 0;
}
.itsetup-checklist-body > [id^="id_"].cols-3 > div:last-child,
.itsetup-checklist-body > [id^="id_"].cols-3 > div:nth-last-child(2):nth-child(3n-1),
.itsetup-checklist-body > [id^="id_"].cols-3 > div:nth-last-child(3):nth-child(3n-2) {
border-bottom: 0;
}
.itsetup-checklist-body > div > div input[type="checkbox"] {
width: 16px;
height: 16px;
min-height: auto;
margin: 1px 0 0;
padding: 0;
accent-color: var(--brand);
box-shadow: none;
}
.itsetup-checklist-panel .hint,
.itsetup-checklist-panel .errorlist {
margin: 0;
padding: 10px 14px 12px;
}
.field-group .grid-2 > .field:only-child {
grid-column: 1 / -1;
}
.hint { .hint {
color: var(--muted); color: var(--muted);
font-size: 12px; font-size: 12px;

View File

@@ -6,10 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Anmeldung" %}</title> <title>{% trans "Anmeldung" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; min-height: 100vh; display: grid; place-items: center; background: linear-gradient(160deg, #eef6ff, #fff3f3); } body { margin: 0; font-family: Arial, sans-serif; min-height: 100vh; background: linear-gradient(160deg, #eef6ff, #fff3f3); padding: 24px; }
.card { width: min(420px, calc(100% - 28px)); background: #fff; border: 1px solid #d9e3f0; border-radius: 14px; padding: 20px; box-shadow: 0 12px 30px rgba(28, 45, 79, 0.12); } .card { width: min(420px, calc(100% - 28px)); margin: 0 auto; background: #fff; border: 1px solid #d9e3f0; border-radius: 14px; padding: 20px; box-shadow: 0 12px 30px rgba(28, 45, 79, 0.12); }
.logo { width: 190px; max-width: 100%; height: auto; display: block; margin-bottom: 12px; }
h1 { margin: 0 0 8px; font-size: 24px; } h1 { margin: 0 0 8px; font-size: 24px; }
p { margin: 0 0 14px; color: #607086; } p { margin: 0 0 14px; color: #607086; }
.field { margin-bottom: 12px; } .field { margin-bottom: 12px; }
@@ -17,14 +17,11 @@
input { width: 100%; padding: 10px; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; } input { width: 100%; padding: 10px; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; }
.btn { width: 100%; } .btn { width: 100%; }
.errorlist { color: #b91c1c; margin: 6px 0; } .errorlist { color: #b91c1c; margin: 6px 0; }
.card-head { display:block; }
</style> </style>
</head> </head>
<body> <body>
{% include 'workflows/includes/app_header.html' with header_show_home=0 %}
<div class="card"> <div class="card">
<div class="card-head">
<img class="logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
</div>
<h1>{% trans "Anmeldung" %}</h1> <h1>{% trans "Anmeldung" %}</h1>
<p>{% trans "Bitte melden Sie sich mit Ihrem Benutzerkonto an." %}</p> <p>{% trans "Bitte melden Sie sich mit Ihrem Benutzerkonto an." %}</p>

View File

@@ -0,0 +1,22 @@
{% load static i18n %}
{% get_current_language as CURRENT_LANGUAGE %}
<!doctype html>
<html lang="{{ CURRENT_LANGUAGE }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
{% block extra_css %}{% endblock %}
{% block extra_head %}{% endblock %}
</head>
<body{% block body_attrs %}{% endblock %}>
{% block pre_shell %}{% endblock %}
<div class="{% block shell_class %}shell{% endblock %}">
{% block shell_header %}{% endblock %}
{% block shell_body %}{% endblock %}
</div>
{% block extra_scripts %}{% endblock %}
</body>
</html>

View File

@@ -1,11 +1,9 @@
{% extends 'workflows/base_shell.html' %}
{% load static %} {% load static %}
<!doctype html>
<html lang="en"> {% block title %}Developer Handbook{% endblock %}
<head>
<meta charset="utf-8" /> {% block extra_head %}
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Developer Handbook</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; }
.shell { max-width: 1120px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; } .shell { max-width: 1120px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; }
@@ -24,15 +22,14 @@
.box { border: 1px solid #d7e0ea; border-radius: 10px; padding: 10px; background: #fcfdff; margin: 8px 0 12px; } .box { border: 1px solid #d7e0ea; border-radius: 10px; padding: 10px; background: #fcfdff; margin: 8px 0 12px; }
.note { border-left: 4px solid #000078; padding: 8px 10px; background: #f4f8ff; margin: 10px 0; } .note { border-left: 4px solid #000078; padding: 8px 10px; background: #f4f8ff; margin: 10px 0; }
</style> </style>
</head> {% endblock %}
<body>
<div class="shell"> {% block shell_body %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /> {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %}
<div class="top"> <div class="top">
<h1>Developer Handbook</h1> <h1>Developer Handbook</h1>
<div style="display:flex; gap:8px; flex-wrap:wrap;"> <div style="display:flex; gap:8px; flex-wrap:wrap;">
<a class="btn btn-secondary" href="/admin-tools/wiki/">Project Wiki</a> <a class="btn btn-secondary" href="/admin-tools/wiki/">Project Wiki</a>
<a class="btn btn-secondary" href="/">Back to Home</a>
</div> </div>
</div> </div>
<p class="sub">Engineering runbook for development, deployment, maintenance, and extension of the TUBCO Onboarding & Offboarding Portal.</p> <p class="sub">Engineering runbook for development, deployment, maintenance, and extension of the TUBCO Onboarding & Offboarding Portal.</p>
@@ -68,6 +65,7 @@
<ul> <ul>
<li><code>/backend/config/</code>: Django settings, WSGI, URL config</li> <li><code>/backend/config/</code>: Django settings, WSGI, URL config</li>
<li><code>/backend/workflows/</code>: application logic, views, models, tasks, templates, static assets</li> <li><code>/backend/workflows/</code>: application logic, views, models, tasks, templates, static assets</li>
<li><code>/backend/workflows/templates/workflows/base_shell.html</code>: standard page shell for new staff-facing pages</li>
<li><code>/backend/media/templates/</code>: PDF HTML templates and letterhead source files</li> <li><code>/backend/media/templates/</code>: PDF HTML templates and letterhead source files</li>
<li><code>/backend/media/pdfs/</code>: generated PDF outputs on host volume</li> <li><code>/backend/media/pdfs/</code>: generated PDF outputs on host volume</li>
<li><code>/backend/locale/</code>: translation catalogs</li> <li><code>/backend/locale/</code>: translation catalogs</li>
@@ -193,6 +191,7 @@ docker compose exec -T web python manage.py run_staging_e2e_check</code></pre>
<ul> <ul>
<li>Use <code>check</code> after model/view/template changes.</li> <li>Use <code>check</code> after model/view/template changes.</li>
<li>Use targeted shell checks for render validation when changing templates or routes.</li> <li>Use targeted shell checks for render validation when changing templates or routes.</li>
<li>New pages should extend <code>base_shell.html</code> and keep header/frame logic out of page-local templates.</li>
<li>Use real PDF generation tests when changing PDF templates or intro/offboarding document logic.</li> <li>Use real PDF generation tests when changing PDF templates or intro/offboarding document logic.</li>
<li>Use the dedicated Release Checklist page as the final go/no-go runbook before shipping changes.</li> <li>Use the dedicated Release Checklist page as the final go/no-go runbook before shipping changes.</li>
<li>The automated bilingual smoke tests now cover DE/EN request language capture and English email-template rendering.</li> <li>The automated bilingual smoke tests now cover DE/EN request language capture and English email-template rendering.</li>
@@ -230,6 +229,5 @@ docker compose exec -T web python manage.py run_staging_e2e_check</code></pre>
<li>Prefer standard framework workflows over custom one-off maintenance scripts.</li> <li>Prefer standard framework workflows over custom one-off maintenance scripts.</li>
<li>When adding new features, document them in both the Project Wiki and this handbook if they change engineering workflow.</li> <li>When adding new features, document them in both the Project Wiki and this handbook if they change engineering workflow.</li>
</ul> </ul>
</div>
</body> {% endblock %}
</html>

View File

@@ -6,14 +6,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Form Builder" %}</title> <title>{% trans "Form Builder" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/form_builder.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/form_builder.css' %}" />
</head> </head>
<body> <body>
<div class="shell"> <div class="shell">
<div class="topbar"> {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
</div>
<header class="header"> <header class="header">
<h1>{% trans "Form Builder" %}</h1> <h1>{% trans "Form Builder" %}</h1>

View File

@@ -1,11 +1,9 @@
{% extends 'workflows/base_shell.html' %}
{% load static i18n %} {% load static i18n %}
<!doctype html>
<html lang="en"> {% block title %}{% trans "Handbook" %}{% endblock %}
<head>
<meta charset="utf-8" /> {% block extra_head %}
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Handbook" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; }
.shell { max-width: 1120px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; } .shell { max-width: 1120px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; }
@@ -23,13 +21,12 @@
.actions { display: flex; gap: 8px; flex-wrap: wrap; } .actions { display: flex; gap: 8px; flex-wrap: wrap; }
@media (max-width: 760px) { .grid { grid-template-columns: 1fr; } } @media (max-width: 760px) { .grid { grid-template-columns: 1fr; } }
</style> </style>
</head> {% endblock %}
<body>
<div class="shell"> {% block shell_body %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /> {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %}
<div class="top"> <div class="top">
<h1>{% trans "Handbook" %}</h1> <h1>{% trans "Handbook" %}</h1>
<a class="btn btn-secondary" href="/">{% trans "Back to Home" %}</a>
</div> </div>
<p class="sub">{% trans "Single documentation entry point for both operational knowledge and long-term engineering knowledge." %}</p> <p class="sub">{% trans "Single documentation entry point for both operational knowledge and long-term engineering knowledge." %}</p>
@@ -79,6 +76,5 @@
</div> </div>
</section> </section>
</div> </div>
</div>
</body> {% endblock %}
</html>

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "TUBCO Onboarding & Offboarding Portal" %}</title> <title>{% trans "TUBCO Onboarding & Offboarding Portal" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
:root { :root {
--brand-blue: #000078; --brand-blue: #000078;
@@ -446,7 +447,7 @@
<div class="shell"> <div class="shell">
<div class="topbar"> <div class="topbar">
<div class="brand-wrap"> <div class="brand-wrap">
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /> <a class="app-brand" href="/"><img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /></a>
</div> </div>
<div class="quick-actions"> <div class="quick-actions">
<form method="post" action="{% url 'set_language' %}" class="lang-switch"> <form method="post" action="{% url 'set_language' %}" class="lang-switch">

View File

@@ -0,0 +1,23 @@
{% load static i18n %}
{% get_current_language as CURRENT_LANGUAGE %}
<div class="app-header{% if header_inside_shell %} app-header-in-shell{% endif %}">
<a class="app-brand" href="/" aria-label="{% trans 'Zur Startseite' %}">
<img class="app-brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
</a>
<div class="app-header-actions">
{% if header_show_lang %}
<form method="post" action="{% url 'set_language' %}" class="app-lang-switch">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
<button class="app-lang-btn {% if CURRENT_LANGUAGE == 'de' %}active{% endif %}" type="submit" name="language" value="de">DE</button>
<button class="app-lang-btn {% if CURRENT_LANGUAGE == 'en' %}active{% endif %}" type="submit" name="language" value="en">EN</button>
</form>
{% endif %}
{% if header_show_dashboard %}
<a class="btn btn-secondary" href="/requests/">{% trans "Zum Dashboard" %}</a>
{% endif %}
{% if header_show_home %}
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
{% endif %}
</div>
</div>

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Integrationen Setup" %}</title> <title>{% trans "Integrationen Setup" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #0f172a; padding: 20px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #0f172a; padding: 20px; }
.shell { max-width: 980px; margin: 0 auto; background: #fff; border: 1px solid #d8e3f0; border-radius: 14px; padding: 16px; } .shell { max-width: 980px; margin: 0 auto; background: #fff; border: 1px solid #d8e3f0; border-radius: 14px; padding: 16px; }
@@ -72,10 +73,7 @@
</head> </head>
<body> <body>
<div class="shell"> <div class="shell">
<div class="topbar"> {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
</div>
<h1>{% trans "Integrationen Setup" %}</h1> <h1>{% trans "Integrationen Setup" %}</h1>
<p class="sub">{% trans "Verwalten Sie Nextcloud- und Mail-Konfiguration ohne Backend-Wechsel." %}</p> <p class="sub">{% trans "Verwalten Sie Nextcloud- und Mail-Konfiguration ohne Backend-Wechsel." %}</p>

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Einweisungs-Builder" %}</title> <title>{% trans "Einweisungs-Builder" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; }
.shell { max-width: 1180px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; } .shell { max-width: 1180px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; }
@@ -34,10 +35,7 @@
</head> </head>
<body> <body>
<div class="shell"> <div class="shell">
<div class="topbar"> {% include 'workflows/includes/app_header.html' with header_show_dashboard=1 header_show_home=1 header_inside_shell=1 %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
</div>
<div class="toolbar"> <div class="toolbar">
<div> <div>

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Offboarding-Anfrage" %}</title> <title>{% trans "Offboarding-Anfrage" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/offboarding_form.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/offboarding_form.css' %}" />
</head> </head>
<body> <body>
@@ -19,15 +20,9 @@
</div> </div>
<div class="wrap"> <div class="wrap">
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /> {% include 'workflows/includes/app_header.html' with header_show_lang=1 header_show_home=1 header_inside_shell=1 %}
<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;"> <div class="wrap-body">
{% 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"> <div class="card">
<h1>{% trans "Offboarding-Anfrage" %}</h1> <h1>{% trans "Offboarding-Anfrage" %}</h1>
<form method="get" action="/offboarding/new/"> <form method="get" action="/offboarding/new/">
@@ -70,6 +65,7 @@
</form> </form>
</div> </div>
</div> </div>
</div>
<script> <script>
(function () { (function () {

View File

@@ -1,27 +1,39 @@
{% load static i18n %}
<!doctype html> <!doctype html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Offboarding gespeichert</title> <title>{% trans "Offboarding gespeichert" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
body { font-family: Arial, sans-serif; margin: 24px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 24px; }
code { background: #f4f4f4; padding: 2px 6px; } .shell { background: #fff; border: 1px solid #d7e0ea; border-radius: 16px; padding: 20px; box-shadow: 0 16px 36px rgba(18,34,56,0.10); }
h1 { margin: 0 0 10px; color: #000078; }
p { margin: 0 0 10px; }
code { background: #f4f6fa; padding: 2px 6px; border-radius: 6px; }
.actions { display:flex; gap:8px; flex-wrap:wrap; margin-top: 14px; }
</style> </style>
</head> </head>
<body> <body>
<h1>Offboarding gespeichert</h1> {% include 'workflows/includes/app_header.html' with header_show_home=1 %}
<p>Vorgangs-ID: <code>{{ obj.id }}</code></p> <div class="shell">
<p>Name: <code>{{ obj.full_name }}</code></p> <h1>{% trans "Offboarding gespeichert" %}</h1>
<p>E-Mail: <code>{{ obj.work_email }}</code></p> <p>{% trans "Vorgangs-ID:" %} <code>{{ obj.id }}</code></p>
<p>Letzter Arbeitstag: <code>{{ obj.last_working_day }}</code></p> <p>{% trans "Name:" %} <code>{{ obj.full_name }}</code></p>
<p>{% trans "E-Mail:" %} <code>{{ obj.work_email }}</code></p>
<p>{% trans "Letzter Arbeitstag:" %} <code>{{ obj.last_working_day }}</code></p>
{% if pdf_url %} {% if pdf_url %}
<p>PDF: <a href="{{ pdf_url }}" target="_blank" rel="noopener">PDF öffnen</a></p> <p>{% trans "PDF:" %} <a href="{{ pdf_url }}" target="_blank" rel="noopener">{% trans "PDF öffnen" %}</a></p>
<p>Datei: <code>{{ obj.generated_pdf_path }}</code></p> <p>{% trans "Datei:" %} <code>{{ obj.generated_pdf_path }}</code></p>
{% else %} {% else %}
<p>PDF wird im Hintergrund erstellt.</p> <p>{% trans "PDF wird im Hintergrund erstellt." %}</p>
{% endif %} {% endif %}
<p><a href="/">Zur Startseite</a></p> <div class="actions">
<p><a href="/offboarding/new/">Neue Offboarding-Anfrage erfassen</a></p> <a class="btn btn-secondary" href="/offboarding/new/">{% trans "Neue Offboarding-Anfrage erfassen" %}</a>
<a class="btn btn-secondary" href="/requests/">{% trans "Zum Dashboard" %}</a>
</div>
</div>
</body> </body>
</html> </html>

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Onboarding-Anfrage" %}</title> <title>{% trans "Onboarding-Anfrage" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/onboarding_form.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/onboarding_form.css' %}" />
</head> </head>
<body> <body>
@@ -18,20 +19,10 @@
</div> </div>
</div> </div>
<div class="top-wrap">
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
<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"> <div class="shell">
{% include 'workflows/includes/app_header.html' with header_show_lang=1 header_show_home=1 header_inside_shell=1 %}
<div class="shell-body">
<aside class="panel"> <aside class="panel">
<h1>{% trans "Onboarding" %}</h1> <h1>{% trans "Onboarding" %}</h1>
<p class="sub">{% trans "Mehrseitiges Formular mit konfigurierbaren Feldern aus dem Admin." %}</p> <p class="sub">{% trans "Mehrseitiges Formular mit konfigurierbaren Feldern aus dem Admin." %}</p>
@@ -75,8 +66,26 @@
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %} {% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
{{ field.errors }} {{ field.errors }}
</div> </div>
{% elif section.key == 'itsetup' and field.name in onboarding_checkbox_lists %}
<div class="itsetup-checklist-panel field-full">
<div class="itsetup-checklist-head">
<h3>{{ field.label }}</h3>
<button
type="button"
class="checklist-toggle-btn"
data-checklist-toggle
data-label-select="{% trans 'Alle auswählen' %}"
data-label-clear="{% trans 'Auswahl aufheben' %}"
>{% trans "Alle auswählen" %}</button>
</div>
<div class="itsetup-checklist-body">
{{ field }}
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</div>
</div>
{% else %} {% else %}
<div class="field {% if section.key == 'abschluss' %}finish-field{% endif %} {% if field.name in onboarding_checkbox_lists or field.name == 'gender' %}field-full{% endif %} {% if field.name in onboarding_checkbox_lists %}checkbox-list{% endif %}"> <div class="field {% if section.key == 'abschluss' %}finish-field{% endif %} {% if field.name in onboarding_checkbox_lists or field.name == 'gender' or field.name == 'additional_hardware_needed_choice' or field.name == 'additional_software_needed_choice' or field.name == 'additional_access_needed_choice' or field.name == 'successor_required_choice' %}field-full{% endif %} {% if field.name in onboarding_checkbox_lists %}checkbox-list{% endif %} {% if section.key == 'itsetup' and field.name in onboarding_checkbox_lists %}itsetup-checklist-block{% endif %}">
{{ field.label_tag }} {{ field.label_tag }}
{{ field }} {{ field }}
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %} {% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
@@ -96,8 +105,26 @@
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %} {% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
{{ field.errors }} {{ field.errors }}
</div> </div>
{% elif section.key == 'itsetup' and field.name in onboarding_checkbox_lists %}
<div class="itsetup-checklist-panel field-full">
<div class="itsetup-checklist-head">
<h3>{{ field.label }}</h3>
<button
type="button"
class="checklist-toggle-btn"
data-checklist-toggle
data-label-select="{% trans 'Alle auswählen' %}"
data-label-clear="{% trans 'Auswahl aufheben' %}"
>{% trans "Alle auswählen" %}</button>
</div>
<div class="itsetup-checklist-body">
{{ field }}
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
{{ field.errors }}
</div>
</div>
{% else %} {% else %}
<div class="field {% if section.key == 'abschluss' %}finish-field{% endif %} {% if field.name in onboarding_checkbox_lists or field.name == 'gender' %}field-full{% endif %} {% if field.name in onboarding_checkbox_lists %}checkbox-list{% endif %}"> <div class="field {% if section.key == 'abschluss' %}finish-field{% endif %} {% if field.name in onboarding_checkbox_lists or field.name == 'gender' or field.name == 'additional_hardware_needed_choice' or field.name == 'additional_software_needed_choice' or field.name == 'additional_access_needed_choice' or field.name == 'successor_required_choice' %}field-full{% endif %} {% if field.name in onboarding_checkbox_lists %}checkbox-list{% endif %} {% if section.key == 'itsetup' and field.name in onboarding_checkbox_lists %}itsetup-checklist-block{% endif %}">
{{ field.label_tag }} {{ field.label_tag }}
{{ field }} {{ field }}
{% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %} {% if field.help_text %}<div class="hint">{{ field.help_text }}</div>{% endif %}
@@ -134,6 +161,7 @@
</form> </form>
</main> </main>
</div> </div>
</div>
<script> <script>
(function () { (function () {
@@ -301,6 +329,48 @@
// Manual-only behavior: fill only when button is clicked. // Manual-only behavior: fill only when button is clicked.
} }
function setupChecklistToggles() {
document.querySelectorAll('[data-checklist-toggle]').forEach(function (button) {
const panel = button.closest('.itsetup-checklist-panel');
if (!panel) return;
const getCheckboxes = function () {
return Array.from(panel.querySelectorAll('input[type="checkbox"]'));
};
const refreshButtonLabel = function () {
const checkboxes = getCheckboxes();
if (!checkboxes.length) return;
const allChecked = checkboxes.every(function (box) { return box.checked; });
button.textContent = allChecked ? (button.dataset.labelClear || 'Auswahl aufheben') : (button.dataset.labelSelect || 'Alle auswählen');
};
button.addEventListener('click', function () {
const checkboxes = getCheckboxes();
if (!checkboxes.length) return;
const shouldCheck = checkboxes.some(function (box) { return !box.checked; });
checkboxes.forEach(function (box) {
box.checked = shouldCheck;
box.dispatchEvent(new Event('change', { bubbles: true }));
});
refreshButtonLabel();
});
getCheckboxes().forEach(function (box) {
box.addEventListener('change', refreshButtonLabel);
});
refreshButtonLabel();
});
}
function setupChecklistColumns() {
document.querySelectorAll('.itsetup-checklist-body > [id^="id_"]').forEach(function (container) {
const itemCount = container.querySelectorAll(':scope > div').length;
container.classList.toggle('cols-3', itemCount > 8);
});
}
function updateStep() { function updateStep() {
pages.forEach((p, i) => p.classList.toggle('active', i === current)); pages.forEach((p, i) => p.classList.toggle('active', i === current));
navItems.forEach((n, i) => n.classList.toggle('active', i === current)); navItems.forEach((n, i) => n.classList.toggle('active', i === current));
@@ -342,6 +412,8 @@
syncConditionals(); syncConditionals();
setupWorkEmailAutofill(); setupWorkEmailAutofill();
setupBusinessCardAutofill(); setupBusinessCardAutofill();
setupChecklistToggles();
setupChecklistColumns();
jumpToFirstErrorPage(); jumpToFirstErrorPage();
updateStep(); updateStep();
})(); })();

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Einweisung durchführen" %}</title> <title>{% trans "Einweisung durchführen" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
:root { :root {
--brand-blue: #000078; --brand-blue: #000078;
@@ -22,10 +23,7 @@
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", Arial, sans-serif; background: linear-gradient(180deg, #eef4ff, #f8fbff); color: var(--ink); padding: 24px; } body { margin: 0; font-family: "Segoe UI", Arial, sans-serif; background: linear-gradient(180deg, #eef4ff, #f8fbff); color: var(--ink); padding: 24px; }
.shell { max-width: 1180px; margin: 0 auto; background: var(--panel); border: 1px solid var(--line); border-radius: 18px; box-shadow: 0 18px 42px rgba(16,32,57,.10); overflow: hidden; } .shell { width: min(var(--app-shell-width), 100%); margin: 0 auto; background: var(--panel); border: 1px solid var(--line); border-radius: 28px; box-shadow: 0 22px 48px rgba(18,34,56,.14); overflow: hidden; }
.topbar { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; padding: 18px 22px; border-bottom: 1px solid var(--line); background: #fff; }
.brand-logo { width: 210px; max-width: 100%; height: auto; display: block; }
.top-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.hero { padding: 20px 22px 18px; border-bottom: 1px solid var(--line); background: linear-gradient(135deg, rgba(0,0,120,.06), rgba(0,0,120,0) 48%), linear-gradient(180deg, #ffffff, #f8fbff); } .hero { padding: 20px 22px 18px; border-bottom: 1px solid var(--line); background: linear-gradient(135deg, rgba(0,0,120,.06), rgba(0,0,120,0) 48%), linear-gradient(180deg, #ffffff, #f8fbff); }
.hero h1 { margin: 0; font-size: 32px; line-height: 1.08; color: var(--brand-blue); } .hero h1 { margin: 0; font-size: 32px; line-height: 1.08; color: var(--brand-blue); }
.sub { margin: 8px 0 0; color: var(--muted); max-width: 780px; } .sub { margin: 8px 0 0; color: var(--muted); max-width: 780px; }
@@ -55,18 +53,12 @@
textarea { width: 100%; min-height: 150px; border: 1px solid #cfd9e8; border-radius: 10px; padding: 11px 12px; font: inherit; resize: vertical; } textarea { width: 100%; min-height: 150px; border: 1px solid #cfd9e8; border-radius: 10px; padding: 11px 12px; font: inherit; resize: vertical; }
.help { color: var(--muted); font-size: 13px; margin-top: 8px; } .help { color: var(--muted); font-size: 13px; margin-top: 8px; }
.actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; } .actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
@media (max-width: 900px) { .meta, .items { grid-template-columns: 1fr; } .topbar { flex-direction: column; } .top-actions { justify-content: flex-start; } .meta-grid { grid-template-columns: 1fr; } .item:nth-last-child(-n+2) { border-bottom: 1px solid #eef3f8; } .item:last-child { border-bottom: 0; } } @media (max-width: 900px) { .meta, .items { grid-template-columns: 1fr; } .meta-grid { grid-template-columns: 1fr; } .item:nth-last-child(-n+2) { border-bottom: 1px solid #eef3f8; } .item:last-child { border-bottom: 0; } }
</style> </style>
</head> </head>
<body> <body>
<div class="shell"> <div class="shell">
<div class="topbar"> {% include 'workflows/includes/app_header.html' with header_show_dashboard=1 header_show_home=1 header_inside_shell=1 %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
<div class="top-actions">
<a class="btn btn-secondary" href="/requests/">{% trans "Zum Dashboard" %}</a>
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
</div>
</div>
<div class="hero"> <div class="hero">
<h1>{% trans "Einweisung durchführen" %}</h1> <h1>{% trans "Einweisung durchführen" %}</h1>

View File

@@ -1,26 +1,38 @@
{% load static i18n %}
<!doctype html> <!doctype html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Onboarding gespeichert</title> <title>{% trans "Onboarding gespeichert" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
body { font-family: Arial, sans-serif; margin: 24px; color: #222; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 24px; }
code { background: #f4f4f4; padding: 2px 6px; } .shell { background: #fff; border: 1px solid #d7e0ea; border-radius: 16px; padding: 20px; box-shadow: 0 16px 36px rgba(18,34,56,0.10); }
h1 { margin: 0 0 10px; color: #000078; }
p { margin: 0 0 10px; }
code { background: #f4f6fa; padding: 2px 6px; border-radius: 6px; }
.actions { display:flex; gap:8px; flex-wrap:wrap; margin-top: 14px; }
</style> </style>
</head> </head>
<body> <body>
<h1>Anfrage erfolgreich gespeichert</h1> {% include 'workflows/includes/app_header.html' with header_show_home=1 %}
<p>Vorgangs-ID: <code>{{ obj.id }}</code></p> <div class="shell">
<p>Name: <code>{{ obj.full_name }}</code></p> <h1>{% trans "Anfrage erfolgreich gespeichert" %}</h1>
<p>E-Mail: <code>{{ obj.work_email }}</code></p> <p>{% trans "Vorgangs-ID:" %} <code>{{ obj.id }}</code></p>
<p>{% trans "Name:" %} <code>{{ obj.full_name }}</code></p>
<p>{% trans "E-Mail:" %} <code>{{ obj.work_email }}</code></p>
{% if pdf_url %} {% if pdf_url %}
<p>PDF: <a href="{{ pdf_url }}" target="_blank" rel="noopener">PDF öffnen</a></p> <p>{% trans "PDF:" %} <a href="{{ pdf_url }}" target="_blank" rel="noopener">{% trans "PDF öffnen" %}</a></p>
<p>Datei: <code>{{ obj.generated_pdf_path }}</code></p> <p>{% trans "Datei:" %} <code>{{ obj.generated_pdf_path }}</code></p>
{% else %} {% else %}
<p>PDF wird im Hintergrund erstellt.</p> <p>{% trans "PDF wird im Hintergrund erstellt." %}</p>
{% endif %} {% endif %}
<p><a href="/">Zur Startseite</a></p> <div class="actions">
<p><a href="/onboarding/new/">Neue Anfrage erfassen</a></p> <a class="btn btn-secondary" href="/onboarding/new/">{% trans "Neue Anfrage erfassen" %}</a>
<a class="btn btn-secondary" href="/requests/">{% trans "Zum Dashboard" %}</a>
</div>
</div>
</body> </body>
</html> </html>

View File

@@ -1,11 +1,9 @@
{% extends 'workflows/base_shell.html' %}
{% load static %} {% load static %}
<!doctype html>
<html lang="en"> {% block title %}Project Wiki{% endblock %}
<head>
<meta charset="utf-8" /> {% block extra_head %}
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Project Wiki</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; }
.shell { max-width: 1120px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; } .shell { max-width: 1120px; margin: 0 auto; background: #fff; border: 1px solid #d7e0ea; border-radius: 14px; padding: 18px; }
@@ -23,13 +21,12 @@
.box { border: 1px solid #d7e0ea; border-radius: 10px; padding: 10px; background: #fcfdff; margin: 8px 0 12px; } .box { border: 1px solid #d7e0ea; border-radius: 10px; padding: 10px; background: #fcfdff; margin: 8px 0 12px; }
.note { border-left: 4px solid #000078; padding: 8px 10px; background: #f4f8ff; margin: 10px 0; } .note { border-left: 4px solid #000078; padding: 8px 10px; background: #f4f8ff; margin: 10px 0; }
</style> </style>
</head> {% endblock %}
<body>
<div class="shell"> {% block shell_body %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /> {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %}
<div class="top"> <div class="top">
<h1>Project Wiki</h1> <h1>Project Wiki</h1>
<a class="btn btn-secondary" href="/">Back to Home</a>
</div> </div>
<p class="sub">Operational and technical documentation for the Onboarding/Offboarding platform.</p> <p class="sub">Operational and technical documentation for the Onboarding/Offboarding platform.</p>
@@ -296,6 +293,5 @@
<div class="note"> <div class="note">
Last updated for current system behavior as of March 19, 2026. Last updated for current system behavior as of March 19, 2026.
</div> </div>
</div>
</body> {% endblock %}
</html>

View File

@@ -1,11 +1,9 @@
{% extends 'workflows/base_shell.html' %}
{% load static i18n %} {% load static i18n %}
<!doctype html>
<html lang="en"> {% block title %}{% trans "Release Checklist" %}{% endblock %}
<head>
<meta charset="utf-8" /> {% block extra_head %}
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Release Checklist" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #1b2b43; padding: 20px; }
.shell { max-width: 1120px; margin: 0 auto; } .shell { max-width: 1120px; margin: 0 auto; }
@@ -28,10 +26,10 @@
.note { margin-top: 16px; padding: 14px 16px; border-radius: 12px; background: #f7faff; border: 1px solid #dfe8f4; color: #355175; } .note { margin-top: 16px; padding: 14px 16px; border-radius: 12px; background: #f7faff; border: 1px solid #dfe8f4; color: #355175; }
@media (max-width: 820px) { .grid { grid-template-columns: 1fr; } } @media (max-width: 820px) { .grid { grid-template-columns: 1fr; } }
</style> </style>
</head> {% endblock %}
<body>
<div class="shell"> {% block shell_body %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /> {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %}
<section class="hero"> <section class="hero">
<div class="eyebrow">{% trans "Release" %}</div> <div class="eyebrow">{% trans "Release" %}</div>
<div class="top"> <div class="top">
@@ -117,6 +115,5 @@ docker compose logs --no-color --tail=200 worker</code></pre>
</div> </div>
<div class="note">{% trans "Project rule: German remains the primary/fallback language. English is secondary. If a release adds new dynamic text, add the German source first and then the English value." %}</div> <div class="note">{% trans "Project rule: German remains the primary/fallback language. English is secondary. If a release adds new dynamic text, add the German source first and then the English value." %}</div>
</div>
</body> {% endblock %}
</html>

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Anfragen Dashboard" %}</title> <title>{% trans "Anfragen Dashboard" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
:root { :root {
--brand-blue: #000078; --brand-blue: #000078;
@@ -877,7 +878,7 @@
<div class="shell"> <div class="shell">
<div class="topbar"> <div class="topbar">
<div class="brand-wrap"> <div class="brand-wrap">
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /> <a class="app-brand" href="/"><img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" /></a>
</div> </div>
<div class="quick-actions"> <div class="quick-actions">
<form method="post" action="{% url 'set_language' %}" class="lang-switch"> <form method="post" action="{% url 'set_language' %}" class="lang-switch">

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% trans "Welcome E-Mails" %}</title> <title>{% trans "Welcome E-Mails" %}</title>
<link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" /> <link rel="stylesheet" href="{% static 'workflows/css/buttons.css' %}" />
<link rel="stylesheet" href="{% static 'workflows/css/app_chrome.css' %}" />
<style> <style>
body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #0f172a; padding: 20px; } body { margin: 0; font-family: Arial, sans-serif; background: #f4f8ff; color: #0f172a; padding: 20px; }
.shell { max-width: 1100px; margin: 0 auto; background: #fff; border: 1px solid #d8e3f0; border-radius: 14px; padding: 16px; } .shell { max-width: 1100px; margin: 0 auto; background: #fff; border: 1px solid #d8e3f0; border-radius: 14px; padding: 16px; }
@@ -43,10 +44,7 @@
</head> </head>
<body> <body>
<div class="shell"> <div class="shell">
<div class="topbar"> {% include 'workflows/includes/app_header.html' with header_show_home=1 header_inside_shell=1 %}
<img class="brand-logo" src="{% static 'workflows/img/tubco-logo.svg' %}" alt="TUB/CO Logo" />
<a class="btn btn-secondary" href="/">{% trans "Zur Startseite" %}</a>
</div>
<h1>{% trans "Geplante Welcome E-Mails" %}</h1> <h1>{% trans "Geplante Welcome E-Mails" %}</h1>
<p class="sub">{% trans "Welcome-Mails konfigurieren und geplante Mails steuern (sofort senden, pausieren, fortsetzen, abbrechen)." %}</p> <p class="sub">{% trans "Welcome-Mails konfigurieren und geplante Mails steuern (sofort senden, pausieren, fortsetzen, abbrechen)." %}</p>