chore: sync GovOPlaN module split state
This commit is contained in:
@@ -15,8 +15,8 @@ import FieldValueInput from "./components/FieldValueInput";
|
||||
import { DismissibleAlert } from "@govoplan/core-webui";
|
||||
import DataGrid, { DataGridEmptyAction, DataGridRowActions, type DataGridColumn } from "../../components/table/DataGrid";
|
||||
import { fieldTypeOptions, humanizeFieldName, normalizeFieldType, type CampaignFieldDefinition } from "./utils/fieldDefinitions";
|
||||
import { insertAfter, moveArrayItem } from "@govoplan/core-webui";
|
||||
export default function CampaignFieldsPage({ settings, campaignId }: { settings: ApiSettings; campaignId: string }) {
|
||||
import { insertAfter, moveArrayItem, i18nMessage } from "@govoplan/core-webui";
|
||||
export default function CampaignFieldsPage({ settings, campaignId }: {settings: ApiSettings;campaignId: string;}) {
|
||||
const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId);
|
||||
const fieldValueKeys = useRef<string[]>([]);
|
||||
|
||||
@@ -30,8 +30,8 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
reload,
|
||||
setError,
|
||||
currentStep: "campaign-fields",
|
||||
unsavedTitle: "Unsaved fields",
|
||||
unsavedMessage: "Campaign fields have unsaved changes. Save them before leaving, or discard them and continue.",
|
||||
unsavedTitle: "i18n:govoplan-campaign.unsaved_fields.c0913c13",
|
||||
unsavedMessage: "i18n:govoplan-campaign.campaign_fields_have_unsaved_changes_save_them_b.c7e992f1",
|
||||
transformLoadedDraft: (loadedVersion, loadedDraft) => migrateFieldOverridePolicy(loadedDraft, asRecord(loadedVersion.editor_state)),
|
||||
onLoaded: (_loadedVersion, loadedDraft) => {
|
||||
fieldValueKeys.current = normalizeFields(loadedDraft.fields).map((field) => field.name);
|
||||
@@ -142,7 +142,7 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
const fieldProblem = describeFieldNameProblem(fields);
|
||||
if (fieldProblem) {
|
||||
setLocalError(fieldProblem);
|
||||
setSaveState("Save blocked");
|
||||
setSaveState("i18n:govoplan-campaign.save_blocked.c09570a4");
|
||||
return false;
|
||||
}
|
||||
return saveDraft("manual");
|
||||
@@ -153,39 +153,39 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
<div className="content-pad workspace-data-page">
|
||||
<div className="page-heading split workspace-heading">
|
||||
<div>
|
||||
<PageTitle loading={loading}>Fields</PageTitle>
|
||||
<PageTitle loading={loading}>i18n:govoplan-campaign.fields.e8b68527</PageTitle>
|
||||
<VersionLine version={version} versions={data.versions} status={saveState} />
|
||||
</div>
|
||||
<div className="button-row compact-actions">
|
||||
<Button onClick={reload} disabled={loading}>Reload</Button>
|
||||
<Button variant="primary" onClick={saveFields} disabled={!canSave}>Save</Button>
|
||||
<Button onClick={reload} disabled={loading}>i18n:govoplan-campaign.reload.cce71553</Button>
|
||||
<Button variant="primary" onClick={saveFields} disabled={!canSave}>i18n:govoplan-campaign.save.efc007a3</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
|
||||
{fieldNameWarning && <DismissibleAlert tone="warning" resetKey={fieldNameWarning} floating>{fieldNameWarning}</DismissibleAlert>}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} currentVersionId={data.campaign?.current_version_id} reload={reload} message="This page is read-only for the selected version." />}
|
||||
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} currentVersionId={data.campaign?.current_version_id} reload={reload} message="i18n:govoplan-campaign.this_page_is_read_only_for_the_selected_version.dacf5743" />}
|
||||
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
<LoadingFrame loading={loading || !draft} label="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50">
|
||||
<>
|
||||
<Card title="Campaign fields">
|
||||
<Card title="i18n:govoplan-campaign.campaign_fields.969e7d80">
|
||||
<div className="admin-table-surface campaign-fields-table-surface">
|
||||
<DataGrid
|
||||
id={`campaign-${campaignId}-fields`}
|
||||
rows={fields}
|
||||
columns={fieldColumns({ locked, fields, globalValues, renameField, setField, setGlobalValue, setOverrideAllowed, addField, moveField, deleteField })}
|
||||
getRowKey={(_field, index) => `field-row-${index}`}
|
||||
emptyText="No campaign fields configured yet."
|
||||
emptyAction={<DataGridEmptyAction onAdd={() => addField(-1)} disabled={locked} label="Add first field" />}
|
||||
className="field-editor-table-wrap field-editor-table"
|
||||
/>
|
||||
emptyText="i18n:govoplan-campaign.no_campaign_fields_configured_yet.6772f948"
|
||||
emptyAction={<DataGridEmptyAction onAdd={() => addField(-1)} disabled={locked} label="i18n:govoplan-campaign.add_first_field.9fef7186" />}
|
||||
className="field-editor-table-wrap field-editor-table" />
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
</>
|
||||
</LoadingFrame>
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
type FieldColumnContext = {
|
||||
@@ -203,32 +203,32 @@ type FieldColumnContext = {
|
||||
|
||||
function fieldColumns({ locked, fields, globalValues, renameField, setField, setGlobalValue, setOverrideAllowed, addField, moveField, deleteField }: FieldColumnContext): DataGridColumn<CampaignFieldDefinition>[] {
|
||||
return [
|
||||
{ id: "name", header: "Field ID", width: 190, resizable: true, sortable: true, filterable: true, sticky: "start", render: (field, index) => <input value={field.name} disabled={locked} placeholder="field_name" onChange={(event) => renameField(index, event.target.value)} />, value: (field) => field.name },
|
||||
{ id: "label", header: "Label", width: 210, resizable: true, sortable: true, filterable: true, render: (field, index) => <input value={field.label} disabled={locked} placeholder="Display label" onChange={(event) => setField(index, { label: event.target.value })} />, value: (field) => field.label },
|
||||
{ id: "type", header: "Type", width: 140, sortable: true, filterable: true, columnType: "from-list", list: { options: fieldTypeOptions.map((option) => ({ value: option, label: option })) }, render: (field, index) => <select value={field.type} disabled={locked} onChange={(event) => setField(index, { type: normalizeFieldType(event.target.value) })}>{fieldTypeOptions.map((option) => <option key={option} value={option}>{option}</option>)}</select>, value: (field) => field.type },
|
||||
{ id: "required", header: "Required", width: 140, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "required", label: "Required" }, { value: "optional", label: "Optional" }] }, render: (field, index) => <ToggleSwitch label="Required" checked={field.required} disabled={locked} onChange={(checked) => setField(index, { required: checked })} />, value: (field) => field.required ? "required" : "optional" },
|
||||
{ id: "global_value", header: "Global value", width: 220, resizable: true, filterable: true, render: (field) => <FieldValueInput fieldType={field.type} value={globalValues[field.name]} disabled={locked || !field.name} placeholder="Optional default" onChange={(value) => setGlobalValue(field.name, value)} />, value: (field) => String(globalValues[field.name] ?? "") },
|
||||
{ id: "override", header: "Recipient override", width: 170, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "can override", label: "Can override" }, { value: "locked", label: "Global only" }] }, render: (field, index) => <ToggleSwitch label="Can override" checked={field.can_override} disabled={locked || !field.name} onChange={(checked) => setOverrideAllowed(index, checked)} />, value: (field) => field.can_override ? "can override" : "locked" },
|
||||
{
|
||||
id: "actions",
|
||||
header: "Actions",
|
||||
width: 180,
|
||||
sticky: "end",
|
||||
render: (_field, index) => (
|
||||
<DataGridRowActions
|
||||
disabled={locked}
|
||||
onAddBelow={() => addField(index)}
|
||||
onRemove={() => deleteField(index)}
|
||||
onMoveUp={index > 0 ? () => moveField(index, index - 1) : undefined}
|
||||
onMoveDown={index < fields.length - 1 ? () => moveField(index, index + 1) : undefined}
|
||||
addLabel="Add field below"
|
||||
removeLabel="Remove field"
|
||||
moveUpLabel="Move field up"
|
||||
moveDownLabel="Move field down"
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
{ id: "name", header: "i18n:govoplan-campaign.field_id.4ee26ffd", width: 190, resizable: true, sortable: true, filterable: true, sticky: "start", render: (field, index) => <input value={field.name} disabled={locked} placeholder="field_name" onChange={(event) => renameField(index, event.target.value)} />, value: (field) => field.name },
|
||||
{ id: "label", header: "i18n:govoplan-campaign.label.74341e3c", width: 210, resizable: true, sortable: true, filterable: true, render: (field, index) => <input value={field.label} disabled={locked} placeholder="i18n:govoplan-campaign.display_label.d747868d" onChange={(event) => setField(index, { label: event.target.value })} />, value: (field) => field.label },
|
||||
{ id: "type", header: "i18n:govoplan-campaign.type.3deb7456", width: 140, sortable: true, filterable: true, columnType: "from-list", list: { options: fieldTypeOptions.map((option) => ({ value: option, label: option })) }, render: (field, index) => <select value={field.type} disabled={locked} onChange={(event) => setField(index, { type: normalizeFieldType(event.target.value) })}>{fieldTypeOptions.map((option) => <option key={option} value={option}>{option}</option>)}</select>, value: (field) => field.type },
|
||||
{ id: "required", header: "i18n:govoplan-campaign.required.eed6bfb4", width: 140, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "required", label: "i18n:govoplan-campaign.required.eed6bfb4" }, { value: "optional", label: "i18n:govoplan-campaign.optional.0c6c4102" }] }, render: (field, index) => <ToggleSwitch label="i18n:govoplan-campaign.required.eed6bfb4" checked={field.required} disabled={locked} onChange={(checked) => setField(index, { required: checked })} />, value: (field) => field.required ? "required" : "optional" },
|
||||
{ id: "global_value", header: "i18n:govoplan-campaign.global_value.ab230e7f", width: 220, resizable: true, filterable: true, render: (field) => <FieldValueInput fieldType={field.type} value={globalValues[field.name]} disabled={locked || !field.name} placeholder="i18n:govoplan-campaign.optional_default.e04a3bfc" onChange={(value) => setGlobalValue(field.name, value)} />, value: (field) => String(globalValues[field.name] ?? "") },
|
||||
{ id: "override", header: "i18n:govoplan-campaign.recipient_override.4d84be6d", width: 170, sortable: true, filterable: true, columnType: "from-list", list: { options: [{ value: "can_override", label: "i18n:govoplan-campaign.can_override.c37b61c4" }, { value: "locked", label: "i18n:govoplan-campaign.global_only.4641751c" }] }, render: (field, index) => <ToggleSwitch label="i18n:govoplan-campaign.can_override.c37b61c4" checked={field.can_override} disabled={locked || !field.name} onChange={(checked) => setOverrideAllowed(index, checked)} />, value: (field) => field.can_override ? "can_override" : "locked" },
|
||||
{
|
||||
id: "actions",
|
||||
header: "i18n:govoplan-campaign.actions.c3cd636a",
|
||||
width: 180,
|
||||
sticky: "end",
|
||||
render: (_field, index) =>
|
||||
<DataGridRowActions
|
||||
disabled={locked}
|
||||
onAddBelow={() => addField(index)}
|
||||
onRemove={() => deleteField(index)}
|
||||
onMoveUp={index > 0 ? () => moveField(index, index - 1) : undefined}
|
||||
onMoveDown={index < fields.length - 1 ? () => moveField(index, index + 1) : undefined}
|
||||
addLabel="i18n:govoplan-campaign.add_field_below.c087fdd7"
|
||||
removeLabel="i18n:govoplan-campaign.remove_field.f4e03605"
|
||||
moveUpLabel="i18n:govoplan-campaign.move_field_up.d98ba824"
|
||||
moveDownLabel="i18n:govoplan-campaign.move_field_down.8f27ccb2" />
|
||||
|
||||
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
function normalizeFields(value: unknown): CampaignFieldDefinition[] {
|
||||
@@ -279,7 +279,7 @@ function migrateFieldOverridePolicy(draft: Record<string, unknown>, editorState:
|
||||
function describeFieldNameProblem(fields: CampaignFieldDefinition[]): string {
|
||||
const names = fields.map((field) => field.name.trim());
|
||||
if (names.some((name) => !name)) {
|
||||
return "Field IDs must not be empty before saving.";
|
||||
return "i18n:govoplan-campaign.field_ids_must_not_be_empty_before_saving.3ac7b4ca";
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
@@ -290,7 +290,7 @@ function describeFieldNameProblem(fields: CampaignFieldDefinition[]): string {
|
||||
}
|
||||
|
||||
if (duplicates.size === 0) return "";
|
||||
return `Duplicate field ID${duplicates.size === 1 ? "" : "s"}: ${[...duplicates].sort().join(", ")}. Field IDs must be unique before saving.`;
|
||||
return i18nMessage("i18n:govoplan-campaign.duplicate_field_id_value_value_field_ids_must_be.9343a5e9", { value0: duplicates.size === 1 ? "" : "s", value1: [...duplicates].sort().join(", ") });
|
||||
}
|
||||
|
||||
function uniqueFieldName(fields: CampaignFieldDefinition[]): string {
|
||||
|
||||
Reference in New Issue
Block a user