db4279d148b36fb8fbd8995feb44e43a5d2ba334
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2af4bf1f88 |
feat(t-paliad-148) commit 5/6: frontend — team-add dropdown + 3-col team table + admin-team profession + onboarding
projects-detail.tsx (the bug surface):
- Team-add dropdown switches from 7 mixed values (lead/associate/pa/of_counsel/local_counsel/expert/observer) to 4 responsibility-only values (lead/member/observer/external). Default 'member'. Closes m's bug — staffing a person no longer pretends to define their firm tier.
- Team table gains a Profession column (between Name and Responsibility), so the firm-tier badge is glanceable at staffing time.
- form.team-profession-hint surfaces the picked person's profession or warns when none is set ("kann keine 4-Augen-Genehmigungen erteilen").
projects-detail.ts:
- ProjectTeamMember type gains responsibility + user_profession. Legacy .role field kept readable for the deprecation window but UI no longer uses it.
- renderTeam renders 3-column tabular layout. Profession pill is read-only (.projekt-team-profession[--none]); responsibility is visible inline (inline-edit deferred to follow-up).
- canManagePartnerUnits switches from m.role==="lead" to m.responsibility==="lead".
- Team-add submit posts {responsibility} instead of {role}.
admin-team.tsx + client/admin-team.ts:
- New Profession column with inline-edit dropdown (6 values + "(extern)" NULL option). User type extends with profession?: string|null.
- Read-only cell uses .projekt-team-profession pill with "(extern)" placeholder for NULL.
onboarding.tsx + client/onboarding.ts:
- New required profession <select> with default 'associate'. Six values match the new enum. Hint copy explains the difference from job_title.
- POST /api/onboarding payload gains profession field.
i18n.ts: ~30 new keys DE+EN — projects.team.profession.* / .responsibility.* / projects.detail.team.col.profession / .responsibility / .form.responsibility / .form.profession.* / admin.team.col.profession.* / onboarding.profession.* / projects.team.profession.none + .hint variants.
CSS:
- .projekt-team-profession pill (firm-tier, read-only).
- .projekt-team-profession--none italic-dashed for NULL professions.
- .projekt-team-responsibility pill (per-project).
- .form-hint--warning for the team-add no-profession warning.
Build: bun build.ts clean (1723 i18n keys, all referenced). go build + go vet + go test (pure-Go) clean.
|
||
|
|
d50ba363a8 |
feat(t-paliad-070): partner-units frontend rename + new admin page
Frontend half of the rename: - New /admin/partner-units page (admin-partner-units.tsx + .ts) with full CRUD + member management. Mirrors /admin/team's aesthetic and uses the same modal pattern. Card on /admin flips from "Geplant" to "Verfügbar" with ICON_BUILDING and a /admin/partner-units link. - Sidebar gains a "Partner Units" admin nav item between Team and Audit. - Onboarding form replaces the free-text Dezernat input with a select populated from /api/partner-units; submits partner_unit_id which the backend uses to insert a membership row in the user-create tx. - Settings: dezernat tab removed entirely (TabName drops to 3). The read-only "Meine Partner Units" view now lives as a card on the profile tab. Free-text dezernat input removed from the profile form. ~250 lines of admin-CRUD removed; replaced by ~70 lines of read-only partner-units summary. - /admin/team: Dezernat column dropped from the table and the inline edit row; "Onboard existing account" modal no longer asks for one. Column count drops from 10 to 9. - /team directory: groups by structured partner_unit_members only; drops the free-text fallback grouping and the "Ohne Dezernat" loose bucket. Single "Ohne Partner Unit" orphan group catches users in no unit. - i18n: ~30 dezernat.* + onboarding.dezernat + admin.team.col.dezernat + admin.card.departments + team.* keys removed; ~30 partner_unit.* keys added in DE+EN. "Partner Unit" / "Partner Units" used as a loanword in DE. - /api/departments?include=members → /api/partner-units?include=members in team.ts (the only frontend-side fetch URL referencing the old endpoints). go build / vet / test clean. cd frontend && bun run build clean. |
||
|
|
b34500ad31 |
feat(t-paliad-051): split paliad.users.role into job_title + global_role
Conflation: paliad.users.role was simultaneously job title (display only)
and global permission ('role=admin' checks across Go/SQL/JS). m wanted
to set his real job title ('Counsel Knowledge Lawyer') without losing
admin access — the t-paliad-050 admin-team UI even rejected role='admin'
on edit, so any UI-driven update silently demoted m.
Per m's three-axis principle ("firm roles are not project roles are not
tool roles"), this lands TWO orthogonal columns:
* paliad.users.job_title — free text, NULL allowed, display only.
NEVER gates anything in code or SQL.
* paliad.users.global_role — CHECK ('standard'|'global_admin'),
default 'standard'. The only thing that gates ops.
Migration 023:
* Drops NOT NULL + 'associate' default off the legacy role column
* Promotes role='admin' rows to global_role='global_admin'; clears
their role text; sets m's job_title='Counsel Knowledge Lawyer'
* Renames role -> job_title with CHECK (job_title IS NULL OR <> '')
* Replaces can_see_project body with global_role='global_admin'
* CASCADE-rebuilds every RLS policy under canonical English names —
with the historic u.role IN ('partner','admin') gates simplified
to u.global_role='global_admin' only (job_title NEVER gates)
Code surface:
* internal/models/models.go: User.Role -> User.JobTitle (*string) +
User.GlobalRole (string)
* internal/services/user_service.go: bootstrap (first row promoted to
global_admin via pg_advisory_xact_lock(7346298141), unchanged constant);
UpdateProfile drops role, accepts job_title only; AdminUpdateUser adds
global_role with last-admin demotion guard (ErrLastGlobalAdmin);
IsAdmin reads global_role
* Other services (dashboard/agenda/appointment/project/deadline/
department/party/note/checklist_instance): pass user.GlobalRole into
visibility predicates; partner-or-admin gates simplified to
global_admin only
* Handlers: drop now-impossible ErrAdminBootstrapOnly cases;
admin_users handles ErrLastGlobalAdmin -> 409
* department_service: SQL u.role -> u.job_title, DepartmentMember.Role
-> JobTitle (*string)
Frontend:
* /api/me + Me interfaces ship {job_title, global_role}
* Onboarding form: 'Berufsbezeichnung / Job title' (job_title)
* Settings + admin-team forms: same renames + i18n updates
* Admin-team: new 'Berechtigung / Permission' column with
'Standard'|'Global Admin' badge + dropdown editor; last-admin
demotion guard at the UI layer
* Sidebar admin-section reveal: me.global_role==='global_admin'
* deadlines/deadlines-detail/projects-detail/notes: partner-as-permission
gates dropped, only global_admin grants those operations
Tests:
* user_service_test: bootstrap promotes first user to global_admin,
subsequent default to standard; AdminUpdateUser refuses to demote
the last global_admin; IsAdmin reads global_role
Migration applied to ydb 2026-04-27. Live state verified:
* m: job_title='Counsel Knowledge Lawyer', global_role='global_admin'
* tester: job_title=NULL, global_role='global_admin'
* 29 stub colleagues: job_title='associate', global_role='standard'
|
||
|
|
7c44bbec7e |
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. |
||
|
|
b8f95f5d7a |
feat: user onboarding flow — first-login profile capture (t-paliad-019)
New users were stuck on the dashboard with a dead-end "Bitte schließen Sie das Onboarding ab" message because nothing created the paliad.users row that all matter-management features depend on. This adds the missing Phase D flow. Backend - UserService.Create: validates display_name / office / role, inserts the paliad.users row with (id, email) from the verified JWT claims (never from the request body — prevents onboarding as someone else). - Admin bootstrap: only the very first paliad.users row may self-assign role='admin'; subsequent requests get ErrAdminBootstrapOnly (403). Guarded by pg_advisory_xact_lock so two concurrent first-logins can't race past the count=0 check under READ COMMITTED. - POST /api/onboarding + GET /onboarding; the page is authenticated but NOT behind the onboarding gate (it's the one page users without a paliad.users row may reach). - gateOnboarded middleware wraps the matter-management pages (Dashboard, Akten, Fristen, Termine, Einstellungen/CalDAV) and 302s to /onboarding when the caller has no paliad.users row. Knowledge-platform pages (Kostenrechner, Glossar, Links, Downloads, Gerichte, Gebührentabellen, Checklisten, Fristenrechner) stay ungated. - auth.VerifiedClaims now carries the email claim; auth.ClaimsFromContext exposes it to handlers. GET /api/me includes the email in the 404 body so the onboarding form can pre-fill the display name from the local-part. Frontend - frontend/src/onboarding.tsx + src/client/onboarding.ts: centred card on the existing .login-card styling. Fields: display_name (required, pre-filled from email local-part), office (dropdown from /api/offices), role (dropdown, default associate), practice_group (optional). - Dashboard client: toggleOnboardingHint now redirects to /onboarding instead of showing the dead-end hint — belt-and-braces behind the server gate in case the DB lookup fell through. - DE + EN i18n keys for every label, placeholder, and error. - Added onboarding to build.ts. Tests: internal/services/user_service_test.go covers the valid path, per-field validation, duplicate (ErrUserAlreadyOnboarded), and the admin-bootstrap gate. Follows the existing TEST_DATABASE_URL skip pattern. |