refactor: onboarding form — drop Praxisgruppe, free-text role, add Dezernat (t-paliad-020)

- Drop the Praxisgruppe field from the onboarding form. Every Paliad user
  is in patent practice, so the field carried no signal. The DB column is
  retained for future use (set to NULL on insert).
- Switch role from a 4-value enum (partner/associate/pa/admin) to free
  text with a <datalist> of suggestions (Partner, Associate, PA, Of
  Counsel, Referendar/in, Trainee, wiss. Mitarbeiter/in, Sekretariat).
  German firms have many roles beyond the original four.
- Add an optional Dezernat field — the team led by a specific partner.
  Free text, no FK (the partner may not be registered yet).

Backend:
- Migration 015: drop the role enum CHECK, replace with non-empty CHECK;
  ADD COLUMN dezernat text.
- UserService.Create: drop validRoles map, require non-empty role string,
  trim and persist Dezernat. Admin bootstrap gate unchanged.
- models.User gains Dezernat *string; userColumns SELECT updated so
  /api/me returns it.

Frontend:
- onboarding.tsx: replace role <select> with <input list=...>; add
  dezernat input; remove practice_group.
- onboarding.ts: send dezernat (if non-empty), require role.
- i18n: add onboarding.role.placeholder, onboarding.dezernat[.placeholder],
  onboarding.error.role; remove the role.* enum and practice_group keys.
This commit is contained in:
m
2026-04-18 20:26:11 +02:00
parent ebb206307d
commit 7c44bbec7e
9 changed files with 106 additions and 60 deletions

View File

@@ -671,16 +671,14 @@ const translations: Record<Lang, Record<string, string>> = {
"onboarding.office": "B\u00fcro",
"onboarding.office.placeholder": "Bitte ausw\u00e4hlen",
"onboarding.role": "Rolle",
"onboarding.role.associate": "Associate",
"onboarding.role.partner": "Partner",
"onboarding.role.pa": "PA",
"onboarding.role.admin": "Admin",
"onboarding.practice_group": "Praxisgruppe",
"onboarding.practice_group.placeholder": "z.B. Patent Litigation",
"onboarding.role.placeholder": "z.B. Associate, Partner, PA",
"onboarding.dezernat": "Dezernat / Partner",
"onboarding.dezernat.placeholder": "z.B. Dr. M\u00fcller, Team Schmidt",
"onboarding.optional": "(optional)",
"onboarding.submit": "Profil anlegen",
"onboarding.error.display_name": "Bitte Anzeigename eingeben.",
"onboarding.error.office": "Bitte B\u00fcro ausw\u00e4hlen.",
"onboarding.error.role": "Bitte Rolle eingeben.",
"onboarding.error.generic": "Profil konnte nicht angelegt werden.",
"onboarding.error.connection": "Verbindungsfehler. Bitte versuchen Sie es erneut.",
@@ -1471,16 +1469,14 @@ const translations: Record<Lang, Record<string, string>> = {
"onboarding.office": "Office",
"onboarding.office.placeholder": "Please select",
"onboarding.role": "Role",
"onboarding.role.associate": "Associate",
"onboarding.role.partner": "Partner",
"onboarding.role.pa": "PA",
"onboarding.role.admin": "Admin",
"onboarding.practice_group": "Practice group",
"onboarding.practice_group.placeholder": "e.g. Patent Litigation",
"onboarding.role.placeholder": "e.g. Associate, Partner, PA",
"onboarding.dezernat": "Department / Partner",
"onboarding.dezernat.placeholder": "e.g. Dr. M\u00fcller, Team Schmidt",
"onboarding.optional": "(optional)",
"onboarding.submit": "Create profile",
"onboarding.error.display_name": "Please enter a display name.",
"onboarding.error.office": "Please select an office.",
"onboarding.error.role": "Please enter a role.",
"onboarding.error.generic": "Could not create profile.",
"onboarding.error.connection": "Connection error. Please try again.",

View File

@@ -86,7 +86,7 @@ async function submitForm(e: Event): Promise<void> {
const displayName = (data.get("display_name") as string || "").trim();
const office = (data.get("office") as string || "").trim();
const role = (data.get("role") as string || "").trim();
const practiceGroup = (data.get("practice_group") as string || "").trim();
const dezernat = (data.get("dezernat") as string || "").trim();
if (!displayName) {
showMessage(t("onboarding.error.display_name"), "login-error");
@@ -96,13 +96,17 @@ async function submitForm(e: Event): Promise<void> {
showMessage(t("onboarding.error.office"), "login-error");
return;
}
if (!role) {
showMessage(t("onboarding.error.role"), "login-error");
return;
}
const payload: Record<string, unknown> = {
display_name: displayName,
office,
role,
};
if (practiceGroup) payload.practice_group = practiceGroup;
if (dezernat) payload.dezernat = dezernat;
submitBtn.disabled = true;
try {

View File

@@ -41,24 +41,39 @@ export function renderOnboarding(): string {
</select>
<label htmlFor="onb-role" className="login-label" data-i18n="onboarding.role">Rolle</label>
<select id="onb-role" name="role" required className="login-input">
<option value="associate" data-i18n="onboarding.role.associate">Associate</option>
<option value="partner" data-i18n="onboarding.role.partner">Partner</option>
<option value="pa" data-i18n="onboarding.role.pa">PA</option>
<option value="admin" data-i18n="onboarding.role.admin">Admin</option>
</select>
<input
type="text"
id="onb-role"
name="role"
list="onb-role-suggestions"
required
autocomplete="off"
className="login-input"
data-i18n-placeholder="onboarding.role.placeholder"
placeholder="z.B. Associate, Partner, PA"
/>
<datalist id="onb-role-suggestions">
<option value="Partner"></option>
<option value="Associate"></option>
<option value="PA"></option>
<option value="Of Counsel"></option>
<option value="Referendar/in"></option>
<option value="Trainee"></option>
<option value="wiss. Mitarbeiter/in"></option>
<option value="Sekretariat"></option>
</datalist>
<label htmlFor="onb-practice-group" className="login-label" data-i18n="onboarding.practice_group">
Praxisgruppe <span className="login-label-optional" data-i18n="onboarding.optional">(optional)</span>
<label htmlFor="onb-dezernat" className="login-label" data-i18n="onboarding.dezernat">
Dezernat / Partner <span className="login-label-optional" data-i18n="onboarding.optional">(optional)</span>
</label>
<input
type="text"
id="onb-practice-group"
name="practice_group"
id="onb-dezernat"
name="dezernat"
autocomplete="off"
className="login-input"
data-i18n-placeholder="onboarding.practice_group.placeholder"
placeholder="z.B. Patent Litigation"
data-i18n-placeholder="onboarding.dezernat.placeholder"
placeholder="z.B. Dr. M&uuml;ller, Team Schmidt"
/>
<button type="submit" className="login-button" data-i18n="onboarding.submit">Profil anlegen</button>