Merge: PR-E bug batch (t-paliad-062)

# Conflicts:
#	frontend/src/client/projects.ts
This commit is contained in:
m
2026-04-27 19:36:21 +02:00
7 changed files with 96 additions and 3 deletions

View File

@@ -50,7 +50,7 @@ worker:
max_workers: 5
persistent: true
head:
name: ""
name: "maria"
max_loops: 50
infinity_mode: false
max_idle_duration: 2h0m0s

View File

@@ -724,8 +724,13 @@ function escapeHtml(s: string): string {
}
function initTabs() {
const id = project?.id ?? parseProjectID();
document.querySelectorAll<HTMLAnchorElement>(".akten-tab").forEach((tab) => {
if (id) tab.href = `/projects/${id}/${tab.dataset.tab}`;
tab.addEventListener("click", (e) => {
// SPA flow on plain left-click; let the browser handle middle-click,
// ctrl/meta-click, and "open in new tab" via the real href above.
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
e.preventDefault();
showTab(tab.dataset.tab as TabId);
});

View File

@@ -19,10 +19,18 @@ interface Project {
let allRows: Project[] = [];
let typeFilter = "";
let statusFilter = "";
let viewMode: "flat" | "tree" | "roots" = "flat";
let viewMode: "flat" | "tree" | "roots" = parseInitialView();
let searchQuery = "";
let loadedOK = false;
// Honour ?view=flat|tree|roots from the URL so dashboard links and bookmarks
// land on the right layout. Anything else falls back to "flat".
function parseInitialView(): "flat" | "tree" | "roots" {
const v = new URLSearchParams(window.location.search).get("view");
if (v === "tree" || v === "roots" || v === "flat") return v;
return "flat";
}
async function loadProjekte() {
const unavailable = document.getElementById("akten-unavailable")!;
const table = document.querySelector<HTMLElement>(".akten-table-wrap")!;
@@ -179,6 +187,7 @@ function initFilters() {
const typeSel = document.getElementById("project-type") as HTMLSelectElement;
const status = document.getElementById("project-status") as HTMLSelectElement;
const view = document.getElementById("project-view") as HTMLSelectElement;
view.value = viewMode;
typeSel.addEventListener("change", () => {
typeFilter = typeSel.value;
render();
@@ -189,10 +198,20 @@ function initFilters() {
});
view.addEventListener("change", () => {
viewMode = view.value as "flat" | "tree" | "roots";
syncViewQuery();
render();
});
}
// Mirror viewMode into ?view= so the URL is shareable. Default "flat" stays
// implicit (drop the param) to keep the canonical path clean.
function syncViewQuery() {
const url = new URL(window.location.href);
if (viewMode === "flat") url.searchParams.delete("view");
else url.searchParams.set("view", viewMode);
window.history.replaceState({}, "", url.toString());
}
document.addEventListener("DOMContentLoaded", () => {
initI18n();
initSidebar();

View File

@@ -1700,7 +1700,7 @@ input[type="range"]::-moz-range-thumb {
.glossar-search {
width: 100%;
padding: 0.65rem 0.75rem 0.65rem 2.5rem;
padding: 0.65rem 4.5rem 0.65rem 2.5rem;
font-size: 0.92rem;
border: 1px solid var(--color-border);
border-radius: var(--radius);

View File

@@ -0,0 +1,18 @@
-- Reverse 024_rename_department_columns: put back the German names.
DO $$ BEGIN ALTER POLICY department_members_write ON paliad.department_members RENAME TO dezernat_mitglieder_write; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER POLICY department_members_select ON paliad.department_members RENAME TO dezernat_mitglieder_select; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER POLICY departments_write ON paliad.departments RENAME TO dezernate_write; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER POLICY departments_select ON paliad.departments RENAME TO dezernate_select; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER INDEX paliad.department_members_user_idx RENAME TO dezernat_mitglieder_user_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER INDEX paliad.departments_lead_idx RENAME TO dezernate_lead_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER INDEX paliad.departments_office_idx RENAME TO dezernate_office_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT department_members_user_id_fkey TO dezernat_mitglieder_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT department_members_department_id_fkey TO dezernat_mitglieder_dezernat_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT department_members_pkey TO dezernat_mitglieder_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT departments_office_check TO dezernate_office_check; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT departments_lead_user_id_fkey TO dezernate_lead_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT departments_pkey TO dezernate_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME COLUMN department_id TO dezernat_id; EXCEPTION WHEN undefined_column OR undefined_table THEN NULL; END $$;

View File

@@ -0,0 +1,49 @@
-- Rename remaining German names on paliad.departments / paliad.department_members
-- (t-paliad-062 — F-03 fix).
--
-- Migration 020 renamed the *tables* paliad.dezernate → departments and
-- paliad.dezernat_mitglieder → department_members, but missed:
-- * the dezernat_id column on the link table (still German on prod youpc),
-- * the indexes/constraints/policies that postgres did not auto-rename
-- when their owning table was renamed.
--
-- Application code (services.DepartmentService.ListWithMembers,
-- ListMembers, AddMember, RemoveMember) selects/inserts on
-- department_members.department_id, so the missing column rename surfaces
-- as a 500 on /api/departments?include=members and on every /admin/team
-- write that touches Dezernat membership.
--
-- Idempotent: every rename is wrapped in a guard that swallows
-- "undefined_column" / "undefined_object" so the migration is a no-op on
-- DBs that were already provisioned with English names by migration 018.
-- ---------------------------------------------------------------------------
-- Column
-- ---------------------------------------------------------------------------
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME COLUMN dezernat_id TO department_id; EXCEPTION WHEN undefined_column OR undefined_table THEN NULL; END $$;
-- ---------------------------------------------------------------------------
-- Constraints (primary key + foreign keys + check). Renaming a pkey
-- constraint also renames the underlying index of the same name.
-- ---------------------------------------------------------------------------
DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_pkey TO departments_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_lead_user_id_fkey TO departments_lead_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.departments RENAME CONSTRAINT dezernate_office_check TO departments_office_check; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_pkey TO department_members_pkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_dezernat_id_fkey TO department_members_department_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER TABLE paliad.department_members RENAME CONSTRAINT dezernat_mitglieder_user_id_fkey TO department_members_user_id_fkey; EXCEPTION WHEN undefined_object THEN NULL; END $$;
-- ---------------------------------------------------------------------------
-- Standalone indexes (non-pkey).
-- ---------------------------------------------------------------------------
DO $$ BEGIN ALTER INDEX paliad.dezernate_office_idx RENAME TO departments_office_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER INDEX paliad.dezernate_lead_idx RENAME TO departments_lead_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER INDEX paliad.dezernat_mitglieder_user_idx RENAME TO department_members_user_idx; EXCEPTION WHEN undefined_object THEN NULL; END $$;
-- ---------------------------------------------------------------------------
-- RLS policies
-- ---------------------------------------------------------------------------
DO $$ BEGIN ALTER POLICY dezernate_select ON paliad.departments RENAME TO departments_select; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER POLICY dezernate_write ON paliad.departments RENAME TO departments_write; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER POLICY dezernat_mitglieder_select ON paliad.department_members RENAME TO department_members_select; EXCEPTION WHEN undefined_object THEN NULL; END $$;
DO $$ BEGIN ALTER POLICY dezernat_mitglieder_write ON paliad.department_members RENAME TO department_members_write; EXCEPTION WHEN undefined_object THEN NULL; END $$;

View File

@@ -256,7 +256,9 @@ func Register(mux *http.ServeMux, client *auth.Client, giteaAPIToken string, svc
protected.HandleFunc("GET /projects", gateOnboarded(handleProjectsListPage))
protected.HandleFunc("GET /projects/new", gateOnboarded(handleProjectsNewPage))
protected.HandleFunc("GET /projects/{id}", gateOnboarded(handleProjectsDetailPage))
protected.HandleFunc("GET /projects/{id}/history", gateOnboarded(handleProjectsDetailPage))
protected.HandleFunc("GET /projects/{id}/events", gateOnboarded(handleProjectsDetailPage))
protected.HandleFunc("GET /projects/{id}/children", gateOnboarded(handleProjectsDetailPage))
protected.HandleFunc("GET /projects/{id}/parties", gateOnboarded(handleProjectsDetailPage))
protected.HandleFunc("GET /projects/{id}/deadlines", gateOnboarded(handleProjectsDetailPage))
protected.HandleFunc("GET /projects/{id}/appointments", gateOnboarded(handleProjectsDetailPage))