Adopt semantic campaign page actions

This commit is contained in:
2026-08-19 14:26:26 +02:00
parent 7bc5e4a35c
commit 039ce35e78
17 changed files with 147 additions and 106 deletions
@@ -119,7 +119,7 @@ CAMPAIGN_USER_DOCUMENTATION = (
topic_id="campaigns.workflow.create-campaign", topic_id="campaigns.workflow.create-campaign",
title="Create a campaign", title="Create a campaign",
summary="Start a governed campaign as an editable draft and complete its purpose and ownership before adding delivery data.", summary="Start a governed campaign as an editable draft and complete its purpose and ownership before adding delivery data.",
body="A new campaign starts with one editable working version. Creating it does not grant access to Mail profiles, managed files, address sources, or delivery actions; those remain separately authorized.", body="A new campaign starts with one editable working version. Campaign editors report saved, unsaved, and saving state in the page action bar; Discard remains immediately before Save, and leaving a dirty draft invokes the shared save-or-discard guard. Destructive campaign lifecycle actions are visually separated from ordinary actions. Creating a campaign does not grant access to Mail profiles, managed files, address sources, or delivery actions; those remain separately authorized.",
order=30, order=30,
audience=("campaign_manager", "campaign_author"), audience=("campaign_manager", "campaign_author"),
required_scopes=("campaigns:campaign:read", "campaigns:campaign:create"), required_scopes=("campaigns:campaign:read", "campaigns:campaign:create"),
@@ -131,7 +131,7 @@ CAMPAIGN_USER_DOCUMENTATION = (
"Open Campaigns and select New campaign.", "Open Campaigns and select New campaign.",
"Use the creation wizard to enter a clear name, identifier, and purpose.", "Use the creation wizard to enter a clear name, identifier, and purpose.",
"Open the new campaign and confirm its owner before adding recipient or delivery data.", "Open the new campaign and confirm its owner before adding recipient or delivery data.",
"Continue through the preparation sections and save the editable working version.", "Continue through the preparation sections; use the stable Discard and Save actions while the bar reports the draft state.",
), ),
outcome="An owned campaign draft with an editable working version.", outcome="An owned campaign draft with an editable working version.",
verification="The Campaign overview shows the new campaign as a draft and identifies its current working version.", verification="The Campaign overview shows the new campaign as a draft and identifies its current working version.",
@@ -5,7 +5,7 @@ import { useGuardedNavigate, usePlatformModuleInstalled, usePlatformUiCapability
import type { ApiSettings } from "../../types"; import type { ApiSettings } from "../../types";
import { Button } from "@govoplan/core-webui"; import { Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui";
import { PageLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import { MetricCard } from "@govoplan/core-webui"; import { MetricCard } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui";
@@ -249,18 +249,20 @@ export default function AttachmentsDataPage({ settings, campaignId }: {settings:
return ( return (
<PageLayout <PageLayout
archetype="editor"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.attachments.6771ade6" title="i18n:govoplan-campaign.attachments.6771ade6"
description={<VersionLine version={version} versions={data.versions} status={saveState} />} description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading} headerLoading={loading}
error={error} error={error}
actions={ actions={<PageActionBar
<> variant="editor"
{filesModuleInstalled && <Button onClick={() => navigate("/files")}>i18n:govoplan-campaign.manage_files.90a419f7</Button>} dirty={dirty}
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button> saving={loading}
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!canSave}>i18n:govoplan-campaign.save.efc007a3</Button> contextActions={filesModuleInstalled ? <Button onClick={() => navigate("/files")}>i18n:govoplan-campaign.manage_files.90a419f7</Button> : undefined}
</> discardAction={{ label: "i18n:govoplan-campaign.discard.36fff63c", onClick: () => void discardDraft() }}
} saveAction={{ label: "i18n:govoplan-campaign.save.efc007a3", onClick: () => saveDraft("manual"), disabled: !canSave && dirty, disabledReason: !canSave && dirty ? "Resolve the current editor blocker before saving." : undefined }}
/>}
notices={(localError || locked) ? <> notices={(localError || locked) ? <>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>} {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
{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" />} {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" />}
@@ -2,7 +2,7 @@ import type { ApiSettings } from "../../types";
import { Button } from "@govoplan/core-webui"; import { Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui";
import VersionLine from "./components/VersionLine"; import VersionLine from "./components/VersionLine";
import { ActionBlockerHint, DocumentationHelpLink, PageLayout } from "@govoplan/core-webui"; import { ActionBlockerHint, DocumentationHelpLink, PageActionBar, PageLayout } from "@govoplan/core-webui";
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData"; import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
export default function CampaignAuditPage({ settings, campaignId }: {settings: ApiSettings;campaignId: string;}) { export default function CampaignAuditPage({ settings, campaignId }: {settings: ApiSettings;campaignId: string;}) {
@@ -11,15 +11,18 @@ export default function CampaignAuditPage({ settings, campaignId }: {settings: A
return ( return (
<PageLayout <PageLayout
archetype="detail"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.audit_log.3cfc5f1c" title="i18n:govoplan-campaign.audit_log.3cfc5f1c"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />} description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />}
loading={loading} loading={loading}
loadingLabel="i18n:govoplan-campaign.loading_audit_data.af52b968" loadingLabel="i18n:govoplan-campaign.loading_audit_data.af52b968"
error={error} error={error}
actions={ actions={<PageActionBar
<Button onClick={() => void reload({ force: true })} disabled={loading}>Discard</Button> variant="detail"
} refreshable
reloadAction={<Button onClick={() => void reload({ force: true })} disabled={loading}>i18n:govoplan-campaign.reload.cce71553</Button>}
/>}
> >
<Card title="i18n:govoplan-campaign.recent_audit_events.7ec32b1d"> <Card title="i18n:govoplan-campaign.recent_audit_events.7ec32b1d">
<ActionBlockerHint <ActionBlockerHint
@@ -1,7 +1,7 @@
import { useMemo, useRef } from "react"; import { useMemo, useRef } from "react";
import type { ApiSettings } from "../../types"; import type { ApiSettings } from "../../types";
import { Button } from "@govoplan/core-webui"; import { Button } from "@govoplan/core-webui";
import { PageLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import LockedVersionNotice from "./components/LockedVersionNotice"; import LockedVersionNotice from "./components/LockedVersionNotice";
@@ -151,17 +151,19 @@ export default function CampaignFieldsPage({ settings, campaignId }: {settings:
return ( return (
<PageLayout <PageLayout
archetype="editor"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.fields.e8b68527" title="i18n:govoplan-campaign.fields.e8b68527"
description={<VersionLine version={version} versions={data.versions} status={saveState} />} description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading} headerLoading={loading}
error={error} error={error}
actions={ actions={<PageActionBar
<> variant="editor"
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button> dirty={dirty}
<Button variant="primary" onClick={saveFields} disabled={!canSave}>i18n:govoplan-campaign.save.efc007a3</Button> saving={loading}
</> discardAction={{ label: "i18n:govoplan-campaign.discard.36fff63c", onClick: () => void discardDraft() }}
} saveAction={{ label: "i18n:govoplan-campaign.save.efc007a3", onClick: saveFields, disabled: !canSave && dirty, disabledReason: !canSave && dirty ? "Resolve the current field-definition blocker before saving." : undefined }}
/>}
notices={(localError || fieldNameWarning || locked) ? <> notices={(localError || fieldNameWarning || locked) ? <>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>} {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
{fieldNameWarning && <DismissibleAlert tone="warning" resetKey={fieldNameWarning} floating>{fieldNameWarning}</DismissibleAlert>} {fieldNameWarning && <DismissibleAlert tone="warning" resetKey={fieldNameWarning} floating>{fieldNameWarning}</DismissibleAlert>}
@@ -1,7 +1,7 @@
import type { ApiSettings } from "../../types"; import type { ApiSettings } from "../../types";
import { Card } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui"; import { Button } from "@govoplan/core-webui";
import { DismissibleAlert, PageLayout } from "@govoplan/core-webui"; import { DismissibleAlert, PageActionBar, PageLayout } from "@govoplan/core-webui";
import VersionLine from "./components/VersionLine"; import VersionLine from "./components/VersionLine";
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData"; import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
import { asRecord, getCampaignJson } from "./utils/campaignView"; import { asRecord, getCampaignJson } from "./utils/campaignView";
@@ -16,18 +16,19 @@ export default function CampaignJsonView({ settings, campaignId }: {settings: Ap
return ( return (
<PageLayout <PageLayout
archetype="detail"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.json.031a4e76" title="i18n:govoplan-campaign.json.031a4e76"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />} description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />}
loading={loading} loading={loading}
loadingLabel="i18n:govoplan-campaign.loading_json.812c7a50" loadingLabel="i18n:govoplan-campaign.loading_json.812c7a50"
error={error} error={error}
actions={ actions={<PageActionBar
<> variant="detail"
<Button onClick={() => void reload({ force: true })} disabled={loading}>Discard</Button> refreshable
<Button onClick={() => downloadJson(filename, campaignJson)} disabled={!version}>i18n:govoplan-campaign.download_json.d296a30a</Button> reloadAction={<Button onClick={() => void reload({ force: true })} disabled={loading}>i18n:govoplan-campaign.reload.cce71553</Button>}
</> primaryActions={<Button onClick={() => downloadJson(filename, campaignJson)} disabled={!version}>i18n:govoplan-campaign.download_json.d296a30a</Button>}
} />}
notices={ notices={
<DismissibleAlert tone="warning" dismissible={false}> <DismissibleAlert tone="warning" dismissible={false}>
This expert view contains the complete authorized campaign configuration, This expert view contains the complete authorized campaign configuration,
@@ -7,7 +7,7 @@ import { Card } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui"; import { Button } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui"; import { StatusBadge } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import { PageLayout, TableActionGroup, i18nMessage, useGuardedNavigate } from "@govoplan/core-webui"; import { PageActionBar, PageLayout, TableActionGroup, i18nMessage, useGuardedNavigate } from "@govoplan/core-webui";
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui"; import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
import { createNewCampaign, listCampaignsDelta, type CampaignDeltaResponse } from "../../api/campaigns"; import { createNewCampaign, listCampaignsDelta, type CampaignDeltaResponse } from "../../api/campaigns";
import type { CampaignListItem } from "../../types"; import type { CampaignListItem } from "../../types";
@@ -143,20 +143,21 @@ export default function CampaignListPage({ settings }: {settings: ApiSettings;})
return ( return (
<PageLayout <PageLayout
archetype="collection"
mode="workspace" mode="workspace"
className="campaigns-page" className="campaigns-page"
title="i18n:govoplan-campaign.all_campaigns.2bd1ee3a" title="i18n:govoplan-campaign.all_campaigns.2bd1ee3a"
description={<p className="mono-small">{lastLoadedAt ? i18nMessage("i18n:govoplan-campaign.last_loaded_value.35ef046a", { value0: lastLoadedAt }) : "i18n:govoplan-campaign.not_loaded_yet.9968c191"}</p>} description={<p className="mono-small">{lastLoadedAt ? i18nMessage("i18n:govoplan-campaign.last_loaded_value.35ef046a", { value0: lastLoadedAt }) : "i18n:govoplan-campaign.not_loaded_yet.9968c191"}</p>}
headerLoading={loading} headerLoading={loading}
error={error} error={error}
actions={ actions={<PageActionBar
<> variant="collection"
<Button onClick={() => void load(null)} disabled={loading}>Discard</Button> refreshable
<Button variant="primary" onClick={create} disabled={creating}> reloadAction={<Button onClick={() => void load(null)} disabled={loading}>i18n:govoplan-campaign.reload.cce71553</Button>}
createAction={<Button variant="primary" onClick={create} disabled={creating}>
{creating ? "i18n:govoplan-campaign.creating.94d7d8ee" : "i18n:govoplan-campaign.new_campaign.aaf9a8a4"} {creating ? "i18n:govoplan-campaign.creating.94d7d8ee" : "i18n:govoplan-campaign.new_campaign.aaf9a8a4"}
</Button> </Button>}
</> />}
}
> >
<Card title="i18n:govoplan-campaign.campaigns.01a23a28"> <Card title="i18n:govoplan-campaign.campaigns.01a23a28">
@@ -10,7 +10,7 @@ import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import { MetricCard } from "@govoplan/core-webui"; import { MetricCard } from "@govoplan/core-webui";
import { PageLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui"; import { StatusBadge } from "@govoplan/core-webui";
import { ToggleSwitch } from "@govoplan/core-webui"; import { ToggleSwitch } from "@govoplan/core-webui";
import { DismissibleAlert, TableActionGroup, hasScope, i18nMessage, useGuardedNavigate, useUnsavedDraftGuard } from "@govoplan/core-webui"; import { DismissibleAlert, TableActionGroup, hasScope, i18nMessage, useGuardedNavigate, useUnsavedDraftGuard } from "@govoplan/core-webui";
@@ -334,14 +334,18 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
return ( return (
<PageLayout <PageLayout
archetype="editor"
mode="workspace" mode="workspace"
title={campaign?.name || "i18n:govoplan-campaign.overview.0efc2e6b"} title={campaign?.name || "i18n:govoplan-campaign.overview.0efc2e6b"}
description={<p className="mono-small">i18n:govoplan-campaign.campaign_overview_version_independent_identity_a.ebaf1113</p>} description={<p className="mono-small">i18n:govoplan-campaign.campaign_overview_version_independent_identity_a.ebaf1113</p>}
headerLoading={loading} headerLoading={loading}
error={error} error={error}
success={message} success={message}
actions={ actions={<PageActionBar
<> variant="editor"
dirty={identityDirty}
saving={savingIdentity}
primaryActions={<>
{canCopy && data.currentVersion && <Button {canCopy && data.currentVersion && <Button
onClick={() => void prepareLifecycleAction("copy_campaign", data.currentVersion ?? undefined)} onClick={() => void prepareLifecycleAction("copy_campaign", data.currentVersion ?? undefined)}
disabled={loading || savingIdentity || lockBusy || lifecycleBusy || identityDirty} disabled={loading || savingIdentity || lockBusy || lifecycleBusy || identityDirty}
@@ -356,6 +360,8 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
<CalendarClock size={16} aria-hidden="true" /> <CalendarClock size={16} aria-hidden="true" />
Schedule Schedule
</Button>} </Button>}
</>}
destructiveActions={<>
{canDelete && <Button {canDelete && <Button
variant="danger" variant="danger"
onClick={() => void prepareLifecycleAction("delete_campaign")} onClick={() => void prepareLifecycleAction("delete_campaign")}
@@ -372,10 +378,10 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
<Archive size={16} aria-hidden="true" /> <Archive size={16} aria-hidden="true" />
i18n:govoplan-campaign.archive_campaign.26dcfb8a i18n:govoplan-campaign.archive_campaign.26dcfb8a
</Button>} </Button>}
<Button onClick={() => void discardOverview()} disabled={loading || savingIdentity || lockBusy}>Discard</Button> </>}
<Button variant="primary" onClick={() => void saveIdentity()} disabled={!campaign || !identityDirty || savingIdentity}>{savingIdentity ? "i18n:govoplan-campaign.saving.56a2285c" : "i18n:govoplan-campaign.save.efc007a3"}</Button> discardAction={{ label: "i18n:govoplan-campaign.discard.36fff63c", onClick: () => void discardOverview(), disabled: loading || lockBusy }}
</> saveAction={{ label: "i18n:govoplan-campaign.save.efc007a3", onClick: () => void saveIdentity(), disabled: !campaign && identityDirty, disabledReason: !campaign && identityDirty ? "The campaign identity is not available." : undefined }}
} />}
> >
<LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_campaign_overview.ffa1adf0"> <LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_campaign_overview.ffa1adf0">
@@ -22,7 +22,7 @@ import { DataGrid, type DataGridColumn, type DataGridListOption, type DataGridQu
import { Dialog } from "@govoplan/core-webui"; import { Dialog } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui"; import { DismissibleAlert } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui";
import { PageLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui"; import { StatusBadge } from "@govoplan/core-webui";
import VersionLine from "./components/VersionLine"; import VersionLine from "./components/VersionLine";
import { LoadingFrame, TableActionGroup, ToggleSwitch, i18nMessage } from "@govoplan/core-webui"; import { LoadingFrame, TableActionGroup, ToggleSwitch, i18nMessage } from "@govoplan/core-webui";
@@ -447,19 +447,22 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
return ( return (
<PageLayout <PageLayout
archetype="detail"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.report.ee45c303" title="i18n:govoplan-campaign.report.ee45c303"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at ?? data.summary?.generated_at} />} description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at ?? data.summary?.generated_at} />}
headerLoading={loading} headerLoading={loading}
error={error || actionError} error={error || actionError}
success={actionMessage} success={actionMessage}
actions={ actions={<PageActionBar
<> variant="detail"
refreshable
reloadAction={<Button onClick={() => void reloadAll()} disabled={loading || jobsLoading}>i18n:govoplan-campaign.reload.cce71553</Button>}
primaryActions={<>
<Button onClick={() => void exportCsv()} disabled={busyAction === "csv"}>i18n:govoplan-campaign.download_csv.eaa216ad</Button> <Button onClick={() => void exportCsv()} disabled={busyAction === "csv"}>i18n:govoplan-campaign.download_csv.eaa216ad</Button>
<Button onClick={() => setEmailOpen(true)}>i18n:govoplan-campaign.email_report.ee3e7091</Button> <Button onClick={() => setEmailOpen(true)}>i18n:govoplan-campaign.email_report.ee3e7091</Button>
<Button onClick={() => void reloadAll()} disabled={loading || jobsLoading}>Discard</Button> </>}
</> />}
}
> >
<LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_report_data.0908ade5"> <LoadingFrame loading={loading} label="i18n:govoplan-campaign.loading_report_data.0908ade5">
<ContentGrid columns={2} collapseAt="workspace" className=""> <ContentGrid columns={2} collapseAt="workspace" className="">
@@ -10,7 +10,7 @@ import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui"; import { DismissibleAlert } from "@govoplan/core-webui";
import { PageLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import { PolicyRow } from "@govoplan/core-webui"; import { PolicyRow } from "@govoplan/core-webui";
import { PolicyTable } from "@govoplan/core-webui"; import { PolicyTable } from "@govoplan/core-webui";
@@ -179,17 +179,19 @@ export default function GlobalSettingsPage({ settings, auth, campaignId, view =
return ( return (
<PageLayout <PageLayout
archetype="editor"
mode="workspace" mode="workspace"
title={pageTitle} title={pageTitle}
description={<VersionLine version={version} versions={data.versions} status={saveState} />} description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading} headerLoading={loading}
error={error} error={error}
actions={ actions={<PageActionBar
<> variant="editor"
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button> dirty={dirty}
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!dirty || locked || !draft}>{dirty ? "i18n:govoplan-campaign.save_now.3989b7c0" : "i18n:govoplan-campaign.saved.c0ae8f6e"}</Button> saving={loading}
</> discardAction={{ label: "i18n:govoplan-campaign.discard.36fff63c", onClick: () => void discardDraft() }}
} saveAction={{ label: "i18n:govoplan-campaign.save.efc007a3", onClick: () => saveDraft("manual"), disabled: (locked || !draft) && dirty, disabledReason: locked && dirty ? "This campaign version is locked." : !draft && dirty ? "The campaign draft is not available." : undefined }}
/>}
notices={(localError || locked) ? <> notices={(localError || locked) ? <>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>} {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
{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" />} {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" />}
@@ -9,6 +9,7 @@ import { FormGrid,
LoadingFrame, LoadingFrame,
MailServerFolderLookupResultView, MailServerFolderLookupResultView,
MetricCard, MetricCard,
PageActionBar,
PageLayout, PageLayout,
PasswordField, PasswordField,
ToggleSwitch, ToggleSwitch,
@@ -328,17 +329,27 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
return ( return (
<PageLayout <PageLayout
archetype={isPolicyView ? "detail" : "editor"}
mode="workspace" mode="workspace"
title={isPolicyView ? "i18n:govoplan-campaign.mail_policy.3eb5d32a" : "i18n:govoplan-campaign.mail_settings.19e07f55"} title={isPolicyView ? "i18n:govoplan-campaign.mail_policy.3eb5d32a" : "i18n:govoplan-campaign.mail_settings.19e07f55"}
description={<VersionLine version={version} versions={data.versions} status={saveState} />} description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading} headerLoading={loading}
error={error} error={error}
actions={ actions={isPolicyView ? (
<> <PageActionBar
{!isPolicyView && <Button variant="primary" onClick={() => void saveDraft("manual")} disabled={!canSave}>{dirty ? "i18n:govoplan-campaign.save_now.3989b7c0" : "i18n:govoplan-campaign.saved.c0ae8f6e"}</Button>} variant="detail"
<Button onClick={() => void discardDraft()} disabled={loading}>i18n:govoplan-campaign.discard.36fff63c</Button> refreshable
</> reloadAction={<Button onClick={() => void reload({ force: true })} disabled={loading}>i18n:govoplan-campaign.reload.cce71553</Button>}
} />
) : (
<PageActionBar
variant="editor"
dirty={dirty}
saving={loading}
discardAction={{ label: "i18n:govoplan-campaign.discard.36fff63c", onClick: () => void discardDraft() }}
saveAction={{ label: "i18n:govoplan-campaign.save.efc007a3", onClick: () => void saveDraft("manual"), disabled: !canSave && dirty, disabledReason: !canSave && dirty ? "Resolve the current mail-settings blocker before saving." : undefined }}
/>
)}
notices={(localError || locked) ? <> notices={(localError || locked) ? <>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>} {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
{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" />} {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" />}
@@ -43,7 +43,7 @@ import { DataGrid, type DataGridQueryState } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui"; import { DismissibleAlert } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui"; import { ConfirmDialog } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import { PageLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui"; import { StatusBadge } from "@govoplan/core-webui";
import { ToggleSwitch } from "@govoplan/core-webui"; import { ToggleSwitch } from "@govoplan/core-webui";
import { i18nMessage } from "@govoplan/core-webui"; import { i18nMessage } from "@govoplan/core-webui";
@@ -1365,20 +1365,21 @@ export default function ReviewSendPage({
return ( return (
<PageLayout <PageLayout
archetype="workspace"
mode="workspace" mode="workspace"
className="review-send-page" className="review-send-page"
title="i18n:govoplan-campaign.review_send.1627617d" title="i18n:govoplan-campaign.review_send.1627617d"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />} description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />}
headerLoading={loading || Boolean(busy)} headerLoading={loading || Boolean(busy)}
error={error} error={error}
actions={ actions={<PageActionBar
<> variant="workspace"
<Button onClick={() => void refreshQueueStatus(false)} disabled={!version || queueStatusLoading || Boolean(busy)}> refreshable
reloadAction={<Button onClick={() => void reload({ force: true })} disabled={loading || Boolean(busy)}>i18n:govoplan-campaign.reload.cce71553</Button>}
primaryActions={<Button onClick={() => void refreshQueueStatus(false)} disabled={!version || queueStatusLoading || Boolean(busy)}>
{queueStatusLoading ? "i18n:govoplan-campaign.refreshing_status.d8965739" : "i18n:govoplan-campaign.refresh_status.ade15a52"} {queueStatusLoading ? "i18n:govoplan-campaign.refreshing_status.d8965739" : "i18n:govoplan-campaign.refresh_status.ade15a52"}
</Button> </Button>}
<Button onClick={() => void reload({ force: true })} disabled={loading || Boolean(busy)}>Discard</Button> />}
</>
}
notices={(message || (version && (historicalVersion || readyForDelivery || userLockedVersion || finalVersion))) ? <> notices={(message || (version && (historicalVersion || readyForDelivery || userLockedVersion || finalVersion))) ? <>
{message && <DismissibleAlert tone="info" resetKey={message} floating>{message}</DismissibleAlert>} {message && <DismissibleAlert tone="info" resetKey={message} floating>{message}</DismissibleAlert>}
{version && (historicalVersion || readyForDelivery || userLockedVersion || finalVersion) && {version && (historicalVersion || readyForDelivery || userLockedVersion || finalVersion) &&
@@ -16,7 +16,7 @@ import { Card } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui"; import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui";
import { FieldLabel } from "@govoplan/core-webui"; import { FieldLabel } from "@govoplan/core-webui";
import { PageLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import { DismissibleAlert, SegmentedControl, ToggleSwitch, i18nMessage } from "@govoplan/core-webui"; import { DismissibleAlert, SegmentedControl, ToggleSwitch, i18nMessage } from "@govoplan/core-webui";
import { WysiwygEditor, type WysiwygEditorHandle } from "@govoplan/core-webui/wysiwyg"; import { WysiwygEditor, type WysiwygEditorHandle } from "@govoplan/core-webui/wysiwyg";
@@ -412,19 +412,21 @@ export default function TemplateDataPage({ settings, campaignId }: {settings: Ap
return ( return (
<PageLayout <PageLayout
archetype="editor"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.template.3ec1ae06" title="i18n:govoplan-campaign.template.3ec1ae06"
description={<VersionLine version={version} versions={data.versions} status={saveState} />} description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading} headerLoading={loading}
error={error} error={error}
success={contentLibraryNotice} success={contentLibraryNotice}
actions={ actions={<PageActionBar
<> variant="editor"
<Button onClick={() => window.location.assign("/templates")}>i18n:govoplan-campaign.manage_templates.23688071</Button> dirty={dirty}
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button> saving={loading}
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!dirty || locked || !draft}>i18n:govoplan-campaign.save.efc007a3</Button> contextActions={<Button onClick={() => window.location.assign("/templates")}>i18n:govoplan-campaign.manage_templates.23688071</Button>}
</> discardAction={{ label: "i18n:govoplan-campaign.discard.36fff63c", onClick: () => void discardDraft() }}
} saveAction={{ label: "i18n:govoplan-campaign.save.efc007a3", onClick: () => saveDraft("manual"), disabled: (locked || !draft) && dirty, disabledReason: locked && dirty ? "This campaign version is locked." : !draft && dirty ? "The campaign draft is not available." : undefined }}
/>}
notices={(localError || locked) ? <> notices={(localError || locked) ? <>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>} {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
{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" />} {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" />}
@@ -1,5 +1,5 @@
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { Button, DismissibleAlert, PageLayout } from "@govoplan/core-webui"; import { DismissibleAlert, PageActionBar, PageLayout } from "@govoplan/core-webui";
import type { ApiSettings } from "../../../types"; import type { ApiSettings } from "../../../types";
import type { CampaignVersionDetail, CampaignVersionListItem } from "../../../api/campaigns"; import type { CampaignVersionDetail, CampaignVersionListItem } from "../../../api/campaigns";
import LockedVersionNotice from "./LockedVersionNotice"; import LockedVersionNotice from "./LockedVersionNotice";
@@ -44,18 +44,20 @@ export default function CampaignDraftPageScaffold({
}: CampaignDraftPageScaffoldProps) { }: CampaignDraftPageScaffoldProps) {
return ( return (
<PageLayout <PageLayout
archetype="editor"
mode="workspace" mode="workspace"
title={title} title={title}
description={<VersionLine version={version} versions={versions} status={saveState} />} description={<VersionLine version={version} versions={versions} status={saveState} />}
loading={loading || !draft} loading={loading || !draft}
loadingLabel="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50" loadingLabel="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50"
error={error || ""} error={error || ""}
actions={ actions={<PageActionBar
<> variant="editor"
<Button onClick={onReload} disabled={loading}>Discard</Button> dirty={dirty}
<Button variant="primary" onClick={onSave} disabled={!dirty || locked || !draft}>i18n:govoplan-campaign.save.efc007a3</Button> saving={loading}
</> discardAction={{ label: "i18n:govoplan-campaign.discard.36fff63c", onClick: onReload }}
} saveAction={{ label: "i18n:govoplan-campaign.save.efc007a3", onClick: onSave, disabled: (locked || !draft) && dirty, disabledReason: locked && dirty ? "This campaign version is locked." : !draft && dirty ? "The campaign draft is not available." : undefined }}
/>}
notices={(localError || locked) ? notices={(localError || locked) ?
<> <>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>} {localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
@@ -1,5 +1,6 @@
import { Link } from "react-router"; import { Link } from "react-router";
import { import {
PageActionBar,
PageLayout, PageLayout,
WizardDirectory, WizardDirectory,
usePlatformUiCapabilities, usePlatformUiCapabilities,
@@ -39,13 +40,12 @@ export default function WizardDirectoryPage({
return ( return (
<PageLayout <PageLayout
archetype="collection"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.guided_workflows" title="i18n:govoplan-campaign.guided_workflows"
actions={ actions={<PageActionBar variant="collection" contextActions={<Link className="btn btn-secondary" to="../">
<Link className="btn btn-secondary" to="../">
i18n:govoplan-campaign.back_to_overview.ec986cba i18n:govoplan-campaign.back_to_overview.ec986cba
</Link> </Link>} />}
}
> >
<WizardDirectory <WizardDirectory
entries={entries} entries={entries}
@@ -26,7 +26,7 @@ import {
type DataGridListOption, type DataGridListOption,
type DataGridQueryState type DataGridQueryState
} from "@govoplan/core-webui"; } from "@govoplan/core-webui";
import { DismissibleAlert, PageLayout } from "@govoplan/core-webui"; import { DismissibleAlert, PageActionBar, PageLayout } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui";
import { MetricCard } from "@govoplan/core-webui"; import { MetricCard } from "@govoplan/core-webui";
import { import {
@@ -757,6 +757,7 @@ export default function OperatorQueuePage({ settings, auth }: {settings: ApiSett
return ( return (
<PageLayout <PageLayout
archetype="collection"
mode="workspace" mode="workspace"
title="i18n:govoplan-campaign.operator_queue.72492fb5" title="i18n:govoplan-campaign.operator_queue.72492fb5"
description={ description={
@@ -767,9 +768,11 @@ export default function OperatorQueuePage({ settings, auth }: {settings: ApiSett
} }
error={error} error={error}
success={message} success={message}
actions={ actions={<PageActionBar
<Button onClick={() => void refreshAll()} disabled={loading || jobsLoading}>i18n:govoplan-campaign.refresh.56e3badc</Button> variant="collection"
} refreshable
reloadAction={<Button onClick={() => void refreshAll()} disabled={loading || jobsLoading}>i18n:govoplan-campaign.refresh.56e3badc</Button>}
/>}
notices={jobsError ? <DismissibleAlert tone="danger" resetKey={jobsError} floating>{jobsError}</DismissibleAlert> : undefined} notices={jobsError ? <DismissibleAlert tone="danger" resetKey={jobsError} floating>{jobsError}</DismissibleAlert> : undefined}
> >
@@ -8,6 +8,7 @@ import { ContentGrid,
DataGrid, DataGrid,
LoadingFrame, LoadingFrame,
MetricCard, MetricCard,
PageActionBar,
PageLayout, PageLayout,
StatusBadge, StatusBadge,
TableActionGroup, TableActionGroup,
@@ -167,19 +168,22 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
return ( return (
<PageLayout <PageLayout
archetype="overview"
mode="workspace" mode="workspace"
className="aggregate-reports-page" className="aggregate-reports-page"
title="i18n:govoplan-campaign.reports.88bc3fe3" title="i18n:govoplan-campaign.reports.88bc3fe3"
description="i18n:govoplan-campaign.privacy_protected_campaign_outcomes_without_reci.5b8a100d" description="i18n:govoplan-campaign.privacy_protected_campaign_outcomes_without_reci.5b8a100d"
error={error} error={error}
actions={ actions={<PageActionBar
<Button variant="overview"
refreshable
reloadAction={<Button
onClick={() => void Promise.all([loadCampaigns(), selectedFromUrl ? loadReport(selectedFromUrl) : Promise.resolve()])} onClick={() => void Promise.all([loadCampaigns(), selectedFromUrl ? loadReport(selectedFromUrl) : Promise.resolve()])}
disabled={listLoading || reportLoading} disabled={listLoading || reportLoading}
> >
<RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-campaign.refresh.56e3badc <RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-campaign.refresh.56e3badc
</Button> </Button>}
} />}
> >
<Card title="i18n:govoplan-campaign.campaign_reports_available_to_you.f14fa403"> <Card title="i18n:govoplan-campaign.campaign_reports_available_to_you.f14fa403">
@@ -3,7 +3,7 @@ import { useMemo, useState } from "react";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui"; import { FormGrid, ContentGrid, Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui";
import { PageLayout, WorkspaceLayout } from "@govoplan/core-webui"; import { PageActionBar, PageLayout, WorkspaceLayout } from "@govoplan/core-webui";
import { FieldLabel } from "@govoplan/core-webui"; import { FieldLabel } from "@govoplan/core-webui";
import { StatusBadge, TableActionGroup, i18nMessage } from "@govoplan/core-webui"; import { StatusBadge, TableActionGroup, i18nMessage } from "@govoplan/core-webui";
import { usePlatformLanguage } from "@govoplan/core-webui"; import { usePlatformLanguage } from "@govoplan/core-webui";
@@ -146,15 +146,14 @@ export default function TemplatesPage() {
helpModuleId="campaign" helpModuleId="campaign"
> >
<PageLayout <PageLayout
archetype="detail"
mode="workspace" mode="workspace"
title={selectedTemplate.name} title={selectedTemplate.name}
description={selectedTemplate.description} description={selectedTemplate.description}
actions={ actions={<PageActionBar variant="detail" primaryActions={<>
<>
<Button disabled>i18n:govoplan-campaign.duplicate.972d5737</Button> <Button disabled>i18n:govoplan-campaign.duplicate.972d5737</Button>
<Button variant="primary" disabled>i18n:govoplan-campaign.use_in_campaign.779163bc</Button> <Button variant="primary" disabled>i18n:govoplan-campaign.use_in_campaign.779163bc</Button>
</> </>} />}
}
> >
{active === "overview" && <TemplateOverview template={selectedTemplate} />} {active === "overview" && <TemplateOverview template={selectedTemplate} />}
@@ -170,15 +169,14 @@ export default function TemplatesPage() {
return ( return (
<PageLayout <PageLayout
archetype="collection"
className="module-entry-page" className="module-entry-page"
title="i18n:govoplan-campaign.templates.f25b700e" title="i18n:govoplan-campaign.templates.f25b700e"
description="i18n:govoplan-campaign.reusable_message_templates_open_a_template_to_ed.792e2afb" description="i18n:govoplan-campaign.reusable_message_templates_open_a_template_to_ed.792e2afb"
actions={ actions={<PageActionBar variant="collection" contextActions={<>
<>
<Button disabled>i18n:govoplan-campaign.import.d6fbc9d2</Button> <Button disabled>i18n:govoplan-campaign.import.d6fbc9d2</Button>
<Button variant="primary" disabled>i18n:govoplan-campaign.export.f3e4fadb</Button> <Button variant="primary" disabled>i18n:govoplan-campaign.export.f3e4fadb</Button>
</> </>} />}
}
> >
<Card <Card