feat(campaign): expose governed archive action
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ExternalLink, LockKeyhole, LockOpen } from "lucide-react";
|
||||
import { Archive, ExternalLink, LockKeyhole, LockOpen } from "lucide-react";
|
||||
import { Link } from "react-router";
|
||||
import type { ApiSettings } from "../../types";
|
||||
import type { ApiSettings, AuthInfo } from "../../types";
|
||||
import { Button } from "@govoplan/core-webui";
|
||||
import { Card } from "@govoplan/core-webui";
|
||||
import { ConfirmDialog } from "@govoplan/core-webui";
|
||||
@@ -10,9 +10,10 @@ import { LoadingFrame } from "@govoplan/core-webui";
|
||||
import { MetricCard } from "@govoplan/core-webui";
|
||||
import { PageTitle } from "@govoplan/core-webui";
|
||||
import { StatusBadge } from "@govoplan/core-webui";
|
||||
import { DismissibleAlert, TableActionGroup, i18nMessage, useGuardedNavigate, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { DismissibleAlert, TableActionGroup, hasScope, i18nMessage, useGuardedNavigate, useUnsavedDraftGuard } from "@govoplan/core-webui";
|
||||
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
|
||||
import {
|
||||
archiveCampaign,
|
||||
lockCampaignVersionPermanently,
|
||||
lockCampaignVersionTemporarily,
|
||||
unlockCampaignVersionUserLock,
|
||||
@@ -39,7 +40,7 @@ const campaignModeOptions = ["draft", "test", "send"];
|
||||
type LockAction = "temporary" | "unlock" | "permanent";
|
||||
type PendingLockAction = {version: CampaignVersionListItem;action: LockAction;} | null;
|
||||
|
||||
export default function CampaignOverviewPage({ settings, campaignId }: {settings: ApiSettings;campaignId: string;}) {
|
||||
export default function CampaignOverviewPage({ settings, auth, campaignId }: {settings: ApiSettings;auth: AuthInfo;campaignId: string;}) {
|
||||
const navigate = useGuardedNavigate();
|
||||
const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId, { includeSummary: true });
|
||||
const campaign = data.campaign;
|
||||
@@ -49,8 +50,11 @@ export default function CampaignOverviewPage({ settings, campaignId }: {settings
|
||||
const [savingIdentity, setSavingIdentity] = useState(false);
|
||||
const [pendingLockAction, setPendingLockAction] = useState<PendingLockAction>(null);
|
||||
const [lockBusy, setLockBusy] = useState(false);
|
||||
const [archiveDialogOpen, setArchiveDialogOpen] = useState(false);
|
||||
const [archiving, setArchiving] = useState(false);
|
||||
const [message, setMessage] = useState("");
|
||||
const versionMetrics = useMemo(() => campaignVersionMetrics(data.currentVersion), [data.currentVersion]);
|
||||
const canArchive = Boolean(campaign) && campaign?.status !== "archived" && hasScope(auth, "campaigns:campaign:archive");
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty: identityDirty,
|
||||
@@ -145,6 +149,23 @@ export default function CampaignOverviewPage({ settings, campaignId }: {settings
|
||||
await reload({ force: true });
|
||||
}
|
||||
|
||||
async function applyArchive() {
|
||||
if (!campaign || archiving) return;
|
||||
setArchiving(true);
|
||||
setError("");
|
||||
setMessage("");
|
||||
try {
|
||||
await archiveCampaign(settings, campaign.id);
|
||||
setArchiveDialogOpen(false);
|
||||
setMessage("i18n:govoplan-campaign.campaign_archived.3f0ca2b7");
|
||||
await reload({ force: true });
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setArchiving(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="content-pad workspace-data-page">
|
||||
<div className="page-heading split workspace-heading">
|
||||
@@ -153,6 +174,14 @@ export default function CampaignOverviewPage({ settings, campaignId }: {settings
|
||||
<p className="mono-small">i18n:govoplan-campaign.campaign_overview_version_independent_identity_a.ebaf1113</p>
|
||||
</div>
|
||||
<div className="button-row compact-actions">
|
||||
{canArchive && <Button
|
||||
variant="danger"
|
||||
onClick={() => setArchiveDialogOpen(true)}
|
||||
disabled={loading || savingIdentity || lockBusy || identityDirty}
|
||||
disabledReason={identityDirty ? "i18n:govoplan-campaign.save_or_discard_overview_changes_before_archiving.413ff9e0" : undefined}>
|
||||
<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>
|
||||
</div>
|
||||
@@ -226,6 +255,16 @@ export default function CampaignOverviewPage({ settings, campaignId }: {settings
|
||||
</Card>
|
||||
</LoadingFrame>
|
||||
|
||||
<ConfirmDialog
|
||||
open={archiveDialogOpen}
|
||||
title="i18n:govoplan-campaign.archive_campaign.26dcfb8a"
|
||||
message="i18n:govoplan-campaign.archive_campaign_confirmation.c0cc62e1"
|
||||
confirmLabel="i18n:govoplan-campaign.archive_campaign.26dcfb8a"
|
||||
tone="danger"
|
||||
busy={archiving}
|
||||
onCancel={() => setArchiveDialogOpen(false)}
|
||||
onConfirm={() => void applyArchive()} />
|
||||
|
||||
<ConfirmDialog
|
||||
open={Boolean(pendingLockAction)}
|
||||
title={lockDialogTitle(pendingLockAction)}
|
||||
|
||||
@@ -89,7 +89,7 @@ function CampaignWorkspaceInner({ settings, auth }: { settings: ApiSettings; aut
|
||||
<SectionSidebar active={active} onSelect={select} />
|
||||
<section className="workspace-content">
|
||||
<Routes>
|
||||
<Route index element={<CampaignOverviewPage settings={settings} campaignId={campaignId || ""} />} />
|
||||
<Route index element={<CampaignOverviewPage settings={settings} auth={auth} campaignId={campaignId || ""} />} />
|
||||
<Route path="data" element={<Navigate to="../recipients" replace />} />
|
||||
<Route path="fields" element={<CampaignFieldsPage settings={settings} campaignId={campaignId || ""} />} />
|
||||
<Route path="recipients" element={<RecipientDataPage settings={settings} campaignId={campaignId || ""} />} />
|
||||
|
||||
Reference in New Issue
Block a user