feat(campaign): filter reports from outcome counts

This commit is contained in:
2026-07-22 09:11:10 +02:00
parent 0b4017c240
commit 4eb651c6ac
4 changed files with 194 additions and 26 deletions

View File

@@ -0,0 +1,85 @@
export type ReportGridQueryState = {
sort: { columnId: string; direction: "asc" | "desc" } | null;
filters: Record<string, string>;
};
export const DEFAULT_REPORT_GRID_SORT = { columnId: "number", direction: "asc" as const };
export type ReportGridShortcutId =
| "all"
| "smtp_accepted"
| "failed"
| "outcome_unknown"
| "not_attempted"
| "smtp_skipped"
| "cancelled"
| "imap_appended"
| "imap_failed"
| "imap_skipped";
const REPORT_GRID_SHORTCUT_FILTERS: Record<ReportGridShortcutId, Record<string, string>> = {
all: {},
smtp_accepted: { send: listFilter(["smtp_accepted", "sent"]) },
failed: { send: listFilter(["failed_temporary", "failed_permanent"]) },
outcome_unknown: { send: listFilter(["outcome_unknown"]) },
not_attempted: { send: listFilter(["not_queued"]) },
smtp_skipped: { send: listFilter(["skipped"]) },
cancelled: { send: listFilter(["cancelled"]) },
imap_appended: { imap: listFilter(["appended"]) },
imap_failed: { imap: listFilter(["failed"]) },
imap_skipped: { imap: listFilter(["skipped"]) }
};
/**
* Return the complete grid query for a count shortcut. Shortcuts are exact
* report views, so applying one intentionally clears every unrelated filter
* and restores the stable report ordering.
*/
export function reportGridQueryForShortcut(shortcutId: ReportGridShortcutId): ReportGridQueryState {
return {
sort: { ...DEFAULT_REPORT_GRID_SORT },
filters: { ...REPORT_GRID_SHORTCUT_FILTERS[shortcutId] }
};
}
/** Applying an already selected outcome shortcut returns to the unfiltered report. */
export function toggleReportGridShortcut(
current: ReportGridQueryState,
shortcutId: ReportGridShortcutId
): ReportGridQueryState {
const target = reportGridQueryForShortcut(shortcutId);
if (shortcutId !== "all" && reportGridFiltersEqual(current.filters, target.filters)) {
return reportGridQueryForShortcut("all");
}
return target;
}
export function activeReportGridShortcut(query: ReportGridQueryState): ReportGridShortcutId | null {
const shortcutIds = Object.keys(REPORT_GRID_SHORTCUT_FILTERS) as ReportGridShortcutId[];
return shortcutIds.find((shortcutId) => reportGridFiltersEqual(
query.filters,
REPORT_GRID_SHORTCUT_FILTERS[shortcutId]
)) ?? null;
}
export function reportGridQueriesEqual(left: ReportGridQueryState, right: ReportGridQueryState): boolean {
if ((left.sort?.columnId ?? "") !== (right.sort?.columnId ?? "")) return false;
if ((left.sort?.direction ?? "") !== (right.sort?.direction ?? "")) return false;
const keys = new Set([...Object.keys(left.filters), ...Object.keys(right.filters)]);
for (const key of keys) {
if ((left.filters[key] ?? "") !== (right.filters[key] ?? "")) return false;
}
return true;
}
function listFilter(values: string[]): string {
return `list:${JSON.stringify(values)}`;
}
function reportGridFiltersEqual(left: Record<string, string>, right: Record<string, string>): boolean {
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
for (const key of keys) {
if ((left[key] ?? "") !== (right[key] ?? "")) return false;
}
return true;
}