Two-part fix from m's 2026-05-21 finding that the Schriftsätze tab
told users "Bitte zuerst einen Verfahrenstyp setzen" while the
project form had no field to set it. The `proceeding_type_id`
column was already on `paliad.projects` and accepted by the API.
Part 1 — Verfahrenstyp picker on the case-fields block
* frontend/src/components/ProjectFormFields.tsx — new optional
<select id="project-proceeding-type-id"> rendered between
Aktenzeichen and Mandantenrolle inside the type=case block.
First option is "(nicht gesetzt)" / "(unset)".
* frontend/src/client/project-form.ts — shared
loadProceedingTypes() + populateProceedingTypeSelect()
helpers. Options sorted by `code` (de.* → dpma.* → epa.* →
upc.*). readPayload sends `proceeding_type_id` only when the
user picked a value; prefillForm restores the saved id via
dataset.preselect to survive the async populate race.
* frontend/src/client/projects-new.ts — kicks off populate on
DOMContentLoaded.
* frontend/src/client/projects-detail.ts — edit-modal preload
now awaits populate; the local loadProceedingTypes duplicate
(used by the counterclaim modal) is replaced by the shared
helper so both surfaces hit the same cache.
Part 2 — Actionable empty-state on the Schriftsätze tab
* frontend/src/projects-detail.tsx — the static <p> empty-state
becomes a div with a "Projekt bearbeiten" button.
* frontend/src/client/projects-detail.ts — openEditModal now
accepts an optional focusFieldID; the new
#project-submissions-edit-cta click handler calls it with
"project-proceeding-type-id" so the picker is scrolled into
view and focused right after the modal opens.
i18n: new keys projects.field.proceeding_type{,.unset,.hint} and
projects.detail.submissions.empty.no_proceeding.cta; reworded
no_proceeding copy to match the new "edit the project" CTA.
Backend already validates via validateProceedingTypeCategory
(mig 087/088 fristenrechner-category guard). Added
TestProjectService_CaseProceedingTypePicker exercising both the
happy and reject paths through a `case`-typed Create.
Manual test path: open any case project → Edit → the Verfahrenstyp
picker shows below Aktenzeichen → save → the Schriftsätze tab now
lists the submission codes. Clicking the empty-state CTA jumps
straight to the picker.
224 lines
11 KiB
TypeScript
224 lines
11 KiB
TypeScript
import { h } from "../jsx";
|
|
import { FIRM } from "../branding";
|
|
|
|
// Reusable Project form body. Renders the field grid only — the surrounding
|
|
// <form>, submit/cancel buttons and the form-msg paragraph belong to the
|
|
// caller because /projects/new and the edit modal want different button
|
|
// labels and submit behaviour.
|
|
//
|
|
// Field IDs are intentionally identical to the ones used historically on
|
|
// /projects/new so the shared client module client/project-form.ts can read
|
|
// them via getElementById on either page (the two forms never coexist on a
|
|
// single page).
|
|
export function ProjectFormFields(): string {
|
|
return (
|
|
<div className="project-form-fields">
|
|
<div className="form-field">
|
|
<label htmlFor="project-type" data-i18n="projects.field.type">Typ</label>
|
|
<select id="project-type" required>
|
|
<option value="" disabled selected data-i18n="projects.field.type.choose">Bitte wählen…</option>
|
|
<option value="client" data-i18n="projects.type.client">Mandant (Wurzel)</option>
|
|
<option value="litigation" data-i18n="projects.type.litigation">Streitsache</option>
|
|
<option value="patent" data-i18n="projects.type.patent">Patent</option>
|
|
<option value="case" data-i18n="projects.type.case">Verfahren</option>
|
|
<option value="project" data-i18n="projects.type.project">Projekt (generisch)</option>
|
|
<option value="other" data-i18n="projects.type.other">Sonstiges</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="form-field" id="projekt-parent-wrap" style="display:none">
|
|
<label htmlFor="projekt-parent-input" data-i18n="projects.field.parent">Übergeordnetes Projekt</label>
|
|
<input
|
|
type="text"
|
|
id="projekt-parent-input"
|
|
placeholder="Titel eingeben, um ein Überprojekt zu suchen..."
|
|
data-i18n-placeholder="projects.field.parent.placeholder"
|
|
autocomplete="off"
|
|
/>
|
|
<input type="hidden" id="projekt-parent-id" />
|
|
<div id="projekt-parent-suggestions" className="collab-suggestions" />
|
|
<p className="form-hint" data-i18n="projects.field.parent.hint">
|
|
Leer lassen für ein Wurzel-Projekt (typisch: Mandant).
|
|
</p>
|
|
</div>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-title" data-i18n="projects.field.title">Titel</label>
|
|
<input
|
|
type="text"
|
|
id="project-title"
|
|
required
|
|
placeholder="z.B. Siemens AG | Siemens v. Huawei | EP 1 234 567"
|
|
data-i18n-placeholder="projects.field.title.placeholder"
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-ref" data-i18n="projects.field.reference">Interne Referenz (optional)</label>
|
|
<input
|
|
type="text"
|
|
id="project-ref"
|
|
placeholder={`z.B. ${FIRM}-2026-0042`}
|
|
data-i18n-placeholder="projects.field.reference.placeholder"
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="project-client-number" data-i18n="projects.field.client_number">Client-Nr. (6 Ziffern)</label>
|
|
<input
|
|
type="text"
|
|
id="project-client-number"
|
|
pattern="[0-9]{6}"
|
|
maxLength={6}
|
|
placeholder="001234"
|
|
/>
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="project-matter-number" data-i18n="projects.field.matter_number">Matter-Nr. (6 Ziffern)</label>
|
|
<input
|
|
type="text"
|
|
id="project-matter-number"
|
|
pattern="[0-9]{6}"
|
|
maxLength={6}
|
|
placeholder="000567"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<p className="form-hint" data-i18n="projects.field.clientmatter.hint">
|
|
{`${FIRM}-Billing-Nummern. Format CCCCCC.MMMMMM. Client-Nr. wird an Unterprojekte vererbt
|
|
(überschreibbar).`}
|
|
</p>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-billing-ref" data-i18n="projects.field.billing_reference">Billing-Referenz (optional)</label>
|
|
<input
|
|
type="text"
|
|
id="project-billing-ref"
|
|
placeholder="z.B. PO-2026-0815"
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-netdocs" data-i18n="projects.field.netdocuments_url">netDocuments-URL (optional)</label>
|
|
<input
|
|
type="url"
|
|
id="project-netdocs"
|
|
placeholder="https://netdocs.example.com/..."
|
|
/>
|
|
</div>
|
|
|
|
{/* Client-specific */}
|
|
<div className="projekt-fields projekt-fields-client" id="fields-client" style="display:none">
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="project-industry" data-i18n="projects.field.industry">Branche</label>
|
|
<input type="text" id="project-industry" placeholder="z.B. industrial" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="project-country" data-i18n="projects.field.country">Land (ISO-2)</label>
|
|
<input type="text" id="project-country" maxLength={2} placeholder="DE" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Patent-specific */}
|
|
<div className="projekt-fields projekt-fields-patent" id="fields-patent" style="display:none">
|
|
<div className="form-field">
|
|
<label htmlFor="project-patent-number" data-i18n="projects.field.patent_number">Patentnummer</label>
|
|
<input type="text" id="project-patent-number" placeholder="EP 1 234 567" />
|
|
</div>
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="project-filing-date" data-i18n="projects.field.filing_date">Anmeldetag</label>
|
|
<input type="date" id="project-filing-date" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="project-grant-date" data-i18n="projects.field.grant_date">Erteilungstag</label>
|
|
<input type="date" id="project-grant-date" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Litigation-specific */}
|
|
<div className="projekt-fields projekt-fields-litigation" id="fields-litigation" style="display:none">
|
|
<div className="form-field">
|
|
<label htmlFor="project-opponent-code" data-i18n="projects.field.opponent_code">Gegner-Kürzel</label>
|
|
<input
|
|
type="text"
|
|
id="project-opponent-code"
|
|
maxLength={16}
|
|
pattern="[A-Z0-9-]{1,16}"
|
|
placeholder="OPNT"
|
|
data-i18n-placeholder="projects.field.opponent_code.placeholder"
|
|
/>
|
|
<p className="form-hint" data-i18n="projects.field.opponent_code.hint">
|
|
Kurzes Kürzel der Gegenseite (Grossbuchstaben, Ziffern, Bindestriche, max. 16 Zeichen). Wird als mittleres Segment in automatisch abgeleiteten Projekt-Codes verwendet (z.B. EXMPL.OPNT.567.INF.CFI).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Case-specific */}
|
|
<div className="projekt-fields projekt-fields-case" id="fields-case" style="display:none">
|
|
<div className="form-field-row">
|
|
<div className="form-field">
|
|
<label htmlFor="project-court" data-i18n="projects.field.court">Gericht</label>
|
|
<input type="text" id="project-court" placeholder="UPC_CFI_Munich" />
|
|
</div>
|
|
<div className="form-field">
|
|
<label htmlFor="project-case-number" data-i18n="projects.field.case_number">Aktenzeichen (Gericht)</label>
|
|
<input type="text" id="project-case-number" placeholder="UPC_CFI_123/2026" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-proceeding-type-id" data-i18n="projects.field.proceeding_type">Verfahrenstyp</label>
|
|
<select id="project-proceeding-type-id">
|
|
<option value="" data-i18n="projects.field.proceeding_type.unset">(nicht gesetzt)</option>
|
|
</select>
|
|
<p className="form-hint" data-i18n="projects.field.proceeding_type.hint">
|
|
Bestimmt, welche Schriftsätze-Vorlagen für dieses Verfahren angezeigt werden.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-our-side" data-i18n="projects.field.client_role">Mandantenrolle</label>
|
|
<select id="project-our-side">
|
|
<option value="" data-i18n="projects.field.client_role.unset">Unbekannt</option>
|
|
<optgroup data-i18n-label="projects.field.client_role.group.active" label="Aktiv (wir greifen an)">
|
|
<option value="claimant" data-i18n="projects.field.client_role.claimant">Klägerseite</option>
|
|
<option value="applicant" data-i18n="projects.field.client_role.applicant">Antragsteller</option>
|
|
<option value="appellant" data-i18n="projects.field.client_role.appellant">Berufungsführer</option>
|
|
</optgroup>
|
|
<optgroup data-i18n-label="projects.field.client_role.group.reactive" label="Reaktiv (wir verteidigen)">
|
|
<option value="defendant" data-i18n="projects.field.client_role.defendant">Beklagtenseite</option>
|
|
<option value="respondent" data-i18n="projects.field.client_role.respondent">Antragsgegner</option>
|
|
</optgroup>
|
|
<optgroup data-i18n-label="projects.field.client_role.group.other" label="Dritte / Sonstige">
|
|
<option value="third_party" data-i18n="projects.field.client_role.third_party">Streithelfer / Dritter</option>
|
|
<option value="other" data-i18n="projects.field.client_role.other">Sonstige Beteiligte</option>
|
|
</optgroup>
|
|
</select>
|
|
<p className="form-hint" data-i18n="projects.field.client_role.hint">
|
|
Bestimmt die Voreinstellung der Perspektive im Fristenrechner-Determinator: Aktiv → Klägerseite, Reaktiv → Beklagtenseite. Lässt sich dort jederzeit überschreiben.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-description" data-i18n="projects.field.description">Notizen</label>
|
|
<textarea id="project-description" rows={4} placeholder="Kurznotizen zum Projekt (optional)..." data-i18n-placeholder="projects.field.description.placeholder" />
|
|
</div>
|
|
|
|
<div className="form-field">
|
|
<label htmlFor="project-status" data-i18n="projects.field.status">Status</label>
|
|
<select id="project-status">
|
|
<option value="active" data-i18n="projects.filter.status.active">Aktiv</option>
|
|
<option value="closed" data-i18n="projects.filter.status.closed">Abgeschlossen</option>
|
|
<option value="archived" data-i18n="projects.filter.status.archived">Archiviert</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|