export type OperatorQueueAction = | "details" | "report" | "pause" | "resume" | "retry" | "queue-unsent" | "reconcile" | "cancel"; export type OperatorQueuePermissions = { canRead: boolean; canOpenReport: boolean; canControl: boolean; canRetry: boolean; canQueue: boolean; canReconcile: boolean; }; export type OperatorQueueFacts = { retryable: number; outcomeUnknown: number; queueableUnattempted: number; pausable: number; paused: number; cancellable: number; }; export const OPERATOR_QUEUE_REASON = { permissionDenied: "i18n:govoplan-campaign.you_do_not_have_permission_for_this_action.dfcfbad6", noQueued: "i18n:govoplan-campaign.no_queued_jobs_can_be_paused.acaa0b4e", noPaused: "i18n:govoplan-campaign.no_paused_jobs_can_be_resumed.869b6275", noFailed: "i18n:govoplan-campaign.no_failed_jobs_are_eligible_for_retry.e65a69f9", noUnsent: "i18n:govoplan-campaign.no_unsent_jobs_are_available_to_queue.585bfe87", noUnknown: "i18n:govoplan-campaign.no_uncertain_outcomes_require_reconciliation.933bc53c", noCancellable: "i18n:govoplan-campaign.no_cancellable_queued_work_remains.80e31d49" } as const; export type CampaignVersionWorkItem = { campaignId: string; versionId: string; versionNumber: number; isCurrent: boolean; summary: T | null; }; /** Preserve every audit version so work does not disappear when a new draft becomes current. */ export function campaignVersionWorkItems( campaignId: string, currentVersionId: string | null | undefined, versions: Array<{id: string;version_number: number;}>, summaries: Record ): CampaignVersionWorkItem[] { return versions.map((version) => ({ campaignId, versionId: version.id, versionNumber: version.version_number, isCurrent: version.id === currentVersionId, summary: summaries[version.id] ?? null })); } export type CampaignLifecycleTotals = {pausable: number;paused: number;cancellable: number;}; export function campaignLifecycleTotals(items: CampaignLifecycleTotals[]): CampaignLifecycleTotals { return items.reduce((totals, item) => ({ pausable: totals.pausable + item.pausable, paused: totals.paused + item.paused, cancellable: totals.cancellable + item.cancellable }), { pausable: 0, paused: 0, cancellable: 0 }); } export function operatorDeliveryStateCounts( queue: {queued?: number;}, // send.queued intentionally does not contribute: paused queue rows retain that send state. send: {queued?: number;claimed?: number;sending?: number;smtpAccepted?: number;sent?: number;} ) { const queued = queue.queued ?? 0; const claimed = send.claimed ?? 0; const sending = send.sending ?? 0; const completed = (send.smtpAccepted ?? 0) + (send.sent ?? 0); return { queued, claimed, sending, completed, queuedOrActive: queued + claimed + sending }; } export function shouldRefreshOperatorVersionSummary(input: { fullDiscovery: boolean; versionChanged: boolean; selected: boolean; cached: boolean; queuedOrActive: number; needsAttention: number; }): boolean { return input.fullDiscovery || input.versionChanged || !input.cached || input.selected || input.queuedOrActive > 0 || input.needsAttention > 0; } /** * Return the reason each fixed queue action is unavailable. A null result means * the action is permitted and meaningful for the current queue projection. */ export function operatorQueueActionBlocks( facts: OperatorQueueFacts, permissions: OperatorQueuePermissions ): Record { return { details: permissions.canRead ? null : OPERATOR_QUEUE_REASON.permissionDenied, report: permissions.canOpenReport ? null : OPERATOR_QUEUE_REASON.permissionDenied, pause: !permissions.canControl ? OPERATOR_QUEUE_REASON.permissionDenied : facts.pausable > 0 ? null : OPERATOR_QUEUE_REASON.noQueued, resume: !permissions.canControl ? OPERATOR_QUEUE_REASON.permissionDenied : facts.paused > 0 ? null : OPERATOR_QUEUE_REASON.noPaused, retry: !permissions.canRetry ? OPERATOR_QUEUE_REASON.permissionDenied : facts.retryable > 0 ? null : OPERATOR_QUEUE_REASON.noFailed, "queue-unsent": !permissions.canQueue ? OPERATOR_QUEUE_REASON.permissionDenied : facts.queueableUnattempted > 0 ? null : OPERATOR_QUEUE_REASON.noUnsent, reconcile: !permissions.canReconcile ? OPERATOR_QUEUE_REASON.permissionDenied : facts.outcomeUnknown > 0 ? null : OPERATOR_QUEUE_REASON.noUnknown, cancel: !permissions.canControl ? OPERATOR_QUEUE_REASON.permissionDenied : facts.cancellable > 0 ? null : OPERATOR_QUEUE_REASON.noCancellable }; }