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",
title="Create a campaign",
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,
audience=("campaign_manager", "campaign_author"),
required_scopes=("campaigns:campaign:read", "campaigns:campaign:create"),
@@ -131,7 +131,7 @@ CAMPAIGN_USER_DOCUMENTATION = (
"Open Campaigns and select New campaign.",
"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.",
"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.",
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 { Button } 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 { MetricCard } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
@@ -249,18 +249,20 @@ export default function AttachmentsDataPage({ settings, campaignId }: {settings:
return (
<PageLayout
archetype="editor"
mode="workspace"
title="i18n:govoplan-campaign.attachments.6771ade6"
description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading}
error={error}
actions={
<>
{filesModuleInstalled && <Button onClick={() => navigate("/files")}>i18n:govoplan-campaign.manage_files.90a419f7</Button>}
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button>
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!canSave}>i18n:govoplan-campaign.save.efc007a3</Button>
</>
}
actions={<PageActionBar
variant="editor"
dirty={dirty}
saving={loading}
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) ? <>
{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" />}
@@ -2,7 +2,7 @@ import type { ApiSettings } from "../../types";
import { Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
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";
export default function CampaignAuditPage({ settings, campaignId }: {settings: ApiSettings;campaignId: string;}) {
@@ -11,15 +11,18 @@ export default function CampaignAuditPage({ settings, campaignId }: {settings: A
return (
<PageLayout
archetype="detail"
mode="workspace"
title="i18n:govoplan-campaign.audit_log.3cfc5f1c"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />}
loading={loading}
loadingLabel="i18n:govoplan-campaign.loading_audit_data.af52b968"
error={error}
actions={
<Button onClick={() => void reload({ force: true })} disabled={loading}>Discard</Button>
}
actions={<PageActionBar
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">
<ActionBlockerHint
@@ -1,7 +1,7 @@
import { useMemo, useRef } from "react";
import type { ApiSettings } from "../../types";
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 { LoadingFrame } from "@govoplan/core-webui";
import LockedVersionNotice from "./components/LockedVersionNotice";
@@ -151,17 +151,19 @@ export default function CampaignFieldsPage({ settings, campaignId }: {settings:
return (
<PageLayout
archetype="editor"
mode="workspace"
title="i18n:govoplan-campaign.fields.e8b68527"
description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading}
error={error}
actions={
<>
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button>
<Button variant="primary" onClick={saveFields} disabled={!canSave}>i18n:govoplan-campaign.save.efc007a3</Button>
</>
}
actions={<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: saveFields, disabled: !canSave && dirty, disabledReason: !canSave && dirty ? "Resolve the current field-definition blocker before saving." : undefined }}
/>}
notices={(localError || fieldNameWarning || locked) ? <>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
{fieldNameWarning && <DismissibleAlert tone="warning" resetKey={fieldNameWarning} floating>{fieldNameWarning}</DismissibleAlert>}
@@ -1,7 +1,7 @@
import type { ApiSettings } from "../../types";
import { Card } 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 { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
import { asRecord, getCampaignJson } from "./utils/campaignView";
@@ -16,18 +16,19 @@ export default function CampaignJsonView({ settings, campaignId }: {settings: Ap
return (
<PageLayout
archetype="detail"
mode="workspace"
title="i18n:govoplan-campaign.json.031a4e76"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />}
loading={loading}
loadingLabel="i18n:govoplan-campaign.loading_json.812c7a50"
error={error}
actions={
<>
<Button onClick={() => void reload({ force: true })} disabled={loading}>Discard</Button>
<Button onClick={() => downloadJson(filename, campaignJson)} disabled={!version}>i18n:govoplan-campaign.download_json.d296a30a</Button>
</>
}
actions={<PageActionBar
variant="detail"
refreshable
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={
<DismissibleAlert tone="warning" dismissible={false}>
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 { StatusBadge } 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 { createNewCampaign, listCampaignsDelta, type CampaignDeltaResponse } from "../../api/campaigns";
import type { CampaignListItem } from "../../types";
@@ -143,20 +143,21 @@ export default function CampaignListPage({ settings }: {settings: ApiSettings;})
return (
<PageLayout
archetype="collection"
mode="workspace"
className="campaigns-page"
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>}
headerLoading={loading}
error={error}
actions={
<>
<Button onClick={() => void load(null)} disabled={loading}>Discard</Button>
<Button variant="primary" onClick={create} disabled={creating}>
actions={<PageActionBar
variant="collection"
refreshable
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"}
</Button>
</>
}
</Button>}
/>}
>
<Card title="i18n:govoplan-campaign.campaigns.01a23a28">
@@ -10,7 +10,7 @@ import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { LoadingFrame } 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 { ToggleSwitch } 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 (
<PageLayout
archetype="editor"
mode="workspace"
title={campaign?.name || "i18n:govoplan-campaign.overview.0efc2e6b"}
description={<p className="mono-small">i18n:govoplan-campaign.campaign_overview_version_independent_identity_a.ebaf1113</p>}
headerLoading={loading}
error={error}
success={message}
actions={
<>
actions={<PageActionBar
variant="editor"
dirty={identityDirty}
saving={savingIdentity}
primaryActions={<>
{canCopy && data.currentVersion && <Button
onClick={() => void prepareLifecycleAction("copy_campaign", data.currentVersion ?? undefined)}
disabled={loading || savingIdentity || lockBusy || lifecycleBusy || identityDirty}
@@ -356,6 +360,8 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
<CalendarClock size={16} aria-hidden="true" />
Schedule
</Button>}
</>}
destructiveActions={<>
{canDelete && <Button
variant="danger"
onClick={() => void prepareLifecycleAction("delete_campaign")}
@@ -372,10 +378,10 @@ export default function CampaignOverviewPage({ settings, auth, campaignId }: {se
<Archive size={16} aria-hidden="true" />
i18n:govoplan-campaign.archive_campaign.26dcfb8a
</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">
@@ -22,7 +22,7 @@ import { DataGrid, type DataGridColumn, type DataGridListOption, type DataGridQu
import { Dialog } from "@govoplan/core-webui";
import { DismissibleAlert } 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 VersionLine from "./components/VersionLine";
import { LoadingFrame, TableActionGroup, ToggleSwitch, i18nMessage } from "@govoplan/core-webui";
@@ -447,19 +447,22 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
return (
<PageLayout
archetype="detail"
mode="workspace"
title="i18n:govoplan-campaign.report.ee45c303"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at ?? data.summary?.generated_at} />}
headerLoading={loading}
error={error || actionError}
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={() => 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">
<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 { FormField } 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 { PolicyRow } from "@govoplan/core-webui";
import { PolicyTable } from "@govoplan/core-webui";
@@ -179,17 +179,19 @@ export default function GlobalSettingsPage({ settings, auth, campaignId, view =
return (
<PageLayout
archetype="editor"
mode="workspace"
title={pageTitle}
description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading}
error={error}
actions={
<>
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button>
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!dirty || locked || !draft}>{dirty ? "i18n:govoplan-campaign.save_now.3989b7c0" : "i18n:govoplan-campaign.saved.c0ae8f6e"}</Button>
</>
}
actions={<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: () => 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) ? <>
{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" />}
@@ -9,6 +9,7 @@ import { FormGrid,
LoadingFrame,
MailServerFolderLookupResultView,
MetricCard,
PageActionBar,
PageLayout,
PasswordField,
ToggleSwitch,
@@ -328,17 +329,27 @@ export default function MailSettingsPage({ settings, campaignId, view = "setting
return (
<PageLayout
archetype={isPolicyView ? "detail" : "editor"}
mode="workspace"
title={isPolicyView ? "i18n:govoplan-campaign.mail_policy.3eb5d32a" : "i18n:govoplan-campaign.mail_settings.19e07f55"}
description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading}
error={error}
actions={
<>
{!isPolicyView && <Button variant="primary" onClick={() => void saveDraft("manual")} disabled={!canSave}>{dirty ? "i18n:govoplan-campaign.save_now.3989b7c0" : "i18n:govoplan-campaign.saved.c0ae8f6e"}</Button>}
<Button onClick={() => void discardDraft()} disabled={loading}>i18n:govoplan-campaign.discard.36fff63c</Button>
</>
}
actions={isPolicyView ? (
<PageActionBar
variant="detail"
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) ? <>
{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" />}
@@ -43,7 +43,7 @@ import { DataGrid, type DataGridQueryState } from "@govoplan/core-webui";
import { DismissibleAlert } from "@govoplan/core-webui";
import { ConfirmDialog } 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 { ToggleSwitch } from "@govoplan/core-webui";
import { i18nMessage } from "@govoplan/core-webui";
@@ -1365,20 +1365,21 @@ export default function ReviewSendPage({
return (
<PageLayout
archetype="workspace"
mode="workspace"
className="review-send-page"
title="i18n:govoplan-campaign.review_send.1627617d"
description={<VersionLine version={version} versions={data.versions} loadedAt={version?.updated_at} />}
headerLoading={loading || Boolean(busy)}
error={error}
actions={
<>
<Button onClick={() => void refreshQueueStatus(false)} disabled={!version || queueStatusLoading || Boolean(busy)}>
actions={<PageActionBar
variant="workspace"
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"}
</Button>
<Button onClick={() => void reload({ force: true })} disabled={loading || Boolean(busy)}>Discard</Button>
</>
}
</Button>}
/>}
notices={(message || (version && (historicalVersion || readyForDelivery || userLockedVersion || finalVersion))) ? <>
{message && <DismissibleAlert tone="info" resetKey={message} floating>{message}</DismissibleAlert>}
{version && (historicalVersion || readyForDelivery || userLockedVersion || finalVersion) &&
@@ -16,7 +16,7 @@ import { Card } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui";
import { FormField } 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 { DismissibleAlert, SegmentedControl, ToggleSwitch, i18nMessage } from "@govoplan/core-webui";
import { WysiwygEditor, type WysiwygEditorHandle } from "@govoplan/core-webui/wysiwyg";
@@ -412,19 +412,21 @@ export default function TemplateDataPage({ settings, campaignId }: {settings: Ap
return (
<PageLayout
archetype="editor"
mode="workspace"
title="i18n:govoplan-campaign.template.3ec1ae06"
description={<VersionLine version={version} versions={data.versions} status={saveState} />}
headerLoading={loading}
error={error}
success={contentLibraryNotice}
actions={
<>
<Button onClick={() => window.location.assign("/templates")}>i18n:govoplan-campaign.manage_templates.23688071</Button>
<Button onClick={() => void discardDraft()} disabled={loading}>Discard</Button>
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!dirty || locked || !draft}>i18n:govoplan-campaign.save.efc007a3</Button>
</>
}
actions={<PageActionBar
variant="editor"
dirty={dirty}
saving={loading}
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) ? <>
{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" />}
@@ -1,5 +1,5 @@
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 { CampaignVersionDetail, CampaignVersionListItem } from "../../../api/campaigns";
import LockedVersionNotice from "./LockedVersionNotice";
@@ -44,18 +44,20 @@ export default function CampaignDraftPageScaffold({
}: CampaignDraftPageScaffoldProps) {
return (
<PageLayout
archetype="editor"
mode="workspace"
title={title}
description={<VersionLine version={version} versions={versions} status={saveState} />}
loading={loading || !draft}
loadingLabel="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50"
error={error || ""}
actions={
<>
<Button onClick={onReload} disabled={loading}>Discard</Button>
<Button variant="primary" onClick={onSave} disabled={!dirty || locked || !draft}>i18n:govoplan-campaign.save.efc007a3</Button>
</>
}
actions={<PageActionBar
variant="editor"
dirty={dirty}
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) ?
<>
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
@@ -1,5 +1,6 @@
import { Link } from "react-router";
import {
PageActionBar,
PageLayout,
WizardDirectory,
usePlatformUiCapabilities,
@@ -39,13 +40,12 @@ export default function WizardDirectoryPage({
return (
<PageLayout
archetype="collection"
mode="workspace"
title="i18n:govoplan-campaign.guided_workflows"
actions={
<Link className="btn btn-secondary" to="../">
actions={<PageActionBar variant="collection" contextActions={<Link className="btn btn-secondary" to="../">
i18n:govoplan-campaign.back_to_overview.ec986cba
</Link>
}
</Link>} />}
>
<WizardDirectory
entries={entries}
@@ -26,7 +26,7 @@ import {
type DataGridListOption,
type DataGridQueryState
} 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 { MetricCard } from "@govoplan/core-webui";
import {
@@ -757,6 +757,7 @@ export default function OperatorQueuePage({ settings, auth }: {settings: ApiSett
return (
<PageLayout
archetype="collection"
mode="workspace"
title="i18n:govoplan-campaign.operator_queue.72492fb5"
description={
@@ -767,9 +768,11 @@ export default function OperatorQueuePage({ settings, auth }: {settings: ApiSett
}
error={error}
success={message}
actions={
<Button onClick={() => void refreshAll()} disabled={loading || jobsLoading}>i18n:govoplan-campaign.refresh.56e3badc</Button>
}
actions={<PageActionBar
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}
>
@@ -8,6 +8,7 @@ import { ContentGrid,
DataGrid,
LoadingFrame,
MetricCard,
PageActionBar,
PageLayout,
StatusBadge,
TableActionGroup,
@@ -167,19 +168,22 @@ export default function AggregateReportsPage({ settings }: {settings: ApiSetting
return (
<PageLayout
archetype="overview"
mode="workspace"
className="aggregate-reports-page"
title="i18n:govoplan-campaign.reports.88bc3fe3"
description="i18n:govoplan-campaign.privacy_protected_campaign_outcomes_without_reci.5b8a100d"
error={error}
actions={
<Button
actions={<PageActionBar
variant="overview"
refreshable
reloadAction={<Button
onClick={() => void Promise.all([loadCampaigns(), selectedFromUrl ? loadReport(selectedFromUrl) : Promise.resolve()])}
disabled={listLoading || reportLoading}
>
<RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-campaign.refresh.56e3badc
</Button>
}
</Button>}
/>}
>
<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 { FormGrid, ContentGrid, Button } 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 { StatusBadge, TableActionGroup, i18nMessage } from "@govoplan/core-webui";
import { usePlatformLanguage } from "@govoplan/core-webui";
@@ -146,15 +146,14 @@ export default function TemplatesPage() {
helpModuleId="campaign"
>
<PageLayout
archetype="detail"
mode="workspace"
title={selectedTemplate.name}
description={selectedTemplate.description}
actions={
<>
actions={<PageActionBar variant="detail" primaryActions={<>
<Button disabled>i18n:govoplan-campaign.duplicate.972d5737</Button>
<Button variant="primary" disabled>i18n:govoplan-campaign.use_in_campaign.779163bc</Button>
</>
}
</>} />}
>
{active === "overview" && <TemplateOverview template={selectedTemplate} />}
@@ -170,15 +169,14 @@ export default function TemplatesPage() {
return (
<PageLayout
archetype="collection"
className="module-entry-page"
title="i18n:govoplan-campaign.templates.f25b700e"
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 variant="primary" disabled>i18n:govoplan-campaign.export.f3e4fadb</Button>
</>
}
</>} />}
>
<Card