feat(webui): add campaign metric drill-downs

This commit is contained in:
2026-08-19 18:47:45 +02:00
parent 039ce35e78
commit 8e2f9d743d
17 changed files with 124 additions and 73 deletions
+48 -26
View File
@@ -1287,7 +1287,27 @@ export default function ReviewSendPage({
}
function scrollToStage(id: string) {
document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
revealReviewElement(id);
}
function revealReviewElement(id: string) {
const target = document.getElementById(id);
if (!target) return;
const stage = target.classList.contains("review-flow-stage")
? target
: target.closest<HTMLElement>(".review-flow-stage");
stage?.querySelector<HTMLButtonElement>('.card-collapse-toggle[aria-expanded="false"]')?.click();
window.requestAnimationFrame(() => {
target.scrollIntoView({ behavior: "smooth", block: "start" });
target.focus({ preventScroll: true });
});
}
function showBuiltDetails(filters: Record<string, string> = {}, includeAll = false) {
setShowAllReviewJobs(includeAll);
setReviewQuery({ sort: null, filters });
setReviewPage(1);
window.requestAnimationFrame(() => revealReviewElement("campaign-built-message-details"));
}
const matchedAttachments = numberFrom(attachmentSummary, ["total_matched_files"]);
@@ -1375,7 +1395,7 @@ export default function ReviewSendPage({
actions={<PageActionBar
variant="workspace"
refreshable
reloadAction={<Button onClick={() => void reload({ force: true })} disabled={loading || Boolean(busy)}>i18n:govoplan-campaign.reload.cce71553</Button>}
reloadAction={{ onReload: () => void reload({ force: true }), loading: loading || Boolean(busy) }}
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>}
@@ -1400,9 +1420,9 @@ export default function ReviewSendPage({
<WorkflowStage stage={stages[0]} nextState={stages[1].state} nextConnectorState={stageConnectorState(stages[1])}>
<MetricGrid columns={4} density="compact" spacing="block" minimum="compact">
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.status.bae7d5be" value={validationPresent ? validationOk ? "i18n:govoplan-campaign.passed.271d60f4" : "i18n:govoplan-campaign.needs_attention.a126722e" : "i18n:govoplan-campaign.not_run.9e019cd5"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.blocking.d785c0d4" value={validationPresent ? validationErrors : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.warnings.1430f976" value={validationPresent ? validationWarnings : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.jobs_needing_attention.95613b02" value={cards?.needs_attention ?? "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.blocking.d785c0d4" value={validationPresent ? validationErrors : "—"} drilldown={validationErrors > 0 && visibleValidationIssues.length > 0 ? { label: "i18n:govoplan-campaign.validation_details.aa503267", onActivate: () => revealReviewElement("campaign-validation-details") } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.warnings.1430f976" value={validationPresent ? validationWarnings : "—"} drilldown={validationWarnings > 0 && visibleValidationIssues.length > 0 ? { label: "i18n:govoplan-campaign.validation_details.aa503267", onActivate: () => revealReviewElement("campaign-validation-details") } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.jobs_needing_attention.95613b02" value={cards?.needs_attention ?? "—"} drilldown={hasBuild && Number(cards?.needs_attention ?? 0) > 0 ? { label: "i18n:govoplan-campaign.review_candidates.438b8b57", onActivate: () => showBuiltDetails() } : undefined} />
</MetricGrid>
<ValidationWorkflowGuidance
errors={validationErrors}
@@ -1412,17 +1432,19 @@ export default function ReviewSendPage({
{validationPresent && validationErrors === 0 &&
<p className="review-flow-inline-note is-complete"><Check size={17} aria-hidden="true" /> i18n:govoplan-campaign.no_blocking_validation_exceptions_remain.73186d0d</p>
}
<AttachmentLinkingPreview
preview={attachmentPreview}
loading={attachmentPreviewLoading}
error={attachmentPreviewError}
linking={attachmentLinking}
disabled={!version || readOnlyVersion || readyForDelivery || Boolean(busy)}
onRefresh={() => void reloadAttachmentPreview(false)}
onLink={() => void linkMatchedAttachmentFiles()} />
<div id="campaign-attachment-preview" tabIndex={-1}>
<AttachmentLinkingPreview
preview={attachmentPreview}
loading={attachmentPreviewLoading}
error={attachmentPreviewError}
linking={attachmentLinking}
disabled={!version || readOnlyVersion || readyForDelivery || Boolean(busy)}
onRefresh={() => void reloadAttachmentPreview(false)}
onLink={() => void linkMatchedAttachmentFiles()} />
</div>
{visibleValidationIssues.length > 0 &&
<div className="review-flow-data-section">
<div id="campaign-validation-details" className="review-flow-data-section" tabIndex={-1}>
<h3>i18n:govoplan-campaign.validation_details.aa503267</h3>
<DescriptionList variant="inline">
{visibleValidationIssues.map((issue, index) =>
@@ -1457,13 +1479,13 @@ export default function ReviewSendPage({
<WorkflowStage stage={stages[1]} nextState={stages[2].state} nextConnectorState={stageConnectorState(stages[2])}>
<MetricGrid columns={4} density="compact" spacing="block" minimum="compact">
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.built.a6ad3f82" value={hasBuild ? builtCount : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.blocked.99613c74" value={hasBuild ? buildBlocked : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.need_review.201a4493" value={hasBuild ? buildNeedsReview : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.reviewed.31ef8593" value={hasBuild ? buildReviewProgress.reviewed : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.remaining.cc632b5e" value={hasBuild ? buildReviewProgress.remaining : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.review_candidates.438b8b57" value={reviewJobs.total || "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.attachment_issues.69748336" value={missingAttachments + ambiguousAttachments} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.built.a6ad3f82" value={hasBuild ? builtCount : "—"} drilldown={hasBuild && builtCount > 0 ? { label: "i18n:govoplan-campaign.show_all_messages.1c2107a1", onActivate: () => showBuiltDetails({}, true) } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.blocked.99613c74" value={hasBuild ? buildBlocked : "—"} drilldown={buildBlocked > 0 ? { label: "i18n:govoplan-campaign.review.e29a79fe", onActivate: () => showBuiltDetails({ validation: 'list:["blocked"]' }) } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.need_review.201a4493" value={hasBuild ? buildNeedsReview : "—"} drilldown={buildNeedsReview > 0 ? { label: "i18n:govoplan-campaign.review_candidates.438b8b57", onActivate: () => showBuiltDetails({ validation: 'list:["warning","needs_review"]' }) } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.reviewed.31ef8593" value={hasBuild ? buildReviewProgress.reviewed : "—"} drilldown={buildReviewProgress.reviewed > 0 ? { label: "i18n:govoplan-campaign.reviewed.31ef8593", onActivate: () => showBuiltDetails({ reviewed: 'list:["yes"]' }, true) } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.remaining.cc632b5e" value={hasBuild ? buildReviewProgress.remaining : "—"} drilldown={buildReviewProgress.remaining > 0 ? { label: "i18n:govoplan-campaign.review.e29a79fe", onActivate: () => showBuiltDetails({ reviewed: 'list:["no"]' }) } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.review_candidates.438b8b57" value={reviewJobs.total || "—"} drilldown={reviewJobs.total > 0 ? { label: "i18n:govoplan-campaign.review_candidates.438b8b57", onActivate: () => showBuiltDetails() } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.attachment_issues.69748336" value={missingAttachments + ambiguousAttachments} drilldown={missingAttachments + ambiguousAttachments > 0 ? { label: "i18n:govoplan-campaign.review_attachment_rules.552c2116", onActivate: () => revealReviewElement("campaign-attachment-preview") } : undefined} />
</MetricGrid>
{hasBuild && <BuiltMessageReviewProgress progress={buildReviewProgress} />}
{hasBuild && <BuiltMessageWorkflowGuidance progress={buildReviewProgress} buildWarnings={buildWarnings} />}
@@ -1518,7 +1540,7 @@ export default function ReviewSendPage({
<p className="review-flow-inline-note is-complete"><Check size={17} aria-hidden="true" /> i18n:govoplan-campaign.all_built_messages_are_ready_no_manual_review_ac.c5549791</p>
}
{hasBuild &&
<div className="review-flow-data-section">
<div id="campaign-built-message-details" className="review-flow-data-section" tabIndex={-1}>
<div className="page-heading split">
<div className="button-row compact-actions">
<Button onClick={() => {setShowAllReviewJobs((value) => !value);setReviewPage(1);setJobsLoadedKey("");}} disabled={busy === "inspect"}>
@@ -1617,10 +1639,10 @@ export default function ReviewSendPage({
<WorkflowStage stage={stages[3]} nextState={stages[4].state} nextConnectorState={stageConnectorState(stages[4])}>
<MetricGrid columns={4} density="compact" spacing="block" minimum="compact">
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.recipients.78cbf8eb" value={jobsTotal || "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.messages_to_send.f7763bf9" value={builtCount || jobsTotal || "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.matched_attachments.ead1eeb1" value={matchedAttachments || "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.missing_ambiguous.fffdd3f5" value={`${missingAttachments} / ${ambiguousAttachments}`} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.recipients.78cbf8eb" value={jobsTotal || "—"} drilldown={hasBuild && jobsTotal > 0 ? { label: "i18n:govoplan-campaign.show_all_messages.1c2107a1", onActivate: () => showBuiltDetails({}, true) } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.messages_to_send.f7763bf9" value={builtCount || jobsTotal || "—"} drilldown={hasBuild && builtCount > 0 ? { label: "i18n:govoplan-campaign.show_all_messages.1c2107a1", onActivate: () => showBuiltDetails({}, true) } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.matched_attachments.ead1eeb1" value={matchedAttachments || "—"} drilldown={matchedAttachments > 0 ? { label: "i18n:govoplan-campaign.review_attachment_rules.552c2116", onActivate: () => revealReviewElement("campaign-attachment-preview") } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.missing_ambiguous.fffdd3f5" value={`${missingAttachments} / ${ambiguousAttachments}`} drilldown={missingAttachments + ambiguousAttachments > 0 ? { label: "i18n:govoplan-campaign.review_attachment_rules.552c2116", onActivate: () => revealReviewElement("campaign-attachment-preview") } : undefined} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.rate_limit.d08e55f5" value={messagesPerMinute > 0 ? i18nMessage("i18n:govoplan-campaign.value_min.c9d89eae", { value0: messagesPerMinute }) : "i18n:govoplan-campaign.not_set.93039e60"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.minimum_duration.91a71a6d" value={estimatedMinutes ? i18nMessage("i18n:govoplan-campaign.about_value_min.7c2e77fc", { value0: estimatedMinutes }) : "—"} />
<MetricCard density="compact" surface="subtle" label="i18n:govoplan-campaign.imap_append.8c0d9e96" value={Boolean(imapAppend.enabled) ? "i18n:govoplan-campaign.enabled.df174a3f" : "i18n:govoplan-campaign.disabled.f4f4473d"} />