import { useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import type { ApiSettings } from "../../types"; import Button from "../../components/Button"; import Card from "../../components/Card"; import ConfirmDialog from "../../components/ConfirmDialog"; import FormField from "../../components/FormField"; import LoadingFrame from "../../components/LoadingFrame"; import MetricCard from "../../components/MetricCard"; import PageTitle from "../../components/PageTitle"; import StatusBadge from "../../components/StatusBadge"; import DismissibleAlert from "../../components/DismissibleAlert"; import DataGrid, { type DataGridColumn } from "../../components/table/DataGrid"; import { lockCampaignVersionPermanently, lockCampaignVersionTemporarily, unlockCampaignVersionUserLock, updateCampaignMetadata, type CampaignVersionListItem, } from "../../api/campaigns"; import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData"; import { canUnlockValidationVersion, formatDateTime, isFinalLockedVersion, isPermanentUserLockedVersion, isTemporaryUserLockedVersion, isVersionReadyForDelivery, summaryValue, } from "./utils/campaignView"; 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 }) { const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId, { includeSummary: true }); const campaign = data.campaign; const versions = useMemo(() => data.versions.slice().sort((a, b) => (b.version_number ?? 0) - (a.version_number ?? 0)), [data.versions]); const [identity, setIdentity] = useState({ external_id: "", name: "", status: "", description: "" }); const [identityDirty, setIdentityDirty] = useState(false); const [savingIdentity, setSavingIdentity] = useState(false); const [pendingLockAction, setPendingLockAction] = useState(null); const [lockBusy, setLockBusy] = useState(false); const [message, setMessage] = useState(""); useEffect(() => { if (!campaign || identityDirty) return; setIdentity({ external_id: campaign.external_id ?? "", name: campaign.name ?? "", status: campaign.status ?? "", description: campaign.description ?? "", }); }, [campaign, identityDirty]); function patchIdentity(key: keyof typeof identity, value: string) { setIdentity((current) => ({ ...current, [key]: value })); setIdentityDirty(true); setMessage(""); } async function saveIdentity() { if (!campaign || savingIdentity || !identityDirty) return; setSavingIdentity(true); setError(""); setMessage(""); try { await updateCampaignMetadata(settings, campaign.id, { external_id: identity.external_id, name: identity.name, status: identity.status, description: identity.description, }); setIdentityDirty(false); await reload(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setSavingIdentity(false); } } async function applyLockAction() { const pending = pendingLockAction; if (!pending || lockBusy) return; setLockBusy(true); setError(""); setMessage(""); try { if (pending.action === "temporary") { await lockCampaignVersionTemporarily(settings, campaignId, pending.version.id); setMessage(`Version #${pending.version.version_number} temporarily locked.`); } else if (pending.action === "unlock") { await unlockCampaignVersionUserLock(settings, campaignId, pending.version.id); setMessage(`Temporary lock removed from version #${pending.version.version_number}.`); } else { await lockCampaignVersionPermanently(settings, campaignId, pending.version.id); setMessage(`Version #${pending.version.version_number} permanently locked.`); } setPendingLockAction(null); await reload(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setLockBusy(false); } } return (
{campaign?.name || "Overview"}

Campaign overview · version-independent identity and version history

{error && {error}} {message && {message}}
patchIdentity("external_id", event.target.value)} /> patchIdentity("name", event.target.value)} />