snapshot: preserve shared confirmation modal standardization

This commit is contained in:
Md Bayazid Bostame
2026-03-26 00:33:02 +01:00
parent 37c2cddf41
commit 4549a867f9
17 changed files with 445 additions and 47 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-25 23:12+0000\n" "POT-Creation-Date: 2026-03-25 23:29+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"
@@ -350,11 +350,18 @@ msgstr "Sign in"
msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an." msgid "Bitte melden Sie sich mit Ihrem Benutzerkonto an."
msgstr "Please sign in with your user account." msgstr "Please sign in with your user account."
#: workflows/templates/registration/login.html:25
msgid "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen."
msgstr "Login failed. Please check your credentials."
#: workflows/templates/registration/login.html:29 #: workflows/templates/registration/login.html:29
#, fuzzy
#| msgid "Fehlgeschlagen"
msgid "Anmeldung fehlgeschlagen"
msgstr "Failed"
#: workflows/templates/registration/login.html:30
msgid ""
"Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut."
msgstr ""
#: workflows/templates/registration/login.html:35
msgid "Anmelden" msgid "Anmelden"
msgstr "Sign in" msgstr "Sign in"
@@ -453,6 +460,19 @@ msgstr ""
msgid "Noch keine Audit-Einträge vorhanden." msgid "Noch keine Audit-Einträge vorhanden."
msgstr "No requests available yet." msgstr "No requests available yet."
#: workflows/templates/workflows/base_shell.html:24
msgid "Bitte bestätigen"
msgstr ""
#: workflows/templates/workflows/base_shell.html:28
#: workflows/templates/workflows/welcome_emails.html:138
msgid "Abbrechen"
msgstr "Cancel"
#: workflows/templates/workflows/base_shell.html:29
msgid "Bestätigen"
msgstr ""
#: workflows/templates/workflows/form_builder.html:4 #: workflows/templates/workflows/form_builder.html:4
#: workflows/templates/workflows/form_builder.html:14 #: workflows/templates/workflows/form_builder.html:14
#: workflows/templates/workflows/home.html:134 #: workflows/templates/workflows/home.html:134
@@ -1795,6 +1815,12 @@ msgstr ""
msgid "Bis" msgid "Bis"
msgstr "" msgstr ""
#: workflows/templates/workflows/requests_dashboard.html:176
#, fuzzy
#| msgid "Ausgewählte Welcome-Einträge wirklich löschen?"
msgid "Ausgewählte Einträge wirklich löschen?"
msgstr "Delete the selected welcome entries?"
#: workflows/templates/workflows/requests_dashboard.html:179 #: workflows/templates/workflows/requests_dashboard.html:179
#: workflows/templates/workflows/welcome_emails.html:78 #: workflows/templates/workflows/welcome_emails.html:78
msgid "ausgewählt" msgid "ausgewählt"
@@ -1849,10 +1875,20 @@ msgstr "Not relevant"
msgid "Timeline" msgid "Timeline"
msgstr "" msgstr ""
#: workflows/templates/workflows/requests_dashboard.html:276
msgid "Eintrag erneut verarbeiten?"
msgstr ""
#: workflows/templates/workflows/requests_dashboard.html:278 #: workflows/templates/workflows/requests_dashboard.html:278
msgid "Erneut versuchen" msgid "Erneut versuchen"
msgstr "" msgstr ""
#: workflows/templates/workflows/requests_dashboard.html:281
#, fuzzy
#| msgid "Option wirklich löschen?"
msgid "Eintrag wirklich löschen?"
msgstr "Delete this option?"
#: workflows/templates/workflows/requests_dashboard.html:290 #: workflows/templates/workflows/requests_dashboard.html:290
msgid "Noch keine Vorgänge vorhanden." msgid "Noch keine Vorgänge vorhanden."
msgstr "No requests available yet." msgstr "No requests available yet."
@@ -1939,10 +1975,6 @@ msgstr "Sent at"
msgid "Fortsetzen" msgid "Fortsetzen"
msgstr "Resume" msgstr "Resume"
#: workflows/templates/workflows/welcome_emails.html:138
msgid "Abbrechen"
msgstr "Cancel"
#: workflows/templates/workflows/welcome_emails.html:145 #: workflows/templates/workflows/welcome_emails.html:145
msgid "Keine geplanten Welcome E-Mails vorhanden." msgid "Keine geplanten Welcome E-Mails vorhanden."
msgstr "No scheduled welcome emails available." msgstr "No scheduled welcome emails available."
@@ -2215,6 +2247,9 @@ msgstr "Introduction was saved as completed."
msgid "Einweisung wurde als Entwurf gespeichert." msgid "Einweisung wurde als Entwurf gespeichert."
msgstr "Introduction was saved as draft." msgstr "Introduction was saved as draft."
#~ msgid "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen."
#~ msgstr "Login failed. Please check your credentials."
#, fuzzy #, fuzzy
#~| msgid "Nextcloud speichern" #~| msgid "Nextcloud speichern"
#~ msgid "Nextcloud deaktivieren" #~ msgid "Nextcloud deaktivieren"

View File

@@ -5,10 +5,10 @@ h1 { margin: 12px 0 6px; color: #000078; }
.flash, .msg { border-radius: 10px; padding: 10px 12px; margin: 0 0 12px; border: 1px solid #d6e1ef; background: #f8fbff; color: #1f3a5f; } .flash, .msg { border-radius: 10px; padding: 10px 12px; margin: 0 0 12px; border: 1px solid #d6e1ef; background: #f8fbff; color: #1f3a5f; }
.flash.error, .msg.error { border-color: #fecaca; background: #fff1f2; color: #991b1b; } .flash.error, .msg.error { border-color: #fecaca; background: #fff1f2; color: #991b1b; }
.flash.success { border-color: #bfe6c9; background: #edf9f1; color: #116634; } .flash.success { border-color: #bfe6c9; background: #edf9f1; color: #116634; }
.card { border: 1px solid #d8e3f0; border-radius: 12px; background: #fbfdff; padding: 12px; margin-bottom: 14px; } .card { border: 1px solid #d8e3f0; border-radius: 12px; background: #fbfdff; padding: 12px; margin-bottom: 14px; transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); }
.grid { display: grid; grid-template-columns: repeat(2, minmax(240px, 1fr)); gap: 10px; } .grid { display: grid; grid-template-columns: repeat(2, minmax(240px, 1fr)); gap: 10px; }
label { display: block; margin-bottom: 4px; font-size: 12px; color: #334155; font-weight: 700; } label { display: block; margin-bottom: 4px; font-size: 12px; color: #334155; font-weight: 700; }
input, select, textarea { width: 100%; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; padding: 8px 9px; background: #fff; } input, select, textarea { width: 100%; box-sizing: border-box; border: 1px solid #cbd5e1; border-radius: 8px; padding: 8px 9px; background: #fff; transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1); }
textarea { min-height: 120px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; } textarea { min-height: 120px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }
.actions { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; } .actions { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; }
.toggle-row { margin-top: 10px; display: inline-flex; align-items: center; gap: 10px; padding: 8px 10px 8px 12px; border: 1px solid #d8e3f0; border-radius: 999px; background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.94)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.9), 0 6px 14px rgba(16, 32, 57, 0.05); } .toggle-row { margin-top: 10px; display: inline-flex; align-items: center; gap: 10px; padding: 8px 10px 8px 12px; border: 1px solid #d8e3f0; border-radius: 999px; background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,249,255,0.94)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.9), 0 6px 14px rgba(16, 32, 57, 0.05); }
@@ -24,8 +24,9 @@ textarea { min-height: 120px; font-family: ui-monospace, SFMono-Regular, Menlo,
.hint { margin-top: 6px; color: #64748b; font-size: 12px; } .hint { margin-top: 6px; color: #64748b; font-size: 12px; }
.toolbar { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; } .toolbar { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.switch { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; } .switch { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.switch .tab { border: 1px solid #c9d6e7; border-radius: 999px; padding: 8px 14px; text-decoration: none; color: #1f2f49; font-weight: 700; background: #f6f9ff; } .switch .tab { border: 1px solid #c9d6e7; border-radius: 999px; padding: 8px 14px; text-decoration: none; color: #1f2f49; font-weight: 700; background: #f6f9ff; transition: background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1); }
.switch .tab.active { background: #000078; color: #fff; border-color: #000078; } .switch .tab.active { background: #000078; color: #fff; border-color: #000078; }
.switch .tab:hover { transform: translateY(-1px); box-shadow: 0 8px 16px rgba(16, 32, 57, 0.06); }
.check-row { margin-top: 8px; display: flex; gap: 12px; flex-wrap: wrap; } .check-row { margin-top: 8px; display: flex; gap: 12px; flex-wrap: wrap; }
.check-row label { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 13px; } .check-row label { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 13px; }
.check-row input[type="checkbox"] { width: auto; } .check-row input[type="checkbox"] { width: auto; }

View File

@@ -4,6 +4,10 @@
--app-brand-blue: #000078; --app-brand-blue: #000078;
--app-panel: rgba(255, 255, 255, 0.9); --app-panel: rgba(255, 255, 255, 0.9);
--app-shadow: 0 22px 48px rgba(18, 34, 56, 0.14); --app-shadow: 0 22px 48px rgba(18, 34, 56, 0.14);
--motion-fast: 160ms;
--motion-base: 200ms;
--motion-slow: 260ms;
--motion-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.app-header { .app-header {
@@ -19,6 +23,10 @@
border-radius: 28px; border-radius: 28px;
background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(248,251,255,0.84)); background: linear-gradient(180deg, rgba(255,255,255,0.95), rgba(248,251,255,0.84));
box-shadow: var(--app-shadow); box-shadow: var(--app-shadow);
transition:
box-shadow var(--motion-base) var(--motion-ease),
border-color var(--motion-base) var(--motion-ease),
background-color var(--motion-base) var(--motion-ease);
} }
.app-header-in-shell { .app-header-in-shell {
@@ -38,6 +46,11 @@
min-width: 0; min-width: 0;
align-items: center; align-items: center;
text-decoration: none; text-decoration: none;
transition: transform var(--motion-base) var(--motion-ease), opacity var(--motion-base) var(--motion-ease);
}
.app-brand:hover {
transform: translateY(-1px);
} }
.app-brand-logo { .app-brand-logo {
@@ -45,6 +58,12 @@
max-width: 100%; max-width: 100%;
height: auto; height: auto;
display: block; display: block;
transition: transform var(--motion-base) var(--motion-ease), filter var(--motion-base) var(--motion-ease);
}
.app-brand:hover .app-brand-logo {
transform: translateY(-1px);
filter: saturate(1.02);
} }
.app-header-actions { .app-header-actions {
@@ -71,6 +90,12 @@
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
transition:
background-color var(--motion-fast) var(--motion-ease),
border-color var(--motion-fast) var(--motion-ease),
color var(--motion-fast) var(--motion-ease),
transform var(--motion-fast) var(--motion-ease),
box-shadow var(--motion-fast) var(--motion-ease);
} }
.app-lang-btn.active { .app-lang-btn.active {
@@ -79,6 +104,15 @@
color: #fff; color: #fff;
} }
.app-lang-btn:hover {
transform: translateY(-1px);
}
.app-lang-btn:focus-visible {
outline: none;
box-shadow: 0 0 0 4px rgba(0, 0, 120, 0.10);
}
.shell, .shell,
.wrap, .wrap,
.top-wrap { .top-wrap {
@@ -99,3 +133,83 @@
justify-content: flex-start; justify-content: flex-start;
} }
} }
.confirm-modal[hidden] {
display: none;
}
.confirm-modal {
position: fixed;
inset: 0;
z-index: 1200;
}
.confirm-backdrop {
position: absolute;
inset: 0;
background: rgba(16, 32, 57, 0.42);
backdrop-filter: blur(4px);
animation: confirmFadeIn var(--motion-base) var(--motion-ease);
}
.confirm-dialog {
position: relative;
width: min(480px, calc(100% - 32px));
margin: min(18vh, 120px) auto 0;
padding: 18px;
border: 1px solid rgba(217, 227, 238, 0.94);
border-radius: 22px;
background: linear-gradient(180deg, rgba(255,255,255,0.99), rgba(247,250,255,0.96));
box-shadow: 0 24px 56px rgba(18, 34, 56, 0.20);
animation: confirmPopIn var(--motion-slow) var(--motion-ease);
}
.confirm-dialog-head h2 {
margin: 0;
font-size: 20px;
color: #18345f;
letter-spacing: -0.02em;
}
.confirm-message {
margin: 10px 0 0;
color: #52647a;
font-size: 14px;
line-height: 1.55;
}
.confirm-actions {
margin-top: 18px;
display: flex;
justify-content: flex-end;
gap: 8px;
flex-wrap: wrap;
}
body.confirm-open {
overflow: hidden;
}
@keyframes confirmFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes confirmPopIn {
from {
opacity: 0;
transform: translateY(10px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@media (prefers-reduced-motion: reduce) {
:root {
--motion-fast: 1ms;
--motion-base: 1ms;
--motion-slow: 1ms;
}
}

View File

@@ -20,7 +20,13 @@
font-size: 14px; font-size: 14px;
line-height: 1.2; line-height: 1.2;
cursor: pointer; cursor: pointer;
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.08s ease; transition:
background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
filter 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.btn:focus-visible { .btn:focus-visible {
@@ -34,7 +40,7 @@
} }
.btn:active:not(:disabled) { .btn:active:not(:disabled) {
transform: translateY(1px); transform: translateY(0);
} }
.btn-primary { .btn-primary {
@@ -44,7 +50,9 @@
} }
.btn-primary:hover:not(:disabled) { .btn-primary:hover:not(:disabled) {
filter: brightness(0.95); filter: brightness(0.97);
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(0, 0, 120, 0.12);
} }
.btn-secondary { .btn-secondary {
@@ -55,4 +63,6 @@
.btn-secondary:hover:not(:disabled) { .btn-secondary:hover:not(:disabled) {
background: #f8fafc; background: #f8fafc;
transform: translateY(-1px);
box-shadow: 0 8px 18px rgba(18, 34, 56, 0.08);
} }

View File

@@ -95,6 +95,10 @@
background: rgba(255,255,255,0.92); background: rgba(255,255,255,0.92);
border-radius: 22px; border-radius: 22px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); box-shadow: inset 0 1px 0 rgba(255,255,255,0.92);
transition:
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.hero-card { .hero-card {
@@ -169,6 +173,12 @@
min-height: 38px; min-height: 38px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
transition:
background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.status-pill-neutral { .status-pill-neutral {
@@ -385,6 +395,10 @@
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); box-shadow: inset 0 1px 0 rgba(255,255,255,0.92);
transition:
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.app-card::before { .app-card::before {
@@ -395,6 +409,17 @@
height: 118px; height: 118px;
border-radius: 50%; border-radius: 50%;
background: rgba(31, 79, 214, 0.08); background: rgba(31, 79, 214, 0.08);
transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.app-card:hover {
transform: translateY(-3px);
border-color: rgba(196, 208, 225, 1);
box-shadow: 0 14px 28px rgba(16, 32, 57, 0.10), inset 0 1px 0 rgba(255,255,255,0.92);
}
.app-card:hover::before {
transform: scale(1.06);
} }
.app-card.primary { .app-card.primary {
@@ -468,6 +493,11 @@
font-weight: 800; font-weight: 800;
letter-spacing: 0.03em; letter-spacing: 0.03em;
text-transform: uppercase; text-transform: uppercase;
transition:
background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.card-actions { .card-actions {
@@ -490,6 +520,10 @@
padding: 14px; padding: 14px;
background: linear-gradient(180deg, rgba(255,255,255,0.99), rgba(247,250,255,0.96)); background: linear-gradient(180deg, rgba(255,255,255,0.99), rgba(247,250,255,0.96));
box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); box-shadow: inset 0 1px 0 rgba(255,255,255,0.92);
transition:
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.admin-card::before { .admin-card::before {
@@ -500,6 +534,17 @@
height: 84px; height: 84px;
border-radius: 50%; border-radius: 50%;
background: rgba(31, 79, 214, 0.06); background: rgba(31, 79, 214, 0.06);
transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.admin-card:hover {
transform: translateY(-2px);
border-color: rgba(196, 208, 225, 1);
box-shadow: 0 12px 24px rgba(16, 32, 57, 0.08), inset 0 1px 0 rgba(255,255,255,0.92);
}
.admin-card:hover::before {
transform: scale(1.05);
} }
.admin-card h3 { .admin-card h3 {

View File

@@ -51,6 +51,12 @@ body {
margin-bottom: 12px; margin-bottom: 12px;
} }
.field.has-error input {
border-color: #e3a3a3;
background: #fffafa;
box-shadow: 0 0 0 4px rgba(185, 28, 28, 0.06);
}
.field label { .field label {
display: block; display: block;
font-weight: 700; font-weight: 700;
@@ -67,19 +73,44 @@ body {
min-height: 44px; min-height: 44px;
font: inherit; font: inherit;
background: #fff; background: #fff;
transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1), background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.field input:focus {
outline: none;
border-color: rgba(0, 0, 120, 0.3);
box-shadow: 0 0 0 4px rgba(0, 0, 120, 0.08);
} }
.btn { .btn {
width: 100%; width: 100%;
} }
.errorlist { .login-alert {
color: #b91c1c; margin: 0 0 12px;
margin: 0 0 10px; padding: 12px 14px;
padding: 10px 12px; border-radius: 12px;
border-radius: 10px; border: 1px solid #d6e1ef;
border: 1px solid #f0c8c8; background: #f8fbff;
background: #fff3f3; display: grid;
gap: 4px;
font-size: 14px;
}
.login-alert strong {
font-size: 14px;
line-height: 1.2;
}
.login-alert span {
color: #5f6f85;
line-height: 1.45;
}
.login-alert-error {
color: #991b1b;
border-color: #f0c8c8;
background: linear-gradient(180deg, #fff7f7, #fff1f1);
} }
@media (max-width: 760px) { @media (max-width: 760px) {

View File

@@ -101,6 +101,10 @@
background: var(--panel); background: var(--panel);
border-radius: 22px; border-radius: 22px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.92); box-shadow: inset 0 1px 0 rgba(255,255,255,0.92);
transition:
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.hero-card { .hero-card {
@@ -256,6 +260,17 @@
height: 110px; height: 110px;
border-radius: 50%; border-radius: 50%;
background: rgba(31, 79, 214, 0.08); background: rgba(31, 79, 214, 0.08);
transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.stat-card:hover {
transform: translateY(-2px);
border-color: rgba(196, 208, 225, 1);
box-shadow: 0 14px 30px rgba(18, 33, 56, 0.08), inset 0 1px 0 rgba(255,255,255,0.92);
}
.stat-card:hover::before {
transform: scale(1.05);
} }
.stat-card.red::before { background: rgba(163, 32, 32, 0.08); } .stat-card.red::before { background: rgba(163, 32, 32, 0.08); }
@@ -461,6 +476,10 @@
color: var(--ink); color: var(--ink);
background: rgba(255,255,255,0.98); background: rgba(255,255,255,0.98);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.98); box-shadow: inset 0 1px 0 rgba(255,255,255,0.98);
transition:
border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.search-box input:focus, .search-box input:focus,
@@ -626,6 +645,11 @@
padding: 15px 14px; padding: 15px 14px;
vertical-align: middle; vertical-align: middle;
box-shadow: 0 8px 22px rgba(26, 51, 89, 0.035); box-shadow: 0 8px 22px rgba(26, 51, 89, 0.035);
transition:
background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1),
transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
tbody td:first-child { tbody td:first-child {
@@ -686,6 +710,12 @@
border: 1px solid rgba(217, 227, 238, 0.96); border: 1px solid rgba(217, 227, 238, 0.96);
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(248,251,255,0.96)); background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(248,251,255,0.96));
box-shadow: inset 0 1px 0 rgba(255,255,255,0.94); box-shadow: inset 0 1px 0 rgba(255,255,255,0.94);
transition:
background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.kind-pill { .kind-pill {
@@ -725,6 +755,7 @@
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
word-break: break-word; word-break: break-word;
transition: color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
} }
.mail-link:hover { text-decoration: underline; } .mail-link:hover { text-decoration: underline; }
@@ -750,6 +781,7 @@
.doc-link:hover { .doc-link:hover {
background: linear-gradient(180deg, rgba(248,251,255,0.99), rgba(238,244,255,0.98)); background: linear-gradient(180deg, rgba(248,251,255,0.99), rgba(238,244,255,0.98));
border-color: rgba(0, 0, 120, 0.18); border-color: rgba(0, 0, 120, 0.18);
transform: translateY(-1px);
} }
.actions-cell { white-space: normal; } .actions-cell { white-space: normal; }
@@ -781,6 +813,16 @@
font-weight: 800; font-weight: 800;
color: #18345f; color: #18345f;
background: rgba(255,255,255,0.65); background: rgba(255,255,255,0.65);
transition:
background-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
box-shadow 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.intro-toggle:hover {
transform: translateY(-1px);
box-shadow: 0 8px 18px rgba(18, 33, 56, 0.06);
} }
.intro-toggle::after { .intro-toggle::after {

View File

@@ -0,0 +1,92 @@
(function () {
const modal = document.getElementById('app-confirm-modal');
if (!modal) return;
const messageNode = document.getElementById('app-confirm-message');
const acceptBtn = document.getElementById('app-confirm-accept');
const cancelBtn = document.getElementById('app-confirm-cancel');
const closeNodes = modal.querySelectorAll('[data-confirm-close]');
let resolver = null;
let lastActive = null;
function close(result) {
modal.hidden = true;
modal.setAttribute('aria-hidden', 'true');
document.body.classList.remove('confirm-open');
if (resolver) {
resolver(result);
resolver = null;
}
if (lastActive && typeof lastActive.focus === 'function') {
lastActive.focus();
}
}
function open(message) {
lastActive = document.activeElement;
messageNode.textContent = message || '';
modal.hidden = false;
modal.setAttribute('aria-hidden', 'false');
document.body.classList.add('confirm-open');
acceptBtn.focus();
return new Promise(function (resolve) {
resolver = resolve;
});
}
window.AppConfirm = { open: open };
acceptBtn.addEventListener('click', function () { close(true); });
cancelBtn.addEventListener('click', function () { close(false); });
closeNodes.forEach(function (node) {
node.addEventListener('click', function () { close(false); });
});
document.addEventListener('keydown', function (event) {
if (modal.hidden) return;
if (event.key === 'Escape') {
event.preventDefault();
close(false);
}
});
document.addEventListener('submit', function (event) {
const form = event.target;
const message = form.dataset.confirm;
if (!message || form.dataset.confirmBypass === '1') return;
event.preventDefault();
open(message).then(function (confirmed) {
if (!confirmed) return;
form.dataset.confirmBypass = '1';
if (typeof form.requestSubmit === 'function') {
form.requestSubmit();
} else {
form.submit();
}
window.setTimeout(function () {
delete form.dataset.confirmBypass;
}, 0);
});
}, true);
document.addEventListener('click', function (event) {
const button = event.target.closest('button[data-confirm], input[type="submit"][data-confirm]');
if (!button || button.dataset.confirmBypass === '1') return;
const form = button.form;
if (!form) return;
event.preventDefault();
event.stopPropagation();
open(button.dataset.confirm).then(function (confirmed) {
if (!confirmed) return;
button.dataset.confirmBypass = '1';
if (typeof form.requestSubmit === 'function') {
form.requestSubmit(button);
} else {
button.click();
}
window.setTimeout(function () {
delete button.dataset.confirmBypass;
}, 0);
});
}, true);
})();

View File

@@ -29,24 +29,35 @@
}); });
rowChecks.forEach((c) => c.addEventListener('change', syncState)); rowChecks.forEach((c) => c.addEventListener('change', syncState));
bulkForm.addEventListener('submit', syncState); bulkForm.addEventListener('submit', function (event) {
window.confirmBulkAction = function () {
syncState(); syncState();
const count = currentSelected().length; const count = currentSelected().length;
if (!count) { if (!count) {
alert(bulkForm.dataset.alertEmpty || 'Bitte mindestens einen Welcome-Eintrag auswählen.'); event.preventDefault();
return false; const emptyMessage = bulkForm.dataset.alertEmpty || 'Bitte mindestens einen Welcome-Eintrag auswählen.';
if (window.AppConfirm) {
window.AppConfirm.open(emptyMessage);
} else {
window.alert(emptyMessage);
}
return;
} }
if (bulkForm.dataset.confirmBypass === '1') return;
event.preventDefault();
const action = bulkAction.value; const action = bulkAction.value;
let message = bulkForm.dataset.confirmSend || 'Ausgewählte Welcome-Einträge sofort senden?';
if (action === 'delete') { if (action === 'delete') {
return confirm(bulkForm.dataset.confirmDelete || 'Ausgewählte Welcome-Einträge wirklich löschen?'); message = bulkForm.dataset.confirmDelete || 'Ausgewählte Welcome-Einträge wirklich löschen?';
} else if (action === 'pause') {
message = bulkForm.dataset.confirmPause || 'Ausgewählte Welcome-Einträge pausieren?';
} }
if (action === 'pause') { window.AppConfirm.open(message).then(function (confirmed) {
return confirm(bulkForm.dataset.confirmPause || 'Ausgewählte Welcome-Einträge pausieren?'); if (!confirmed) return;
} bulkForm.dataset.confirmBypass = '1';
return confirm(bulkForm.dataset.confirmSend || 'Ausgewählte Welcome-Einträge sofort senden?'); bulkForm.requestSubmit();
}; window.setTimeout(function () { delete bulkForm.dataset.confirmBypass; }, 0);
});
});
syncState(); syncState();
})(); })();

View File

@@ -21,11 +21,17 @@
<form method="post" action="/accounts/login/"> <form method="post" action="/accounts/login/">
{% csrf_token %} {% csrf_token %}
{% if form.errors %} {% if next %}
<div class="errorlist">{% trans "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen." %}</div> <input type="hidden" name="next" value="{{ next }}" />
{% endif %} {% endif %}
<div class="field">{{ form.username.label_tag }}{{ form.username }}</div> {% if form.errors %}
<div class="field">{{ form.password.label_tag }}{{ form.password }}</div> <div class="login-alert login-alert-error" role="alert" aria-live="assertive">
<strong>{% trans "Anmeldung fehlgeschlagen" %}</strong>
<span>{% trans "Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut." %}</span>
</div>
{% endif %}
<div class="field{% if form.errors %} has-error{% endif %}">{{ form.username.label_tag }}{{ form.username }}</div>
<div class="field{% if form.errors %} has-error{% endif %}">{{ form.password.label_tag }}{{ form.password }}</div>
<button class="btn btn-primary" type="submit">{% trans "Anmelden" %}</button> <button class="btn btn-primary" type="submit">{% trans "Anmelden" %}</button>
</form> </form>
</div> </div>

View File

@@ -17,6 +17,20 @@
{% block shell_header %}{% endblock %} {% block shell_header %}{% endblock %}
{% block shell_body %}{% endblock %} {% block shell_body %}{% endblock %}
</div> </div>
<div class="confirm-modal" id="app-confirm-modal" hidden aria-hidden="true">
<div class="confirm-backdrop" data-confirm-close="1"></div>
<div class="confirm-dialog" role="dialog" aria-modal="true" aria-labelledby="app-confirm-title" aria-describedby="app-confirm-message">
<div class="confirm-dialog-head">
<h2 id="app-confirm-title">{% trans "Bitte bestätigen" %}</h2>
</div>
<p class="confirm-message" id="app-confirm-message"></p>
<div class="confirm-actions">
<button class="btn btn-secondary" type="button" id="app-confirm-cancel">{% trans "Abbrechen" %}</button>
<button class="btn btn-primary" type="button" id="app-confirm-accept">{% trans "Bestätigen" %}</button>
</div>
</div>
</div>
<script src="{% static 'workflows/js/confirm_dialog.js' %}"></script>
{% block extra_scripts %}{% endblock %} {% block extra_scripts %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -108,7 +108,7 @@
<td><input type="text" name="value_{{ item.id }}" value="{{ item.value }}" /></td> <td><input type="text" name="value_{{ item.id }}" value="{{ item.value }}" /></td>
<td><input type="checkbox" name="active_{{ item.id }}" {% if item.is_active %}checked{% endif %} /></td> <td><input type="checkbox" name="active_{{ item.id }}" {% if item.is_active %}checked{% endif %} /></td>
<td> <td>
<button class="btn btn-secondary" type="submit" name="delete_option_id" value="{{ item.id }}" onclick="return confirm('{% trans 'Option wirklich löschen?' %}');">{% trans "Löschen" %}</button> <button class="btn btn-secondary" type="submit" name="delete_option_id" value="{{ item.id }}" data-confirm="{% trans 'Option wirklich löschen?' %}">{% trans "Löschen" %}</button>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}
@@ -164,4 +164,3 @@
</form> </form>
</section> </section>
{% endblock %} {% endblock %}

View File

@@ -103,7 +103,7 @@
<td><input type="text" name="value_{{ item.id }}" value="{{ item.condition_value }}" placeholder="{% trans 'z. B. HR Works' %}" /></td> <td><input type="text" name="value_{{ item.id }}" value="{{ item.condition_value }}" placeholder="{% trans 'z. B. HR Works' %}" /></td>
<td><input type="checkbox" name="active_{{ item.id }}" {% if item.is_active %}checked{% endif %} /></td> <td><input type="checkbox" name="active_{{ item.id }}" {% if item.is_active %}checked{% endif %} /></td>
<td class="actions"> <td class="actions">
<button class="btn btn-secondary" type="submit" name="delete_item_id" value="{{ item.id }}" onclick="return confirm('{% trans 'Checklistenpunkt wirklich löschen?' %}');">{% trans "Löschen" %}</button> <button class="btn btn-secondary" type="submit" name="delete_item_id" value="{{ item.id }}" data-confirm="{% trans 'Checklistenpunkt wirklich löschen?' %}">{% trans "Löschen" %}</button>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}

View File

@@ -81,7 +81,7 @@
<div class="actions"> <div class="actions">
<button class="btn btn-secondary" type="submit" name="session_action" value="save">{% trans "Als Entwurf speichern" %}</button> <button class="btn btn-secondary" type="submit" name="session_action" value="save">{% trans "Als Entwurf speichern" %}</button>
<button class="btn btn-primary" type="submit" name="session_action" value="complete">{% trans "Als abgeschlossen markieren" %}</button> <button class="btn btn-primary" type="submit" name="session_action" value="complete">{% trans "Als abgeschlossen markieren" %}</button>
<button class="btn btn-secondary" type="submit" name="session_action" value="reset" onclick="return confirm('{% trans 'Einweisung wirklich zurücksetzen?' %}');">{% trans "Alles zurücksetzen" %}</button> <button class="btn btn-secondary" type="submit" name="session_action" value="reset" data-confirm="{% trans 'Einweisung wirklich zurücksetzen?' %}">{% trans "Alles zurücksetzen" %}</button>
</div> </div>
</div> </div>
</form> </form>
@@ -102,4 +102,3 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -173,7 +173,7 @@
</div> </div>
{% if request.user.is_staff %} {% if request.user.is_staff %}
<div class="control-stack"> <div class="control-stack">
<form method="post" action="/requests/" id="bulk-delete-form" onsubmit="return confirm('Ausgewählte Einträge wirklich löschen?');"> <form method="post" action="/requests/" id="bulk-delete-form" data-confirm="{% trans 'Ausgewählte Einträge wirklich löschen?' %}">
{% csrf_token %} {% csrf_token %}
<div class="bulk-toolbar"> <div class="bulk-toolbar">
<span class="bulk-info"><span id="selected-count">0</span> {% trans "ausgewählt" %}</span> <span class="bulk-info"><span id="selected-count">0</span> {% trans "ausgewählt" %}</span>
@@ -273,12 +273,12 @@
<td class="actions-cell"> <td class="actions-cell">
<a class="btn btn-secondary" href="/requests/timeline/{{ row.kind_slug }}/{{ row.id }}/">{% trans "Timeline" %}</a> <a class="btn btn-secondary" href="/requests/timeline/{{ row.kind_slug }}/{{ row.id }}/">{% trans "Timeline" %}</a>
{% if row.status_key == 'failed' %} {% if row.status_key == 'failed' %}
<form method="post" action="/requests/retry/{{ row.kind_slug }}/{{ row.id }}/" class="inline-delete" onsubmit="return confirm('Eintrag erneut verarbeiten?');"> <form method="post" action="/requests/retry/{{ row.kind_slug }}/{{ row.id }}/" class="inline-delete" data-confirm="{% trans 'Eintrag erneut verarbeiten?' %}">
{% csrf_token %} {% csrf_token %}
<button class="btn btn-secondary" type="submit">{% trans "Erneut versuchen" %}</button> <button class="btn btn-secondary" type="submit">{% trans "Erneut versuchen" %}</button>
</form> </form>
{% endif %} {% endif %}
<form method="post" action="/requests/" class="inline-delete" onsubmit="return confirm('Eintrag wirklich löschen?');"> <form method="post" action="/requests/" class="inline-delete" data-confirm="{% trans 'Eintrag wirklich löschen?' %}">
{% csrf_token %} {% csrf_token %}
<button class="btn btn-secondary" type="submit" name="single_delete" value="{{ row.kind_slug }}:{{ row.id }}">{% trans "Löschen" %}</button> <button class="btn btn-secondary" type="submit" name="single_delete" value="{{ row.kind_slug }}:{{ row.id }}">{% trans "Löschen" %}</button>
</form> </form>

View File

@@ -62,7 +62,7 @@
</div> </div>
</form> </form>
<form class="bulk-bar" id="welcome-bulk-form" method="post" action="/admin-tools/welcome-emails/bulk-action/" onsubmit="return confirmBulkAction();" data-alert-empty="{% trans 'Bitte mindestens einen Welcome-Eintrag auswählen.' %}" data-confirm-delete="{% trans 'Ausgewählte Welcome-Einträge wirklich löschen?' %}" data-confirm-pause="{% trans 'Ausgewählte Welcome-Einträge pausieren?' %}" data-confirm-send="{% trans 'Ausgewählte Welcome-Einträge sofort senden?' %}"> <form class="bulk-bar" id="welcome-bulk-form" method="post" action="/admin-tools/welcome-emails/bulk-action/" data-alert-empty="{% trans 'Bitte mindestens einen Welcome-Eintrag auswählen.' %}" data-confirm-delete="{% trans 'Ausgewählte Welcome-Einträge wirklich löschen?' %}" data-confirm-pause="{% trans 'Ausgewählte Welcome-Einträge pausieren?' %}" data-confirm-send="{% trans 'Ausgewählte Welcome-Einträge sofort senden?' %}">
{% csrf_token %} {% csrf_token %}
<label style="display:inline-flex; align-items:center; gap:6px; margin:0;"> <label style="display:inline-flex; align-items:center; gap:6px; margin:0;">
<input type="checkbox" id="select-all-welcome" /> <input type="checkbox" id="select-all-welcome" />
@@ -151,4 +151,3 @@
{% block extra_scripts %} {% block extra_scripts %}
<script src="{% static 'workflows/js/welcome_emails.js' %}"></script> <script src="{% static 'workflows/js/welcome_emails.js' %}"></script>
{% endblock %} {% endblock %}