Migrate Forms Runtime interface patterns

This commit is contained in:
2026-08-03 13:02:19 +02:00
parent b4388b1e1e
commit 07dd35bcc0
9 changed files with 440 additions and 32 deletions
+107 -24
View File
@@ -2,15 +2,20 @@ import { ArrowLeft, ExternalLink, RefreshCw, Save, Send } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useParams } from "react-router";
import {
ActionBlockerHint,
Button,
ConfirmDialog,
DocumentationHelpLink,
DismissibleAlert,
LoadingIndicator,
PageScrollViewport,
StatusBadge,
ToggleSwitch,
hasScope,
i18nMessage,
useGuardedNavigate,
usePlatformLanguage,
useUnsavedDraftGuard,
type PlatformRouteContext
} from "@govoplan/core-webui";
import {
@@ -31,11 +36,17 @@ import {
type FormHandoff,
type ValidationResult
} from "../../api/formsRuntime";
import {
FORMS_RUNTIME_DOCUMENTATION,
FORMS_RUNTIME_FIELD_DOCUMENTATION,
FORMS_RUNTIME_I18N
} from "./interfacePatterns";
export default function FormInstancePage({ settings, auth }: PlatformRouteContext) {
const { instanceId = "" } = useParams();
const navigate = useGuardedNavigate();
const { language } = usePlatformLanguage();
const [instance, setInstance] = useState<FormInstance | null>(null);
const [definition, setDefinition] = useState<FormDefinition | null>(null);
const [history, setHistory] = useState<FormInstance[]>([]);
@@ -50,6 +61,8 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
const [handoffKind, setHandoffKind] = useState<"case" | "workflow">("case");
const [handoffBinding, setHandoffBinding] = useState("");
const [compensating, setCompensating] = useState<FormHandoff | null>(null);
const [confirmingSubmit, setConfirmingSubmit] = useState(false);
const [confirmingHandoff, setConfirmingHandoff] = useState(false);
const load = useCallback(async (signal?: AbortSignal) => {
setLoading(true);
@@ -91,8 +104,13 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
}
}, [definition, handoffKind]);
const editable = instance?.status === "started" || instance?.status === "draft";
const canSave = instance?.status === "draft" && definition?.allow_drafts;
const editableLifecycle = instance?.status === "started" || instance?.status === "draft";
const canParticipate = hasScope(auth, "forms_runtime:submission:participate");
const canWrite = hasScope(auth, "forms_runtime:workspace:write");
const canAdmin = hasScope(auth, "forms_runtime:workspace:admin");
const canEditPermission = canParticipate || canWrite;
const editable = Boolean(editableLifecycle && canEditPermission);
const canSave = Boolean(instance?.status === "draft" && definition?.allow_drafts && canEditPermission);
const changed = useMemo(
() => Boolean(instance && JSON.stringify(values) !== JSON.stringify(instance.values)),
[instance, values]
@@ -106,30 +124,43 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
return grouped;
}, [instance]);
const localized = useMemo(
() => localizeDefinition(definition),
[definition]
() => localizeDefinition(definition, language),
[definition, language]
);
const groups = useMemo(
() => definition ? visibleGroups(definition, values) : [],
[definition, values]
);
const mayHandoff = Boolean(instance && ["submitted", "validated", "needs_review", "accepted"].includes(instance.status) && instance.service_ref);
const canAdmin = hasScope(auth, "forms_runtime:workspace:admin");
const canHandoff = canWrite || canAdmin;
async function save() {
if (!instance || !canSave || !changed || !changeReason.trim()) return;
async function save(): Promise<boolean> {
if (!instance || !canSave || !changed || !changeReason.trim()) return false;
setSaving(true);
setError("");
try {
await saveFormDraft(settings, instance, values, changeReason.trim());
await load();
return true;
} catch (reason) {
setError(reason instanceof Error ? reason.message : "The draft could not be saved.");
return false;
} finally {
setSaving(false);
}
}
useUnsavedDraftGuard({
dirty: Boolean(instance && (changed || changeReason)),
onSave: save,
onDiscard: () => {
setValues(instance?.values ?? {});
setChangeReason("");
},
title: "i18n:govoplan-forms-runtime.unsaved_title",
message: "i18n:govoplan-forms-runtime.unsaved_message"
});
async function submit() {
if (!instance || !editable) return;
setSaving(true);
@@ -195,7 +226,8 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
Forms
</Button>
{definition && <strong>{localized.title}</strong>}
{instance && <StatusBadge status={editable ? "active" : "inactive"} label={humanize(instance.status)} />}
{instance && <StatusBadge status={editableLifecycle ? "active" : "inactive"} label={stateLabel(instance.status)} />}
<DocumentationHelpLink reference={FORMS_RUNTIME_DOCUMENTATION} />
</div>
<PageScrollViewport className="form-instance-viewport">
{error &&
@@ -204,6 +236,23 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
</DismissibleAlert>
}
{loading && <LoadingIndicator label="Loading Form" />}
{!loading && instance && !editable &&
<ActionBlockerHint
tone="info"
reason={canEditPermission ? {
summary: "Read-only Form",
details: FORMS_RUNTIME_I18N.lifecycleReason
} : {
summary: "Read-only Form",
details: FORMS_RUNTIME_I18N.editReason,
requiredAction: FORMS_RUNTIME_I18N.permissionAction,
actor: FORMS_RUNTIME_I18N.permissionActor,
target: FORMS_RUNTIME_I18N.permissionDestination
}}
labels={{ requiredAction: FORMS_RUNTIME_I18N.requiredAction, actor: FORMS_RUNTIME_I18N.actor, target: FORMS_RUNTIME_I18N.destination }}
documentation={FORMS_RUNTIME_DOCUMENTATION}
/>
}
{!loading && instance && definition &&
<div className="form-instance-content">
<section className="form-instance-main">
@@ -226,7 +275,7 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
}}
optionLabels={localized.optionLabels[field.key] ?? {}}
value={values[field.key]}
disabled={!editable || saving}
disabled={!editable || saving}
diagnostics={diagnostics.get(field.key) ?? []}
onChange={(value) => setValues((current) => {
const next = { ...current };
@@ -247,6 +296,7 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
}
{editable &&
<div className="form-instance-actions">
<DocumentationHelpLink reference={FORMS_RUNTIME_FIELD_DOCUMENTATION} />
{canSave &&
<label className="form-change-reason">
<span>Change reason</span>
@@ -260,12 +310,13 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
{canSave &&
<Button
onClick={() => void save()}
disabled={!changed || !changeReason.trim() || saving}>
disabled={!changed || !changeReason.trim() || saving}
disabledReason={saving ? FORMS_RUNTIME_I18N.saving : !changed ? FORMS_RUNTIME_I18N.unchanged : !changeReason.trim() ? FORMS_RUNTIME_I18N.changeReason : undefined}>
<Save size={16} aria-hidden="true" />
Save draft
</Button>
}
<Button variant="primary" onClick={() => void submit()} disabled={saving}>
<Button variant="primary" onClick={() => setConfirmingSubmit(true)} disabled={saving} disabledReason={saving ? FORMS_RUNTIME_I18N.saving : undefined}>
<Send size={16} aria-hidden="true" />
Submit
</Button>
@@ -288,7 +339,7 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
{definition.handoff_kinds.includes("workflow") && <option value="workflow">Start Workflow</option>}
</select>
<input value={handoffBinding} disabled={handoffBusy} onChange={(event) => setHandoffBinding(event.target.value)} placeholder="Target binding (optional)" aria-label="Exact target binding" />
<Button variant="primary" disabled={handoffBusy} onClick={() => void startHandoff()}><Send size={15} aria-hidden="true" />Start</Button>
<Button variant="primary" disabled={handoffBusy || !canHandoff} disabledReason={handoffBusy ? FORMS_RUNTIME_I18N.saving : !canHandoff ? FORMS_RUNTIME_I18N.handoffReason : undefined} onClick={() => setConfirmingHandoff(true)}><Send size={15} aria-hidden="true" />Start</Button>
</div>
}
</div>
@@ -296,13 +347,13 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
<div className="form-handoff-list">
{handoffs.map((item) =>
<div className="form-handoff-row" key={item.effect_id}>
<span><strong>{humanize(item.binding_kind)}</strong><small>{item.binding_reference}</small></span>
<StatusBadge status={item.state === "accepted" || item.state === "reconciled" ? "active" : "inactive"} label={humanize(item.state)} />
<span><strong>{domainLabel(item.binding_kind)}</strong><small>{item.binding_reference}</small></span>
<StatusBadge status={item.state === "accepted" || item.state === "reconciled" ? "active" : "inactive"} label={stateLabel(item.state)} />
{item.last_error && <span className="form-handoff-error">{item.last_error}</span>}
<span className="form-handoff-actions">
{item.href && <Button onClick={() => navigate(item.href!)}><ExternalLink size={15} aria-hidden="true" />Open</Button>}
{item.state === "rejected" && <Button disabled={handoffBusy} onClick={() => void handoffAction(item, "retry")}><RefreshCw size={15} aria-hidden="true" />Retry</Button>}
{item.state === "outcome_unknown" && <Button disabled={handoffBusy} onClick={() => void handoffAction(item, "reconcile")}><RefreshCw size={15} aria-hidden="true" />Reconcile</Button>}
{item.state === "rejected" && <Button disabled={handoffBusy || !canHandoff} disabledReason={handoffBusy ? FORMS_RUNTIME_I18N.saving : !canHandoff ? FORMS_RUNTIME_I18N.handoffReason : undefined} onClick={() => void handoffAction(item, "retry")}><RefreshCw size={15} aria-hidden="true" />Retry</Button>}
{item.state === "outcome_unknown" && <Button disabled={handoffBusy || !canHandoff} disabledReason={handoffBusy ? FORMS_RUNTIME_I18N.saving : !canHandoff ? FORMS_RUNTIME_I18N.handoffReason : undefined} onClick={() => void handoffAction(item, "reconcile")}><RefreshCw size={15} aria-hidden="true" />Reconcile</Button>}
{canAdmin && (item.state === "rejected" || item.state === "outcome_unknown") && <Button variant="danger" disabled={handoffBusy} onClick={() => setCompensating(item)}>Compensate</Button>}
</span>
</div>
@@ -317,9 +368,9 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
<ol>
{events.map((event) =>
<li key={event.event_id}>
<strong>{humanize(event.status)}</strong>
<strong>{stateLabel(event.status)}</strong>
<span>{humanize(event.event_type)}</span>
<time>{formatDateTime(event.occurred_at)}</time>
<time>{formatDateTime(event.occurred_at, language)}</time>
</li>
)}
</ol>
@@ -331,7 +382,7 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
<li key={item.revision}>
<strong>Revision {item.revision}</strong>
<span>{item.change_reason}</span>
<time>{formatDateTime(item.recorded_at)}</time>
<time>{formatDateTime(item.recorded_at, language)}</time>
</li>
)}
</ol>
@@ -341,6 +392,30 @@ export default function FormInstancePage({ settings, auth }: PlatformRouteContex
}
</PageScrollViewport>
</div>
<ConfirmDialog
open={confirmingSubmit}
title="i18n:govoplan-forms-runtime.submit_title"
message="i18n:govoplan-forms-runtime.submit_message"
confirmLabel="Submit"
busy={saving}
onCancel={() => setConfirmingSubmit(false)}
onConfirm={() => {
setConfirmingSubmit(false);
void submit();
}}
/>
<ConfirmDialog
open={confirmingHandoff}
title="i18n:govoplan-forms-runtime.handoff_title"
message={i18nMessage("i18n:govoplan-forms-runtime.handoff_message", { kind: domainLabel(handoffKind) })}
confirmLabel="Start"
busy={handoffBusy}
onCancel={() => setConfirmingHandoff(false)}
onConfirm={() => {
setConfirmingHandoff(false);
void startHandoff();
}}
/>
<ConfirmDialog
open={Boolean(compensating)}
title="Compensate handoff"
@@ -565,7 +640,7 @@ function comparable(left: unknown, right: unknown, compare: (left: number | stri
return false;
}
function localizeDefinition(definition: FormDefinition | null) {
function localizeDefinition(definition: FormDefinition | null, language: string) {
const canonical = {
title: definition?.title ?? "",
description: definition?.description ?? null,
@@ -575,8 +650,8 @@ function localizeDefinition(definition: FormDefinition | null) {
sectionTitles: {} as Record<string, string>
};
if (!definition?.localizations?.length) return canonical;
const browserLocales = navigator.languages.map((value) => value.toLowerCase());
const localization = definition.localizations.find((item) => browserLocales.some((locale) => locale === item.locale.toLowerCase() || locale.startsWith(`${item.locale.toLowerCase()}-`)))
const requestedLocale = language.toLowerCase();
const localization = definition.localizations.find((item) => requestedLocale === item.locale.toLowerCase() || requestedLocale.startsWith(`${item.locale.toLowerCase()}-`))
?? definition.localizations.find((item) => item.locale.toLowerCase() === definition.fallback_locale?.toLowerCase());
if (!localization) return canonical;
return {
@@ -589,10 +664,18 @@ function localizeDefinition(definition: FormDefinition | null) {
};
}
function formatDateTime(value: string): string {
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value));
function formatDateTime(value: string, locale?: string): string {
return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value));
}
function humanize(value: string): string {
return value.replace(/[_:.-]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function stateLabel(value: string): string {
return `i18n:govoplan-forms-runtime.state_${value}`;
}
function domainLabel(value: string): string {
return `i18n:govoplan-forms-runtime.domain_${value}`;
}
+16 -6
View File
@@ -2,20 +2,25 @@ import { RefreshCw } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import {
Button,
DocumentationHelpLink,
DismissibleAlert,
LoadingIndicator,
PageScrollViewport,
StatusBadge,
i18nMessage,
useGuardedNavigate,
usePlatformLanguage,
type PlatformRouteContext
} from "@govoplan/core-webui";
import { listFormInstances, type FormInstance } from "../../api/formsRuntime";
import { FORMS_RUNTIME_DOCUMENTATION, FORMS_RUNTIME_I18N } from "./interfacePatterns";
const OPEN_STATUSES = ["started", "draft", "submitted", "validated", "needs_review"];
export default function FormsRuntimePage({ settings }: PlatformRouteContext) {
const navigate = useGuardedNavigate();
const { language } = usePlatformLanguage();
const [items, setItems] = useState<FormInstance[]>([]);
const [total, setTotal] = useState(0);
const [status, setStatus] = useState("open");
@@ -50,7 +55,7 @@ export default function FormsRuntimePage({ settings }: PlatformRouteContext) {
<main className="forms-runtime-page">
<div className="forms-runtime-shell">
<div className="forms-runtime-toolbar">
<Button onClick={() => void load()} disabled={loading}>
<Button onClick={() => void load()} disabled={loading} disabledReason={loading ? FORMS_RUNTIME_I18N.loading : undefined}>
<RefreshCw size={16} aria-hidden="true" />
Refresh
</Button>
@@ -68,7 +73,8 @@ export default function FormsRuntimePage({ settings }: PlatformRouteContext) {
<option value="archived">Archived</option>
</select>
</label>
<span className="forms-runtime-count">{total} forms</span>
<span className="forms-runtime-count">{i18nMessage("i18n:govoplan-forms-runtime.form_count", { total })}</span>
<DocumentationHelpLink reference={FORMS_RUNTIME_DOCUMENTATION} />
</div>
<PageScrollViewport className="forms-runtime-list-viewport">
{error &&
@@ -93,8 +99,8 @@ export default function FormsRuntimePage({ settings }: PlatformRouteContext) {
<strong>{item.definition_ref.label ?? humanize(item.definition_ref.object_id)}</strong>
<span>Revision {item.definition_ref.version ?? "-"}</span>
</span>
<span>{formatDateTime(item.recorded_at)}</span>
<StatusBadge status={isOpen(item.status) ? "active" : "inactive"} label={humanize(item.status)} />
<span>{formatDateTime(item.recorded_at, language)}</span>
<StatusBadge status={isOpen(item.status) ? "active" : "inactive"} label={stateLabel(item.status)} />
</button>
)}
</div>
@@ -109,10 +115,14 @@ function isOpen(status: string): boolean {
return OPEN_STATUSES.includes(status);
}
function formatDateTime(value: string): string {
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value));
function formatDateTime(value: string, locale?: string): string {
return new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value));
}
function humanize(value: string): string {
return value.replace(/[_:.-]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function stateLabel(value: string): string {
return `i18n:govoplan-forms-runtime.state_${value}`;
}
@@ -0,0 +1,27 @@
import type { DocumentationHelpReference } from "@govoplan/core-webui";
export const FORMS_RUNTIME_DOCUMENTATION = {
topicId: "forms_runtime.submissions",
documentationType: "user"
} satisfies DocumentationHelpReference;
export const FORMS_RUNTIME_FIELD_DOCUMENTATION = {
topicId: "forms_runtime.reference.fields-and-consequences",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const FORMS_RUNTIME_I18N = {
loading: "i18n:govoplan-forms-runtime.loading_reason",
saving: "i18n:govoplan-forms-runtime.saving_reason",
editReason: "i18n:govoplan-forms-runtime.edit_permission_reason",
handoffReason: "i18n:govoplan-forms-runtime.handoff_permission_reason",
lifecycleReason: "i18n:govoplan-forms-runtime.lifecycle_reason",
unchanged: "i18n:govoplan-forms-runtime.unchanged_reason",
changeReason: "i18n:govoplan-forms-runtime.change_reason_required",
requiredAction: "i18n:govoplan-forms-runtime.required_action",
actor: "i18n:govoplan-forms-runtime.responsible_actor",
destination: "i18n:govoplan-forms-runtime.destination",
permissionAction: "i18n:govoplan-forms-runtime.permission_action",
permissionActor: "i18n:govoplan-forms-runtime.permission_actor",
permissionDestination: "i18n:govoplan-forms-runtime.permission_destination"
} as const;
+155
View File
@@ -0,0 +1,155 @@
import type { PlatformTranslations } from "@govoplan/core-webui";
const en = {
"i18n:govoplan-forms-runtime.forms": "Forms",
"i18n:govoplan-forms-runtime.loading_reason": "Form data is still loading.",
"i18n:govoplan-forms-runtime.saving_reason": "A Form operation is still running.",
"i18n:govoplan-forms-runtime.edit_permission_reason": "Your account may read this Form but may not change or submit it.",
"i18n:govoplan-forms-runtime.handoff_permission_reason": "Your account may not create or recover Case and Workflow handoffs.",
"i18n:govoplan-forms-runtime.lifecycle_reason": "This Form is immutable in its current lifecycle state.",
"i18n:govoplan-forms-runtime.unchanged_reason": "There are no changed values to save.",
"i18n:govoplan-forms-runtime.change_reason_required": "Enter a change reason before saving the draft.",
"i18n:govoplan-forms-runtime.required_action": "Required action",
"i18n:govoplan-forms-runtime.responsible_actor": "Responsible actor",
"i18n:govoplan-forms-runtime.destination": "Destination",
"i18n:govoplan-forms-runtime.permission_action": "Ask for the appropriate Forms Runtime permission or assignment.",
"i18n:govoplan-forms-runtime.permission_actor": "An Access administrator or the responsible process owner",
"i18n:govoplan-forms-runtime.permission_destination": "Access role assignments or the assigning workflow",
"i18n:govoplan-forms-runtime.unsaved_title": "Unsaved Form",
"i18n:govoplan-forms-runtime.unsaved_message": "Save or discard the changed Form values before leaving this surface.",
"i18n:govoplan-forms-runtime.submit_title": "Submit Form",
"i18n:govoplan-forms-runtime.submit_message": "Submit this Form? The server validates the exact published definition and records an immutable receipt.",
"i18n:govoplan-forms-runtime.handoff_title": "Start governed handoff",
"i18n:govoplan-forms-runtime.handoff_message": "Start the {kind} handoff? Intent is recorded before execution and an unknown outcome requires reconciliation.",
"i18n:govoplan-forms-runtime.form_count": "{total} forms",
"i18n:govoplan-forms-runtime.state_started": "Started",
"i18n:govoplan-forms-runtime.state_draft": "Draft",
"i18n:govoplan-forms-runtime.state_submitted": "Submitted",
"i18n:govoplan-forms-runtime.state_validated": "Validated",
"i18n:govoplan-forms-runtime.state_needs_review": "Needs review",
"i18n:govoplan-forms-runtime.state_accepted": "Accepted",
"i18n:govoplan-forms-runtime.state_rejected": "Rejected",
"i18n:govoplan-forms-runtime.state_handed_off": "Handed off",
"i18n:govoplan-forms-runtime.state_archived": "Archived",
"i18n:govoplan-forms-runtime.state_reconciled": "Reconciled",
"i18n:govoplan-forms-runtime.state_outcome_unknown": "Outcome unknown",
"i18n:govoplan-forms-runtime.state_requested": "Requested",
"i18n:govoplan-forms-runtime.state_compensated": "Compensated",
"i18n:govoplan-forms-runtime.domain_case": "Case",
"i18n:govoplan-forms-runtime.domain_workflow": "Workflow",
"Forms": "Forms",
"Refresh": "Refresh",
"Status": "Status",
"Open": "Open",
"All": "All",
"Draft": "Draft",
"Submitted": "Submitted",
"Needs review": "Needs review",
"Accepted": "Accepted",
"Rejected": "Rejected",
"Handed off": "Handed off",
"Archived": "Archived",
"Loading forms": "Loading forms",
"No matching Forms.": "No matching Forms.",
"Revision": "Revision",
"Loading Form": "Loading Form",
"Change reason": "Change reason",
"Save draft": "Save draft",
"Submit": "Submit",
"Submission receipt": "Submission receipt",
"Case and workflow handoffs": "Case and workflow handoffs",
"Create Case": "Create Case",
"Start Workflow": "Start Workflow",
"Target binding (optional)": "Target binding (optional)",
"Exact target binding": "Exact target binding",
"Start": "Start",
"No handoff has been requested.": "No handoff has been requested.",
"Open target": "Open target",
"Retry": "Retry",
"Reconcile": "Reconcile",
"Compensate": "Compensate",
"Status history": "Status history",
"Revisions": "Revisions",
"Select": "Select",
"Compensate handoff": "Compensate handoff",
"Confirm absent and compensate": "Confirm absent and compensate",
"Read-only Form": "Read-only Form"
} as const;
const de: Record<keyof typeof en, string> = {
"i18n:govoplan-forms-runtime.forms": "Formulare",
"i18n:govoplan-forms-runtime.loading_reason": "Formulardaten werden noch geladen.",
"i18n:govoplan-forms-runtime.saving_reason": "Eine Formularaktion läuft noch.",
"i18n:govoplan-forms-runtime.edit_permission_reason": "Ihr Konto darf dieses Formular lesen, aber nicht ändern oder absenden.",
"i18n:govoplan-forms-runtime.handoff_permission_reason": "Ihr Konto darf keine Fall- oder Workflow-Übergaben erstellen oder wiederherstellen.",
"i18n:govoplan-forms-runtime.lifecycle_reason": "Dieses Formular ist in seinem aktuellen Lebenszyklus unveränderlich.",
"i18n:govoplan-forms-runtime.unchanged_reason": "Es gibt keine geänderten Werte zu speichern.",
"i18n:govoplan-forms-runtime.change_reason_required": "Geben Sie vor dem Speichern des Entwurfs einen Änderungsgrund ein.",
"i18n:govoplan-forms-runtime.required_action": "Erforderliche Aktion",
"i18n:govoplan-forms-runtime.responsible_actor": "Verantwortliche Stelle",
"i18n:govoplan-forms-runtime.destination": "Ziel",
"i18n:govoplan-forms-runtime.permission_action": "Fordern Sie die passende Formularberechtigung oder Zuweisung an.",
"i18n:govoplan-forms-runtime.permission_actor": "Eine Zugriffsadministration oder die verantwortliche Prozessstelle",
"i18n:govoplan-forms-runtime.permission_destination": "Zugriff und Rollenzuweisungen oder der zuweisende Workflow",
"i18n:govoplan-forms-runtime.unsaved_title": "Ungespeichertes Formular",
"i18n:govoplan-forms-runtime.unsaved_message": "Speichern oder verwerfen Sie die geänderten Formularwerte, bevor Sie diese Oberfläche verlassen.",
"i18n:govoplan-forms-runtime.submit_title": "Formular absenden",
"i18n:govoplan-forms-runtime.submit_message": "Dieses Formular absenden? Der Server prüft die exakte veröffentlichte Definition und erfasst einen unveränderlichen Beleg.",
"i18n:govoplan-forms-runtime.handoff_title": "Geregelte Übergabe starten",
"i18n:govoplan-forms-runtime.handoff_message": "Die Übergabe an {kind} starten? Die Absicht wird vor der Ausführung erfasst; ein unbekanntes Ergebnis muss abgeglichen werden.",
"i18n:govoplan-forms-runtime.form_count": "{total} Formulare",
"i18n:govoplan-forms-runtime.state_started": "Gestartet",
"i18n:govoplan-forms-runtime.state_draft": "Entwurf",
"i18n:govoplan-forms-runtime.state_submitted": "Abgesendet",
"i18n:govoplan-forms-runtime.state_validated": "Validiert",
"i18n:govoplan-forms-runtime.state_needs_review": "Prüfung erforderlich",
"i18n:govoplan-forms-runtime.state_accepted": "Angenommen",
"i18n:govoplan-forms-runtime.state_rejected": "Abgelehnt",
"i18n:govoplan-forms-runtime.state_handed_off": "Übergeben",
"i18n:govoplan-forms-runtime.state_archived": "Archiviert",
"i18n:govoplan-forms-runtime.state_reconciled": "Abgeglichen",
"i18n:govoplan-forms-runtime.state_outcome_unknown": "Ergebnis unbekannt",
"i18n:govoplan-forms-runtime.state_requested": "Angefordert",
"i18n:govoplan-forms-runtime.state_compensated": "Kompensiert",
"i18n:govoplan-forms-runtime.domain_case": "Fall",
"i18n:govoplan-forms-runtime.domain_workflow": "Workflow",
"Forms": "Formulare",
"Refresh": "Aktualisieren",
"Status": "Status",
"Open": "Offen",
"All": "Alle",
"Draft": "Entwurf",
"Submitted": "Abgesendet",
"Needs review": "Prüfung erforderlich",
"Accepted": "Angenommen",
"Rejected": "Abgelehnt",
"Handed off": "Übergeben",
"Archived": "Archiviert",
"Loading forms": "Formulare werden geladen",
"No matching Forms.": "Keine passenden Formulare.",
"Revision": "Revision",
"Loading Form": "Formular wird geladen",
"Change reason": "Änderungsgrund",
"Save draft": "Entwurf speichern",
"Submit": "Absenden",
"Submission receipt": "Übermittlungsbeleg",
"Case and workflow handoffs": "Fall- und Workflow-Übergaben",
"Create Case": "Fall erstellen",
"Start Workflow": "Workflow starten",
"Target binding (optional)": "Zielbindung (optional)",
"Exact target binding": "Exakte Zielbindung",
"Start": "Starten",
"No handoff has been requested.": "Es wurde keine Übergabe angefordert.",
"Open target": "Ziel öffnen",
"Retry": "Erneut versuchen",
"Reconcile": "Abgleichen",
"Compensate": "Kompensieren",
"Status history": "Statusverlauf",
"Revisions": "Revisionen",
"Select": "Auswählen",
"Compensate handoff": "Übergabe kompensieren",
"Confirm absent and compensate": "Fehlen bestätigen und kompensieren",
"Read-only Form": "Schreibgeschütztes Formular"
};
export const generatedTranslations: PlatformTranslations = { en, de };
+4 -2
View File
@@ -1,5 +1,6 @@
import { createElement, lazy } from "react";
import type { PlatformWebModule } from "@govoplan/core-webui";
import { generatedTranslations } from "./i18n/generatedTranslations";
import "./styles/forms-runtime.css";
@@ -12,10 +13,11 @@ const routeScopes = [
export const formsRuntimeModule: PlatformWebModule = {
id: "forms_runtime",
label: "Forms",
label: "i18n:govoplan-forms-runtime.forms",
version: "0.1.14",
dependencies: ["access", "forms"],
optionalDependencies: ["files", "approvals", "workflow_engine", "portal", "cases", "policy", "audit"],
translations: generatedTranslations,
routes: [
{
path: "/forms-runtime",
@@ -35,7 +37,7 @@ export const formsRuntimeModule: PlatformWebModule = {
navItems: [
{
to: "/forms-runtime",
label: "Forms",
label: "i18n:govoplan-forms-runtime.forms",
iconName: "form",
anyOf: routeScopes,
order: 37,
+5
View File
@@ -48,6 +48,11 @@
padding: 16px 18px 24px;
}
.form-instance-viewport > .action-blocker-hint {
max-width: 1280px;
margin: 0 auto 16px;
}
.forms-runtime-list {
overflow: hidden;
border: 1px solid var(--border);