Frontend Specs
Section 13 — Admin Reports (`/admin/reports`)
Role access: ADMIN only. VIEWER/EDITOR/CLIENT redirected to /dashboard.
docs/specs/frontend/admin-spec-04c-admin-reports.mdOn this page
- 13.1 Report Builder
- 1. Purpose
- 2. Visual Layout
- 3. Data Source (tRPC endpoint)
- 4. Zod Schema
- 5. Fetch Frequency
- 6. Data Manipulations
- 7. Rationale
- 8. Interaction Flows
- 9. Error States
- 10. Role-Based Variations
- 13.2 Scheduled Reports
- 1. Purpose
- 2. Visual Layout
- 3. Data Source (tRPC endpoint)
- 4. Zod Schema
- 5. Fetch Frequency
- 6. Data Manipulations
- 7. Rationale
- 8. Interaction Flows
- 9. Error States
- 10. Role-Based Variations
- 13.3 Report Library
- 1. Purpose
- 2. Visual Layout
- 3. Data Source (tRPC endpoint)
- 4. Zod Schema
- 5. Fetch Frequency
- 6. Data Manipulations
- 7. Rationale
- 8. Interaction Flows
- 9. Error States
- 10. Role-Based Variations
Role access: ADMIN only.
VIEWER/EDITOR/CLIENTredirected to/dashboard. Layout: 3-tab interface: Report Builder, Scheduled, Library.1200pxmax-width. Sidebar:carbon(#12161E) background. Notes: Reports are platform-wide analytics and operational summaries. They can be one-time or scheduled (daily/weekly/monthly). All reports support export to PDF, CSV, and email delivery. Report generation is async and may take 1–5 minutes for large datasets.
13.1 Report Builder#
1. Purpose#
Create custom reports by selecting data sources, metrics, filters, and visualizations. Admins build one-time or reusable report templates for board meetings, investor updates, team reviews, or compliance documentation.
2. Visual Layout#
Section A: Report Configuration Form (top, 60% width)
- Report name: Text input, required, max 100 chars. Placeholder: "Q2 Revenue Summary".
- Description: Textarea, optional, max 500 chars. Placeholder: "Monthly report for board meeting."
- Data sources: Multi-select checkboxes (grouped by category):
- Business: Revenue, Subscriptions, Clients, Churn, ARPU
- Content: Content generated, Content published, Approval queue, Content performance
- Marketing: Leads, Lead sources, Conversion rate, Campaign performance
- Operations: API usage, Worker throughput, Queue depth, System health
- Support: Tickets (if support system exists), Response time, Resolution rate
- Metrics per source: When a source is checked, metric selectors appear below it:
- Revenue source: MRR, ARR, New MRR, Churned MRR, Expansion MRR, ARPU
- Content source: Total content, Published content, Pending approval, Avg generation time
- Each metric: checkbox + optional "Compare to previous period" toggle
- Time range: Dropdown — "Last 7 days", "Last 30 days", "Last 90 days", "Last 12 months", "Quarter to date", "Year to date", "Custom" (date picker).
- Filters: Dynamic filters based on selected sources:
- Plan filter (if revenue/subscription selected)
- Client filter (if any client-specific data selected)
- Status filter (if applicable)
- Visualization: Radio group — "Table only", "Charts + Table", "Charts only", "Executive Summary" (narrative with key highlights).
- Comparison: Toggle "Compare to previous period" — adds prior period columns/chart series.
- "Generate Report" button: Ember Orange CTA. Disabled until name and at least one source/metric selected.
Section B: Live Preview (top, 40% width)
- As admin selects metrics and time range, a live preview updates:
- Mini chart placeholder: "Select metrics to see preview"
- Once metrics selected: sparkline or mini bar chart of the selected metric over the time range
- Key stat: "{N} {metric} in selected period" with trend arrow
- Preview is a low-fidelity approximation (uses cached data, not full report generation).
Section C: Report Templates (below form, full width)
- Horizontal scrollable card row of pre-built templates:
- "Monthly Revenue Report" (Revenue + Subscriptions + Churn, last 30 days, charts + table)
- "Content Performance Report" (Content + Marketing, last 30 days, charts + table)
- "System Health Report" (Operations only, last 7 days, executive summary)
- "Board Meeting Deck" (All sources, last 90 days, executive summary, comparison enabled)
- "GDPR Compliance Report" (Data Governance + Audit, last 90 days, table only)
- Each template card: icon + name + description + "Use Template" button (secondary). Clicking pre-fills the form.
3. Data Source (tRPC endpoint)#
admin.getReportPreview.useQuery({
sources: z.array(z.string()),
metrics: z.array(z.string()),
timeRange: z.object({ from: z.date(), to: z.date() }),
filters: z.record(z.any()).optional(),
}, { enabled: false }); // manual trigger when preview requested
admin.generateReport.useMutation({
name: z.string().min(1).max(100),
description: z.string().max(500).optional(),
sources: z.array(z.string()).min(1),
metrics: z.array(z.string()).min(1),
timeRange: z.object({ from: z.date(), to: z.date() }),
filters: z.record(z.any()).optional(),
visualization: z.enum(["TABLE", "CHARTS_TABLE", "CHARTS", "EXECUTIVE"]),
compareToPrevious: z.boolean().default(false),
schedule: z.object({
enabled: z.boolean().default(false),
frequency: z.enum(["DAILY", "WEEKLY", "MONTHLY"]).optional(),
recipients: z.array(z.string().email()).optional(),
}).optional(),
});
4. Zod Schema#
const ReportSourceSchema = z.enum([
"REVENUE", "SUBSCRIPTIONS", "CLIENTS", "CHURN", "ARPU",
"CONTENT", "CONTENT_PERFORMANCE", "APPROVAL_QUEUE",
"LEADS", "LEAD_SOURCES", "CONVERSION_RATE", "CAMPAIGNS",
"API_USAGE", "WORKER_THROUGHPUT", "QUEUE_DEPTH", "SYSTEM_HEALTH",
"SUPPORT_TICKETS", "RESPONSE_TIME", "RESOLUTION_RATE",
"AUDIT_TRAIL", "DATA_GOVERNANCE",
]);
const ReportMetricSchema = z.string(); // dynamic based on source, e.g., "MRR", "ARR", "TOTAL_CONTENT"
const ReportVisualizationSchema = z.enum(["TABLE", "CHARTS_TABLE", "CHARTS", "EXECUTIVE"]);
const ReportTemplateSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
icon: z.string(), // Lucide icon name
sources: z.array(ReportSourceSchema),
metrics: z.array(ReportMetricSchema),
timeRange: z.string(), // e.g., "LAST_30_DAYS"
visualization: ReportVisualizationSchema,
compareToPrevious: z.boolean().default(false),
});
const ReportInputSchema = z.object({
name: z.string().min(1).max(100),
description: z.string().max(500).optional(),
sources: z.array(ReportSourceSchema).min(1),
metrics: z.array(ReportMetricSchema).min(1),
timeRange: z.object({ from: z.date(), to: z.date() }),
filters: z.record(z.any()).optional(),
visualization: ReportVisualizationSchema,
compareToPrevious: z.boolean().default(false),
schedule: z.object({
enabled: z.boolean().default(false),
frequency: z.enum(["DAILY", "WEEKLY", "MONTHLY"]).optional(),
recipients: z.array(z.string().email()).optional(),
}).optional(),
});
const ReportPreviewSchema = z.object({
metricName: z.string(),
value: z.number(),
trend: z.enum(["UP", "DOWN", "STABLE"]),
trendPercent: z.number(),
miniChart: z.array(z.object({
label: z.string(),
value: z.number(),
})).optional(),
});
5. Fetch Frequency#
- On-demand: Preview is manually triggered (debounced 500ms after metric selection). Report generation is on-demand mutation.
- Static: Templates are static data (no API call needed).
6. Data Manipulations#
- Preview aggregation: Uses pre-aggregated cached data (last 24h) for instant preview. Full report uses raw data (may take minutes).
- Metric labels: "MRR" → "Monthly Recurring Revenue"; "TOTAL_CONTENT" → "Total Content Generated"; human-readable mapping.
- Time range presets: "LAST_7_DAYS" →
from: now-7d, to: now. "QUARTER_TO_DATE" →from: quarter start, to: now. Custom shows date pickers. - Filter dependency: Revenue metrics show plan filter. Content metrics show status filter. Dynamic filter UI based on selected sources.
- Template pre-fill: Click template → form populated with all values. Admin can modify before generating.
- Comparison: If enabled, all metrics show "vs previous period" column or dashed line in chart. Previous period = equal duration before
fromdate. - Executive summary: Narrative generation using AI (optional). "MRR grew 12% to $12,450, driven by 3 new Pro subscriptions. Churn remained low at 2%. Content generation increased 34% due to new AI model."
7. Rationale#
- Self-service reporting: Admins shouldn't need engineering to generate reports. Point-and-click report builder empowers ops, finance, and leadership teams.
- Pre-built templates: 80% of reports are the same every month. Templates save time. "Monthly Revenue Report" is one click away.
- Live preview: Prevents "generate and hope." Admin sees a preview of the data before committing to a full report generation (which may take 5 minutes and cost compute).
- Multi-source reports: Board reports need revenue + content + operations in one PDF. Single-source reports are too narrow for executive audiences.
- Comparison: "MRR is $12,450" is a number. "MRR is $12,450, up 12% from last month" is insight. Comparison is the default for executive reports.
- Visualization options: Tables for detail-oriented analysts. Charts for executives who want trends. Executive summary for board members who want the narrative. One report, multiple outputs.
- Scheduling: If a report is needed weekly, don't make the admin click "generate" every Monday. Schedule it. Email delivery to stakeholders.
- Cached preview: Full report generation queries large datasets. Preview uses cached aggregates to be instant. Only the full report is expensive.
- Filter dependency: Showing a "plan filter" when the admin selected "API usage" is confusing. Dynamic filters keep the UI clean and relevant.
- Template icons: Visual identification of report type. Revenue = dollar icon, Content = file icon, System = server icon.
8. Interaction Flows#
- Select template: Click "Use Template" on any template card → form pre-filled → modify as needed → "Generate Report".
- Build from scratch: Type name → select sources → metrics appear → select metrics → configure time range → set filters → choose visualization → toggle comparison → "Generate".
- Preview update: After selecting 1+ metrics, preview panel updates with cached data. If no data available, "No data for preview. Generate full report for complete data."
- Generate report: Click "Generate Report" → mutation → report queued → navigates to Report Library with new report in "Processing" state → toast "Report '{name}' queued. Check library in 1–5 minutes."
- Schedule toggle: If "Schedule this report" is toggled ON, additional fields appear: frequency (daily/weekly/monthly) + recipient emails (comma-separated, validated). Report is saved as a scheduled template and run automatically.
- Form validation: Real-time validation. Name required, at least 1 source, at least 1 metric. Red borders + messages on invalid fields.
- Impersonation: Read-only. Form viewable but all fields disabled. "Generate" button hidden. Cannot create or schedule reports.
9. Error States#
- Loading: Skeleton template cards (5) + form fields disabled until loaded.
- Preview fail: "Preview unavailable. Selected metrics may require full report generation."
- Generate fail: Toast "Report generation failed. Dataset too large. Try narrowing time range or reducing metrics."
- No data: If selected metrics have no data for the time range, preview shows "No data for this period."
- Template fail: If template pre-fill fails, toast "Template data unavailable. Build report manually."
- Schedule fail: If email recipients invalid, inline red text: "Invalid email: {email}".
- Concurrent generation: If admin already has 3 reports processing, toast: "Maximum 3 concurrent reports. Wait for one to complete."
- No permission: Non-ADMIN redirected.
10. Role-Based Variations#
- ADMIN: Full access — build, generate, schedule, use templates, preview.
- Other roles: No access.
- Impersonation: Read-only. Form viewable but disabled. No generation. No scheduling. Templates viewable.
13.2 Scheduled Reports#
1. Purpose#
Manage recurring reports that run automatically on a schedule (daily, weekly, monthly). Admins create, edit, pause, and delete scheduled reports. View delivery history and failure logs.
2. Visual Layout#
Section A: Scheduled Report List (top, full width)
- Table: Name | Schedule | Last Run | Next Run | Status | Recipients | Actions
- Name: Report name + description tooltip on hover.
- Schedule: Badge + frequency. "📅 Daily at 08:00 IST" or "📅 Weekly (Mon) at 09:00 IST" or "📅 Monthly (1st) at 07:00 IST".
- Last Run: Relative time + status. "2 hr ago — Succeeded" (green) or "1 day ago — Failed" (red). Click → opens run history.
- Next Run: "Tomorrow at 08:00 IST" or "In 2 days" or "—" (if paused).
- Status:
ACTIVE(green badge, toggle ON) orPAUSED(gray badge, toggle OFF). Toggleable inline. - Recipients: Email pills, truncated if > 3. "admin@rankflow.ai, board@rankflow.ai, +2 more". Hover tooltip shows full list.
- Actions: 3-dot menu:
- "Run Now" → triggers immediate execution
- "Edit" → opens report builder pre-filled with this report's config
- "Duplicate" → creates copy with "(Copy)" suffix
- "View History" → navigates to run history for this report
- "Delete" → confirmation → removes schedule
Section B: Run History (below, full width, collapsible)
- Click "View History" on any row → expands below showing:
- Table: Run Time | Status | Duration | Size | Download | Error
- Run Time: Relative timestamp. "2 hr ago", "Yesterday at 08:00".
- Status:
SUCCEEDED(green),FAILED(red),CANCELLED(gray). - Duration: "{N} min {N} sec" or "{N} sec" for fast reports.
- Size: "{N} MB" or "{N} KB" (PDF/CSV size).
- Download: "Download PDF" / "Download CSV" links if SUCCEEDED. "—" if FAILED.
- Error: Error message truncated to 50 chars. Full message on hover or in detail modal.
- Pagination: 20 per page. Default shows last 10 runs.
Section C: Schedule Stats (above table)
- Horizontal row: "Active Schedules: {N}" | "Paused: {N}" | "Succeeded Today: {N}" | "Failed Today: {N}" | "Avg Generation Time: {N} min"
- "Failed Today" in red if > 0.
3. Data Source (tRPC endpoint)#
admin.getScheduledReports.useQuery(undefined, { refetchInterval: 60000 }); // 1min standard
admin.getReportRunHistory.useQuery({
reportId: z.string().uuid(),
page: z.number().min(1).default(1),
pageSize: z.number().min(1).max(50).default(20),
}, { enabled: false }); // manual trigger when expanded
admin.toggleScheduledReport.useMutation({ reportId: z.string().uuid(), enabled: z.boolean() });
admin.runScheduledReportNow.useMutation({ reportId: z.string().uuid() });
admin.deleteScheduledReport.useMutation({ reportId: z.string().uuid() });
4. Zod Schema#
const ScheduledReportSchema = z.object({
id: z.string().uuid(),
name: z.string(),
description: z.string().optional(),
schedule: z.object({
frequency: z.enum(["DAILY", "WEEKLY", "MONTHLY"]),
dayOfWeek: z.number().min(0).max(6).optional(), // 0=Sunday for weekly
dayOfMonth: z.number().min(1).max(31).optional(), // for monthly
time: z.string(), // "08:00" in UTC
timezone: z.string().default("UTC"),
}),
lastRunAt: z.date().optional(),
lastRunStatus: z.enum(["SUCCEEDED", "FAILED", "CANCELLED"]).optional(),
lastRunError: z.string().optional(),
nextRunAt: z.date().optional(),
status: z.enum(["ACTIVE", "PAUSED"]),
recipients: z.array(z.string().email()),
createdAt: z.date(),
updatedAt: z.date(),
});
const ReportRunHistoryItemSchema = z.object({
id: z.string().uuid(),
reportId: z.string().uuid(),
runAt: z.date(),
status: z.enum(["SUCCEEDED", "FAILED", "CANCELLED"]),
durationSeconds: z.number(),
sizeBytes: z.number().optional(),
pdfUrl: z.string().url().optional(),
csvUrl: z.string().url().optional(),
errorMessage: z.string().optional(),
});
const ScheduledReportListResponseSchema = z.object({
items: z.array(ScheduledReportSchema),
total: z.number(),
activeCount: z.number(),
pausedCount: z.number(),
succeededToday: z.number(),
failedToday: z.number(),
avgGenerationMinutes: z.number().optional(),
});
5. Fetch Frequency#
- Standard (
60000ms / 1min): Schedule status changes (active/paused) and last run times update. 1min polling. - On-demand: Toggle status, run now, delete, expand history, download.
- SSE: When a scheduled report completes (success or fail), push update to table. Next run time updates automatically.
6. Data Manipulations#
- Schedule formatting:
DAILYat 08:00 UTC → "Daily at 08:00 UTC (2:30 PM IST)"WEEKLYon Monday at 09:00 UTC → "Weekly (Mon) at 09:00 UTC"MONTHLYon 1st at 07:00 UTC → "Monthly (1st) at 07:00 UTC"
- Last run: Relative time + status badge. If FAILED, red badge with error tooltip.
- Next run: If ACTIVE, computed next occurrence. If PAUSED, "—".
- Recipients: Email pills. Truncated to 3 + "+{N} more". Hover shows full list.
- Status toggle: Inline toggle switch. Active = green. Paused = gray. Toggle → mutation. If failed, toggle reverts.
- Run history: Expandable per row. Last 10 runs shown inline. Click "View All" → full history modal with pagination.
- Download links: If run succeeded, PDF and CSV download links. Expire after 7 days. If expired, "Download expired. Run again."
- Stats: "Succeeded Today" counts runs with
status: SUCCEEDEDandrunAttoday. "Failed Today" same for FAILED. - Avg generation time:
(sum of durationSeconds) / count / 60→ minutes. Only for SUCCEEDED runs.
7. Rationale#
- Scheduled reports: Automation. The admin sets it once, and the report generates and emails itself every Monday morning. Zero manual work.
- Inline toggle: Pausing a schedule is common (e.g., "pause during holidays" or "pause while we fix a data bug"). One-click toggle without opening an edit form.
- Run history: Visibility into report health. If a report has failed 3 times in a row, it's broken. Admin sees the pattern and investigates. Error messages in history help debug.
- Run now: If a scheduled report is needed early (e.g., board meeting moved up), "Run Now" triggers it immediately without waiting for the schedule.
- 1min polling: Next run times are computed (not real-time). 1min is sufficient. When a report actually runs, SSE pushes the completion status.
- Recipient pills: Email list can be long. Pills with truncation keep the table readable. Hover shows full list.
- Download expiry: Report files contain sensitive data. Auto-expiring after 7 days prevents indefinite exposure. Admin can re-run if needed.
- Stats bar: "Failed Today" in red draws attention to broken reports. If it's 0, the system is healthy. If it's > 0, action needed.
- Duplicate: If a report is almost right but needs a small tweak, duplicate and edit. Faster than rebuilding from scratch.
8. Interaction Flows#
- Toggle schedule: Click status toggle → immediate UI change → mutation → toast "Schedule {enabled/paused}. Next run: {time} or '—'."
- Run now: Click "Run Now" in menu → confirmation: "Run '{name}' immediately? This will queue a report generation." → "Run" → toast "Report queued. Check history in 1–5 minutes." → history table updates with new row (PROCESSING).
- View history: Click "View History" → row expands with last 10 runs → click "View All" → modal with full pagination.
- Download: Click "Download PDF" or "Download CSV" in history → browser downloads. If expired, "Download expired. Run again or wait for next scheduled run."
- Edit: Click "Edit" → opens Report Builder with form pre-filled → modify → "Save Schedule" → updates existing schedule (doesn't create new).
- Duplicate: Click "Duplicate" → new schedule created with "(Copy)" suffix → toast "Schedule duplicated. Edit as needed." → row added to table.
- Delete: Click "Delete" → confirmation: "Delete schedule for '{name}'? This will stop all future reports. Past reports remain in history." → "Delete" → row removed → toast "Schedule deleted."
- Impersonation: Read-only. Toggle, run now, edit, duplicate, delete all disabled. History viewable. Downloads disabled.
9. Error States#
- Loading: Skeleton table (8 rows) + skeleton stats.
- Empty: "No scheduled reports. Create a scheduled report from the Report Builder or use a template."
- Toggle fail: Toast "Failed to toggle schedule. Report may be running." → toggle reverts.
- Run now fail: Toast "Failed to queue report. Maximum concurrent reports reached. Wait for existing reports to complete."
- History fail: If history can't load, "History unavailable. Report may have been deleted."
- Download fail: Toast "Download failed. File may have expired. Re-run the report."
- All failed: If all schedules failed today, red banner: "⚠️ All scheduled reports failed today. Check system health and report configuration."
- No permission: Non-ADMIN redirected.
10. Role-Based Variations#
- ADMIN: Full access — toggle, run now, edit, duplicate, delete, view history, download.
- Other roles: No access.
- Impersonation: Read-only. Table viewable. History viewable. No actions. No downloads.
13.3 Report Library#
1. Purpose#
Browse, search, and download all generated reports (one-time and scheduled). The archive of all past reports. Admins find old reports by name, date, type, or metric.
2. Visual Layout#
Section A: Search & Filters (top, full width)
- Search: "Search by report name, metric, or client"
- Type filter: Multi-select (
One-time,Scheduled,Template) - Source filter: Multi-select (Revenue, Content, Marketing, Operations, etc.)
- Date range: "Last 7 days", "Last 30 days", "Last 90 days", "Custom"
- Status filter: Multi-select (
READY,PROCESSING,FAILED) - Sort: "Date", "Name", "Size" (dropdown)
- "Export List" button (secondary, CSV of report metadata)
Section B: Report Grid (below, full width)
- Card grid (3 columns desktop, 2 tablet, 1 mobile). Each card:
- Icon: Document icon (PDF) or spreadsheet icon (CSV) or chart icon (Charts + Table).
- Name: Report name, truncated to 2 lines.
text-basetext-primary. - Meta:
text-smtext-muted. "Generated {relative time} · {N} MB · {source count} sources" - Status badge:
READY(green),PROCESSING(amber + spinner),FAILED(red). - Preview thumbnail: If report includes charts, a tiny thumbnail (200px wide) of the first chart. If table-only, a mini table preview (first 3 rows). If processing, skeleton thumbnail.
- Actions: "Download PDF" (primary, Ember) + "Download CSV" (secondary) + "View" (opens report viewer modal) + "Delete" (trash icon, danger).
- PROCESSING cards: Skeleton chart + amber badge + "Processing... {N}%" (if progress available). No download buttons. "Cancel" button if admin wants to abort.
- FAILED cards: Red badge + "Failed: {error preview}" + "Retry" button.
Section C: Report Viewer Modal
- Click "View" on any READY card → modal:
- Full report rendered in modal (scrollable, max-height 80vh).
- If executive summary: narrative text with highlighted stats.
- If charts + table: charts first, then table below.
- If table only: full data table with sorting and pagination (50 rows per page within modal).
- "Download PDF" and "Download CSV" buttons at top (sticky).
- Report metadata sidebar: generated by, generated at, data sources, time range, filters used.
- "Share" button: generates a temporary shareable link (expires in 24h) for sending to non-admin stakeholders.
3. Data Source (tRPC endpoint)#
admin.getReportLibrary.useQuery({
search: z.string().optional(),
types: z.array(z.enum(["ONE_TIME", "SCHEDULED", "TEMPLATE"])).optional(),
sources: z.array(ReportSourceSchema).optional(),
dateRange: z.object({ from: z.date().optional(), to: z.date().optional() }).optional(),
statuses: z.array(z.enum(["READY", "PROCESSING", "FAILED"])).optional(),
sortBy: z.enum(["DATE", "NAME", "SIZE"]).default("DATE"),
sortOrder: z.enum(["ASC", "DESC"]).default("DESC"),
page: z.number().min(1).default(1),
pageSize: z.number().min(1).max(50).default(24),
}, { refetchInterval: 30000 }); // 30s frequent
admin.getReportDetail.useQuery({
reportId: z.string().uuid(),
}, { enabled: false }); // manual trigger
admin.deleteReport.useMutation({ reportId: z.string().uuid() });
admin.cancelReport.useMutation({ reportId: z.string().uuid() }); // for PROCESSING reports
admin.shareReport.useMutation({ reportId: z.string().uuid(), expiresInHours: z.number().min(1).max(72).default(24) });
4. Zod Schema#
const ReportTypeSchema = z.enum(["ONE_TIME", "SCHEDULED", "TEMPLATE"]);
const ReportStatusSchema = z.enum(["READY", "PROCESSING", "FAILED"]);
const ReportLibraryItemSchema = z.object({
id: z.string().uuid(),
name: z.string(),
type: ReportTypeSchema,
sources: z.array(ReportSourceSchema),
status: ReportStatusSchema,
generatedAt: z.date().optional(),
generatedBy: z.string().optional(),
sizeBytes: z.number().optional(),
pdfUrl: z.string().url().optional(),
csvUrl: z.string().url().optional(),
thumbnailUrl: z.string().url().optional(),
errorMessage: z.string().optional(),
progressPercent: z.number().min(0).max(100).optional(), // for PROCESSING
timeRange: z.object({ from: z.date(), to: z.date() }).optional(),
});
const ReportDetailSchema = z.object({
report: ReportLibraryItemSchema,
content: z.object({
executiveSummary: z.string().optional(),
charts: z.array(z.object({
title: z.string(),
type: z.enum(["LINE", "BAR", "PIE", "AREA", "TABLE"]),
data: z.record(z.any()),
})).optional(),
tables: z.array(z.object({
title: z.string(),
headers: z.array(z.string()),
rows: z.array(z.array(z.any())),
})).optional(),
}),
metadata: z.object({
sources: z.array(ReportSourceSchema),
metrics: z.array(z.string()),
timeRange: z.object({ from: z.date(), to: z.date() }),
filters: z.record(z.any()).optional(),
generatedAt: z.date(),
generatedBy: z.string(),
durationSeconds: z.number(),
}),
});
const ReportLibraryResponseSchema = z.object({
items: z.array(ReportLibraryItemSchema),
total: z.number(),
hasMore: z.boolean(),
});
5. Fetch Frequency#
- Frequent (
30000ms / 30s): Report statuses change (PROCESSING → READY/FAILED). 30s polling catches transitions. - On-demand: Filter, search, sort, pagination, view detail, download, delete, share.
- SSE: When a report completes, push to library (status change + download links). When a new report is generated, prepend to grid.
6. Data Manipulations#
- Status badge colors: READY = green; PROCESSING = amber + spinner; FAILED = red.
- Size formatting:
< 1024 KB→ "{N} KB";< 1024 MB→ "{N} MB";>= 1024 MB→ "{N} GB". - Thumbnail: Generated server-side during report creation. 200x150px PNG. For chart reports, shows first chart. For table reports, shows first 3 rows as a mini table. For processing, skeleton.
- Relative time: "Generated 2 hr ago", "Yesterday", "3 days ago".
- Card grid: 3 columns on desktop. Cards are fixed height (300px) with scrollable content if report name is long. Consistent grid prevents layout shifts.
- PROCESSING progress: If progress data available, show progress bar in card: "Processing... 45%". If not, indeterminate spinner.
- Source count: Meta line shows number of data sources. "3 sources" means Revenue + Content + Operations.
- Type badge: Small badge on card: "One-time", "Scheduled", "Template". Different colors: One-time = blue, Scheduled = green, Template = gray.
- Share link: Temporary URL with token. Expires in 24h (configurable). No auth required for link. Link leads to read-only report viewer. Admin can revoke link early.
- Modal viewer: Report rendered in modal. Charts are interactive (Recharts). Tables are sortable and paginated. Sticky download buttons at top.
- Metadata sidebar: Shows data provenance. "Generated by John Doe on Jun 15, 2026 at 14:32. Sources: Revenue, Content. Time range: May 1–May 31, 2026. Filters: Plan = Growth." Transparency and reproducibility.
7. Rationale#
- Report library: Archive of all reports. If a board member asks "what was Q1 revenue?" and the admin already generated the report, it's in the library. No need to regenerate.
- Card grid with thumbnails: Visual browsing. Thumbnails let admin spot the right report quickly. "That one with the green line chart — that's the revenue report."
- 30s polling for processing: When an admin clicks "Generate Report," they want to know when it's done. 30s polling shows the status transition. The card changes from amber spinner to green download button.
- Shareable links: Board members and investors don't have admin accounts. A shareable link lets them view the report without login. Auto-expires for security.
- Modal viewer: Preview before downloading. If the report looks wrong, admin can delete it and regenerate without downloading a broken PDF.
- Cancel processing: If a report was generated with wrong parameters, admin can cancel it while processing. Saves compute and storage.
- Source count on card: Quick filter. If admin is looking for a revenue report, they can see at a glance which cards have revenue as a source.
- Type differentiation: One-time reports are ad-hoc. Scheduled reports are recurring. Templates are blueprints. Knowing the type tells admin how the report was created.
- Metadata sidebar: Data provenance is critical for trust. "This report says MRR is $12,450. Was that from all plans or just Pro? The metadata tells me."
- Search: Report names + metric names + source names are all searchable. If admin can't remember the report name, they can search "churn" or "revenue."
- Export list: CSV of all report metadata (not report content). Useful for auditing what reports were generated and when.
8. Interaction Flows#
- Filter: Change status filter to "FAILED" → only failed reports shown. Change type to "Scheduled" → only scheduled reports shown. URL updates with filters.
- Search: Type "revenue" → cards filter live (debounced 300ms). Only reports with "revenue" in name or sources shown.
- View report: Click "View" → modal opens with full report content → scroll through charts and tables → click "Download PDF" or "Download CSV".
- Download: Click "Download PDF" on card → browser downloads. Filename:
{reportName}_{YYYYMMDD}.pdf. - Share: Click "Share" in modal → generates link → shows link in copyable field with "Copy" button + expiry timer "Expires in 24h" → "Revoke" button to invalidate early.
- Delete: Click trash on card → confirmation: "Delete report '{name}'? PDF and CSV will be removed." → "Delete" → card removed with fade-out → toast "Report deleted."
- Cancel processing: Click "Cancel" on PROCESSING card → confirmation: "Cancel report generation? Partial data will be discarded." → "Cancel" → status changes to FAILED → toast "Report cancelled."
- Retry failed: Click "Retry" on FAILED card → re-queues report generation with same parameters → status changes to PROCESSING → toast "Report re-queued."
- Impersonation: Read-only. Cards viewable. Modal viewer accessible. Downloads disabled. Share disabled. Delete and cancel disabled.
9. Error States#
- Loading: Skeleton card grid (9 cards with shimmer).
- Empty (filtered): "No reports match your filters. Try adjusting date range or status."
- Empty (no reports): "No reports generated yet. Create your first report in the Report Builder."
- View fail: If report content can't load, modal shows "Report content unavailable. File may have been corrupted or expired."
- Download fail: Toast "Download failed. File may have expired. Re-generate the report."
- Share fail: Toast "Share link generation failed. Report may be too large or processing."
- Delete fail: Toast "Delete failed. Report may be in use by another process."
- Cancel fail: Toast "Cancel failed. Report may have already completed."
- No permission: Non-ADMIN redirected.
10. Role-Based Variations#
- ADMIN: Full access — view, filter, search, download, share, delete, cancel, retry.
- Other roles: No access.
- Impersonation: Read-only. Cards viewable. Modal viewer accessible. No downloads, no sharing, no deletion, no cancellation.