import { useEffect, useMemo, useState } from "react"; import type { ApiSettings, AuthInfo } from "../../types"; import { getCampaignCalendarInvitationCatalog, getCampaignPostboxCatalog, type CampaignCalendarCatalog, type CampaignPostboxCatalog } from "../../api/campaigns"; 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 { PageActionBar, PageLayout } from "@govoplan/core-webui"; import { LoadingFrame } from "@govoplan/core-webui"; import { PolicyRow } from "@govoplan/core-webui"; import { PolicyTable } from "@govoplan/core-webui"; import LockedVersionNotice from "./components/LockedVersionNotice"; import CampaignAccessCard from "./components/CampaignAccessCard"; import VersionLine from "./components/VersionLine"; import { ToggleSwitch } from "@govoplan/core-webui"; import { hasScope } from "@govoplan/core-webui"; import { RetentionPolicyEditor } from "@govoplan/core-webui"; import { usePlatformModuleInstalled } from "@govoplan/core-webui"; import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData"; import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor"; import { asRecord, isAuditLockedVersion } from "./utils/campaignView"; import { cloneJson, getBool, getNumber, getText, updateNested } from "./utils/draftEditor"; import { getDraftFields } from "./utils/fieldDefinitions"; import PostboxTargetsDialog, { normalizePostboxTargets } from "./components/PostboxTargetsDialog"; const behaviorOptions = ["block", "ask", "drop", "continue", "warn"]; type EditorState = Record; type SettingsView = "settings" | "policy"; type GlobalSettingsPageProps = { settings: ApiSettings; auth: AuthInfo; campaignId: string; view?: SettingsView; }; export default function GlobalSettingsPage({ settings, auth, campaignId, view = "settings" }: GlobalSettingsPageProps) { const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId); const [editorState, setEditorState] = useState({}); const [postboxCatalog, setPostboxCatalog] = useState({ available: false, postboxes: [], templates: [], organization_units: [] }); const [calendarCatalog, setCalendarCatalog] = useState({ available: false, calendars: [] }); const [postboxTargetsOpen, setPostboxTargetsOpen] = useState(false); const postboxModuleInstalled = usePlatformModuleInstalled("postbox"); const calendarModuleInstalled = usePlatformModuleInstalled("calendar"); const mailModuleInstalled = usePlatformModuleInstalled("mail"); const isPolicyView = view === "policy"; const version = data.currentVersion; const locked = isAuditLockedVersion(version, data.campaign?.current_version_id); const { draft, displayDraft, dirty, saveState, localError, patch, markDirty, discardDraft, saveDraft } = useCampaignDraftEditor({ settings, campaignId, version, locked, reload, setError, currentStep: isPolicyView ? "policies" : "global-settings", unsavedTitle: isPolicyView ? "i18n:govoplan-campaign.unsaved_campaign_policy_changes.181f3426" : "i18n:govoplan-campaign.unsaved_campaign_settings.0555d713", unsavedMessage: isPolicyView ? "i18n:govoplan-campaign.campaign_policies_have_unsaved_changes_save_them.7b07827f" : "i18n:govoplan-campaign.campaign_settings_have_unsaved_changes_save_them.c1c5f65f", extraPayload: () => ({ editor_state: editorState }), onLoaded: (loadedVersion) => setEditorState(cloneJson(loadedVersion.editor_state ?? {})), onSaved: (savedVersion) => setEditorState(cloneJson(savedVersion.editor_state ?? editorState)) }); const validationPolicy = asRecord(displayDraft.validation_policy); const attachments = asRecord(displayDraft.attachments); const delivery = asRecord(displayDraft.delivery); const rateLimit = asRecord(delivery.rate_limit); const retry = asRecord(delivery.retry); const postboxDelivery = asRecord(delivery.postbox); const calendarInvitation = asRecord(delivery.calendar_invitation); const fieldDefinitions = useMemo( () => getDraftFields(displayDraft), [displayDraft] ); const statusTracking = asRecord(displayDraft.status_tracking); const optIns = asRecord(editorState.opt_ins); const canReadRetentionPolicy = hasScope(auth, "admin:policies:read"); const canWriteRetentionPolicy = hasScope(auth, "admin:policies:write"); const pageTitle = isPolicyView ? "i18n:govoplan-campaign.campaign_policies.0b5de1f5" : "i18n:govoplan-campaign.campaign_settings.efffec26"; useEffect(() => { if (!postboxModuleInstalled) { setPostboxCatalog({ available: false, postboxes: [], templates: [], organization_units: [] }); return; } let cancelled = false; void getCampaignPostboxCatalog(settings, campaignId) .then((catalog) => { if (!cancelled) setPostboxCatalog(catalog); }) .catch(() => { if (!cancelled) { setPostboxCatalog({ available: false, postboxes: [], templates: [], organization_units: [] }); } }); return () => { cancelled = true; }; }, [ campaignId, postboxModuleInstalled, settings.accessToken, settings.apiBaseUrl, settings.apiKey ]); useEffect(() => { if (!calendarModuleInstalled) { setCalendarCatalog({ available: false, reason: "The Calendar module is not active.", calendars: [] }); return; } let cancelled = false; void getCampaignCalendarInvitationCatalog(settings, campaignId) .then((catalog) => { if (!cancelled) setCalendarCatalog(catalog); }) .catch((cause) => { if (!cancelled) { setCalendarCatalog({ available: false, reason: cause instanceof Error ? cause.message : String(cause), calendars: [] }); } }); return () => { cancelled = true; }; }, [ campaignId, calendarModuleInstalled, settings.accessToken, settings.apiBaseUrl, settings.apiKey ]); function patchEditor(path: string[], value: unknown) { if (locked) return; setEditorState((current) => updateNested(current, path, value)); markDirty(); } function patchSendWithoutAttachmentsBehavior(value: string) { patch(["attachments", "send_without_attachments_behavior"], value); patch(["attachments", "send_without_attachments"], value !== "block"); } return ( } headerLoading={loading} error={error} actions={ 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 && {localError}} {locked && } : undefined} > {isPolicyView ? <> {canReadRetentionPolicy && } patch(["validation_policy", "missing_required_attachment"], value)} />} effective={behaviorSummary(getText(validationPolicy, "missing_required_attachment", "ask"))} /> patch(["validation_policy", "missing_optional_attachment"], value)} />} effective={behaviorSummary(getText(validationPolicy, "missing_optional_attachment", "warn"))} /> patch(["validation_policy", "ambiguous_attachment_match"], value)} />} effective={behaviorSummary(getText(validationPolicy, "ambiguous_attachment_match", "ask"))} /> patch(["validation_policy", "unsent_attachment_files"], value)} />} effective={behaviorSummary(getText(validationPolicy, "unsent_attachment_files", "warn"))} /> patch(["validation_policy", "missing_email"], value)} options={["block", "drop"]} />} effective={behaviorSummary(getText(validationPolicy, "missing_email", "block"))} /> patch(["validation_policy", "template_error"], value)} options={["block", "drop"]} />} effective={behaviorSummary(getText(validationPolicy, "template_error", "block"))} /> patch(["validation_policy", "ignore_empty_fields"], checked)} />} effective={getBool(validationPolicy, "ignore_empty_fields") ? "i18n:govoplan-campaign.missing_values_render_as_empty_text.0a6bce45" : "i18n:govoplan-campaign.missing_values_remain_visible_and_can_trigger_te.31464ee2"} /> patch(["attachments", "missing_behavior"], value)} />} effective={behaviorSummary(getText(attachments, "missing_behavior", "warn"))} /> patch(["attachments", "ambiguous_behavior"], value)} />} effective={behaviorSummary(getText(attachments, "ambiguous_behavior", "ask"))} /> } effective={behaviorSummary(sendWithoutAttachmentsBehavior(attachments))} /> : <> {data.campaign && } patch(["delivery", "rate_limit", "messages_per_minute"], Number(event.target.value || 1))} /> patch(["delivery", "rate_limit", "concurrency"], Number(event.target.value || 1))} /> patch(["delivery", "retry", "max_attempts"], Number(event.target.value || 1))} /> patch(["status_tracking", "enabled"], checked)} /> {postboxModuleInstalled &&
patch(["delivery", "postbox", "classification"], event.target.value)} /> {!postboxCatalog.available && Postbox delivery is installed but its delivery catalog is unavailable. }
}
patch(["delivery", "calendar_invitation", "enabled"], checked)} /> patch(["delivery", "calendar_invitation", "summary_template"], event.target.value)} /> patch(["delivery", "calendar_invitation", "start_at_template"], event.target.value)} /> patch(["delivery", "calendar_invitation", "end_at_template"], event.target.value)} /> patch(["delivery", "calendar_invitation", "timezone"], event.target.value)} /> patch(["delivery", "calendar_invitation", "location_template"], event.target.value)} /> patch( ["delivery", "calendar_invitation", "categories"], event.target.value.split(",").map((value) => value.trim()).filter(Boolean) )} />