import { t, tDyn, getLang } from "./i18n"; // Shared logic for the Project form rendered by ProjectFormFields.tsx. // Used by /projects/new and the edit modal on /projects/{id}. export interface ProceedingTypeRow { id: number; code: string; name: string; name_en: string; jurisdiction?: string; is_active: boolean; } let proceedingTypesCache: ProceedingTypeRow[] | null = null; // loadProceedingTypes fetches active fristenrechner-category proceeding // types — the only set a project may bind to (mig 087/088 + service // validation guard `validateProceedingTypeCategory`). Cached at module // level so the page only pays for one fetch even when both the new- // project page and the edit modal exercise the picker. export async function loadProceedingTypes(): Promise { if (proceedingTypesCache) return proceedingTypesCache; try { const resp = await fetch("/api/proceeding-types-db?category=fristenrechner"); if (!resp.ok) return []; const rows = ((await resp.json()) ?? []) as ProceedingTypeRow[]; proceedingTypesCache = rows.filter((r) => r.is_active); return proceedingTypesCache; } catch { return []; } } export interface ProjectMini { id: string; title: string; type: string; reference?: string | null; // t-paliad-222 / m/paliad#50: auto-derived dotted project code from // the ancestor tree. Populated by the service projection on every // /api/projects response, so the picker can show the code without an // extra fetch. code?: string; } export interface ProjectFormState { type: string; parentID: string; parentTitle: string; title: string; reference: string; description: string; status: string; clientNumber: string; matterNumber: string; billingReference: string; netDocumentsURL: string; industry: string; country: string; patentNumber: string; filingDate: string; grantDate: string; court: string; caseNumber: string; ourSide: string; } let parentCandidates: ProjectMini[] = []; function $(id: string): HTMLElement { const el = document.getElementById(id); if (!el) throw new Error("missing form element: " + id); return el; } function tryGet(id: string): HTMLElement | null { return document.getElementById(id); } // showFieldsForType toggles parent-picker + type-specific blocks. export function showFieldsForType(typeSel: string) { const parentWrap = tryGet("projekt-parent-wrap") as HTMLDivElement | null; const clientFields = tryGet("fields-client") as HTMLDivElement | null; const litigationFields = tryGet("fields-litigation") as HTMLDivElement | null; const patentFields = tryGet("fields-patent") as HTMLDivElement | null; const caseFields = tryGet("fields-case") as HTMLDivElement | null; if (clientFields) clientFields.style.display = typeSel === "client" ? "block" : "none"; if (litigationFields) litigationFields.style.display = typeSel === "litigation" ? "block" : "none"; if (patentFields) patentFields.style.display = typeSel === "patent" ? "block" : "none"; if (caseFields) caseFields.style.display = typeSel === "case" ? "block" : "none"; if (parentWrap) parentWrap.style.display = typeSel === "client" ? "none" : "block"; } export async function loadParentCandidates(excludeID?: string) { try { const resp = await fetch("/api/projects"); if (!resp.ok) return; const all = (await resp.json()) as ProjectMini[]; parentCandidates = excludeID ? all.filter((p) => p.id !== excludeID) : all; } catch { /* network — leave empty */ } } function esc(s: string): string { const d = document.createElement("div"); d.textContent = s; return d.innerHTML; } export function initParentPicker() { const input = tryGet("projekt-parent-input") as HTMLInputElement | null; const hidden = tryGet("projekt-parent-id") as HTMLInputElement | null; const sugs = tryGet("projekt-parent-suggestions") as HTMLDivElement | null; if (!input || !hidden || !sugs) return; input.addEventListener("input", () => { const q = input.value.trim().toLowerCase(); hidden.value = ""; if (!q) { sugs.innerHTML = ""; return; } const matches = parentCandidates .filter((p) => { // Search across title + manual reference + auto-derived code // so the user can type "EXMPL" or "INF.CFI" and find the row. const hay = (p.title + " " + (p.reference || "") + " " + (p.code || "")).toLowerCase(); return hay.includes(q); }) .slice(0, 8); sugs.innerHTML = matches .map((p) => { // Render the auto-derived code (if any, and distinct from // reference) as a small mono badge on the right so the user // can disambiguate two same-titled projects by their tree // position. Single template literal kept readable inline. const code = p.code && p.code !== (p.reference || "") ? p.code : ""; const codeBadge = code ? `${esc(code)}` : ""; return `
${esc(p.title)} ${esc(tDyn("projects.type." + p.type) || p.type)} ${codeBadge}
`; }) .join(""); sugs.querySelectorAll(".collab-suggestion").forEach((el) => { el.addEventListener("click", () => { hidden.value = el.dataset.id!; input.value = el.dataset.title!; sugs.innerHTML = ""; }); }); }); } // wireTypeChange wires the