Three additions on top of Slice B's edit-mode chrome. **Catalog expansion (2 new widgets, default-hidden — opt-in via picker):** - pinned-projects: surfaces a list of the user's pinned matters via the pre-existing PinService (mig 062/063, pre-dates t-paliad-219). New DashboardService.loadPinnedProjects joins paliad.user_pinned_projects to paliad.projects under the standard visibility predicate, preserves pinned-at-DESC order, capped at PinnedProjectsCap=20. PinnedProjects []PinnedProjectRef grows DashboardData; SetPinService wired post-construction to mirror the SetApprovalService pattern. - quick-actions: pure UI affordance with three buttons linking to the existing /projects/new, /deadlines/new, /appointments/new routes. No backend payload, no settings schema. Both default-hidden — m's brief asked for "high-value adds"; injecting new widgets into every user's dashboard unannounced would be loud. Factory test relaxed: visibility now matches catalog.DefaultVisible instead of the previous "all-visible" invariant. **Firm-wide admin default (mig 117 + new service + 4 endpoints):** - paliad.firm_dashboard_default: single-row table (id smallint PK CHECK id=1) with layout_json + updated_by + updated_at. RLS: SELECT authenticated, no INSERT/UPDATE policy (writes go through the service-role connection behind the adminGate). - FirmDashboardDefaultService Get/Set/Clear. Validates against the catalog on Set so an admin can't seed an invalid layout. - DashboardLayoutService.SetFirmDefaultService wires in the firm source. Both GetOrSeed and ResetToDefault now prefer the firm default over the code-resident FactoryDefaultLayout when one is set. Nil-safe — empty firm row falls back to the factory layout, transient DB errors fall back too (a blip can't strand a user without a dashboard). - HTTP: GET / PUT / DELETE /api/admin/firm-dashboard-default (admin- gated). POST /api/me/dashboard-layout/promote: admin convenience — reads the admin's own current layout and stashes it as the firm default (saves the JSON-editor step; admins edit via /dashboard's normal editor, then click Promote). **Frontend (Slice B's edit-mode footer grew an admin button):** - "Als Firmen-Standard speichern" button in the edit footer; hidden via CSS-inline until syncPromoteButtonVisibility unhides for global_admin. Confirm() → POST /promote → toast. - The existing "Auf Standard zurücksetzen" copy stays the same — the semantics now "firm default if set, else factory", which is the desired surface: users see one canonical "Standard" link. i18n: 13 new keys × DE+EN (dashboard.pinned.*, dashboard.quick.*, dashboard.edit.promote*). i18n-keys.ts regenerated by build. m/paliad#46. go build ./... clean; go vet ./... clean go test ./internal/... clean (Slice C catalog test + factory-default test relaxation; FirmDashboardDefault round-trip tests gated on TEST_DATABASE_URL) Migration 117 dry-run: PASS (other dry-run failures are pre-existing local-DB collisions on origin/main; mig 117 itself clean) bun run build clean: dashboard.html carries new section markup + admin button; dashboard.js bundles renderPinnedProjects + promote handler + all new i18n keys
34 lines
1.7 KiB
SQL
34 lines
1.7 KiB
SQL
-- t-paliad-219 Slice C: firm-wide dashboard default layout.
|
|
--
|
|
-- Design: docs/design-dashboard-configurable-2026-05-20.md §8.2 (firm-wide
|
|
-- admin default, deferred to v1.1 — now activated by m's Slice C brief).
|
|
--
|
|
-- A single optional row that holds the firm's preferred dashboard layout.
|
|
-- DashboardLayoutService.GetOrSeed reads this on first call for a new user
|
|
-- (falling back to the code-resident FactoryDefaultLayout when null);
|
|
-- ResetToDefault similarly prefers the firm default. Admins promote their
|
|
-- own current layout into this row via POST /api/me/dashboard-layout/promote.
|
|
--
|
|
-- Single-row design via CHECK (id = 1) so there's no ambiguity about which
|
|
-- row is "the default". RLS lets any authenticated user SELECT (so the
|
|
-- service can read it during seed); only the application (service-role
|
|
-- connection) writes — the admin gate sits on the HTTP handler.
|
|
|
|
CREATE TABLE paliad.firm_dashboard_default (
|
|
id smallint PRIMARY KEY DEFAULT 1 CHECK (id = 1),
|
|
layout_json jsonb NOT NULL,
|
|
updated_by uuid REFERENCES paliad.users(id) ON DELETE SET NULL,
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
ALTER TABLE paliad.firm_dashboard_default ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- All authenticated users can SELECT — the dashboard seed path needs to
|
|
-- read it for every new user. The HTTP handler enforces admin-only on the
|
|
-- PUT/DELETE paths; the service runs under service-role so writes bypass
|
|
-- RLS anyway. No INSERT/UPDATE policy means no Supabase-JWT-authenticated
|
|
-- client can write, which is the desired posture.
|
|
CREATE POLICY firm_dashboard_default_read
|
|
ON paliad.firm_dashboard_default FOR SELECT
|
|
USING (true);
|