Add bulk calendar invitation delivery

This commit is contained in:
2026-08-02 16:38:48 +02:00
parent 7733265cc8
commit bf6e07f307
17 changed files with 1356 additions and 20 deletions
@@ -388,6 +388,16 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
{ id: "queue", header: "i18n:govoplan-campaign.queue.d325fcd9", width: 130, sortable: true, filterable: true, columnType: "from-list", list: { options: QUEUE_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.queue_status ?? "unknown")} />, value: (row) => String(row.queue_status ?? "unknown") },
{ id: "send", header: "Delivery", width: 160, sortable: true, filterable: true, columnType: "from-list", list: { options: SEND_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.send_status ?? "unknown")} label={deliveryStatusLabel(String(row.send_status ?? "unknown"))} />, value: (row) => String(row.send_status ?? "unknown") },
{ id: "postbox", header: "Postbox", width: 155, sortable: true, filterable: true, columnType: "from-list", list: { options: POSTBOX_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.postbox_status ?? "unknown")} label={deliveryStatusLabel(String(row.postbox_status ?? "unknown"))} />, value: (row) => String(row.postbox_status ?? "unknown") },
{
id: "rsvp",
header: "RSVP",
width: 145,
value: (row) => calendarRsvpStatus(row),
render: (row) => {
const status = calendarRsvpStatus(row);
return status === "—" ? <span className="muted"></span> : <StatusBadge status={status.toLowerCase()} label={humanize(status)} />;
}
},
{ id: "print", header: "Print", width: 135, sortable: true, filterable: true, columnType: "from-list", list: { options: PRINT_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.print_status ?? "unknown")} label={deliveryStatusLabel(String(row.print_status ?? "unknown"))} />, value: (row) => String(row.print_status ?? "unknown") },
{ id: "imap", header: "i18n:govoplan-campaign.imap.271f9ef2", width: 130, sortable: true, filterable: true, columnType: "from-list", list: { options: IMAP_STATUS_OPTIONS, display: "pill" }, render: (row) => <StatusBadge status={String(row.imap_status ?? "unknown")} label={deliveryStatusLabel(String(row.imap_status ?? "unknown"))} />, value: (row) => String(row.imap_status ?? "unknown") },
{ id: "attempts", header: "i18n:govoplan-campaign.attempts.5a29585e", width: 105, align: "right", sortable: true, filterType: "integer", value: (row) => Number(row.attempt_count ?? 0), render: (row) => String(Number(row.attempt_count ?? 0) + Number(row.postbox_attempt_count ?? 0)) },
@@ -603,6 +613,7 @@ export default function CampaignReportPage({ settings, campaignId }: {settings:
<div><dt>i18n:govoplan-campaign.smtp_state.ff372566</dt><dd><StatusBadge status={String(detail.job.send_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.send_status ?? "unknown"))} /></dd></div>
<div><dt>Postbox state</dt><dd><StatusBadge status={String(detail.job.postbox_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.postbox_status ?? "unknown"))} /></dd></div>
<div><dt>Postbox targets</dt><dd>{String(detail.job.postbox_target_count ?? 0)}</dd></div>
<div><dt>Calendar RSVP</dt><dd>{calendarRsvpStatus(detail.job)}</dd></div>
<div><dt>Print state</dt><dd><StatusBadge status={String(detail.job.print_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.print_status ?? "unknown"))} /></dd></div>
<div><dt>i18n:govoplan-campaign.imap_state.03b83be0</dt><dd><StatusBadge status={String(detail.job.imap_status ?? "unknown")} label={deliveryStatusLabel(String(detail.job.imap_status ?? "unknown"))} /></dd></div>
<div><dt>i18n:govoplan-campaign.attachments.6771ade6</dt><dd>{String(detail.job.matched_file_count ?? detail.job.attachment_count ?? 0)}</dd></div>
@@ -1037,6 +1048,11 @@ function deliveryStatusLabel(status: string): string | undefined {
return status === "skipped" ? "i18n:govoplan-campaign.skipped.5a000ad7" : undefined;
}
function calendarRsvpStatus(row: Record<string, unknown>): string {
const invitation = asRecord(row.calendar_invitation);
return String(invitation.rsvp_status || "—");
}
function initialReportQuery(): string {
if (typeof window === "undefined") return "";
return new URLSearchParams(window.location.search).get("q")?.trim() ?? "";