Time Reports
Six tabs over the same dataset — Summary, Detailed, Weekly pivot, Profitability, Workload, Utilization. Plus the Custom Report Builder, saved views, scheduled email, and PDF / CSV export.
/time/reports is a six-tab surface, all sharing one filter row and one toolbar. Switch tabs without losing context: filters, date range, and rounding state carry through. Anything you can't answer in the six tabs, build in the Custom Report Builder.
Screenshot needed — /time/reports with tabs across the top (Summary / Detailed / Weekly / Profitability / Workload / Utilization), filter row + toolbar, Summary tab content (KPI cards + duration bar + project donut).
Filter row + toolbar
Above every tab, the filter row:
| Filter | What it does |
|---|---|
| Date range | Today / Yesterday / Last 7 days / This week / Last 30 days / This month / Last month / This quarter / This year / Custom |
| Member | One or many users |
| Client | One or many CRM contacts |
| Project | One or many projects |
| Tag | One or many tags from time_entries.tags |
| Description | Free-text contains |
| + Add filter | Billable status, status (draft / submitted / approved), entity type |
And the toolbar on the right:
| Action | What it does |
|---|---|
| Rounding | Off / nearest / up / down × 0 / 5 / 15 / 30 min — display-only, doesn't mutate entries |
| Saved views | Popover of your saved filter+tab+widget snapshots. Click the Clock on any view to schedule email delivery; click the X to delete. |
| Custom builder | Jumps to /time/reports/builder to compose chart-by-chart reports |
| Schedule manager (mail icon) | Opens /time/reports/schedules to pause / resume / delete every workspace email schedule |
| Create invoice | Pulls the currently filtered billable + approved hours into a new invoice draft (delegates to the existing invoicing bridge) |
| Export CSV | Downloads the current tab's rows. Detailed exports row-for-row; the others export their aggregated shape. |
| Export PDF | Renders the current tab to a print-ready A4 PDF via the shared Puppeteer stack. KPIs + tables included; charts are not embedded (the PDF is for hand-off, not for chart-by-chart browsing). |
| Settings | Open /time/settings |
All filter / tab / toolbar state lives in the URL, so deep-links and refreshes round-trip cleanly.
The six tabs
Summary
The default. Designed for the "where did my hours go this week?" question.
| Element | What |
|---|---|
| KPI cards | Total Hours · Billable Hours · Amount · Avg Daily |
| Duration by month | Bar chart, hours per month over the selected range |
| Project distribution | Donut chart with project legend + percentages |
| Project / description breakdown | Collapsible table, rows = projects, expand to see descriptions |
Detailed
A flat list of every entry that matches the filters, paginated. Columns: Date, User, Project, Description, Tags, Duration, Billable, Rate, Amount. Sortable on every column. CSV / PDF export from this tab uses the exact rows shown.
Admins and managers see an Add entry for… button on this tab — opens a quick form with a member picker so an admin can log time on behalf of someone else. The audit log records on_behalf_of so the trail stays transparent. See Timer → Logging time for someone else.
Weekly
Pivot grid: members × weeks, cells are hours. Heatmap-tinted (light → dark for higher hours), totals row + column. Useful for "who's been doing what" at a manager level.
Profitability (Pro)
Per-project rows showing revenue − cost − expenses → margin %:
| Column | Source |
|---|---|
| Revenue | Sum of hours × hourly_rate for billable + approved entries |
| Cost | Sum of hours × cost_rate for entries where cost_rate IS NOT NULL |
| Expenses | Sum of project-tagged expenses_expenses rows in the period |
| Margin | Revenue − Cost − Expenses |
| Margin % | Margin / Revenue |
Sorted by margin desc by default. A warning chip surfaces when cost_rate is missing on N entries — those rows show "—" for cost and margin.
Requires Time Pro. Free workspaces see an upgrade card with a contextual explainer.
Workload
Same pivot shape as Weekly (members × weeks) but adds two columns: Total (sum across the range) and vs. capacity (Total ÷ default_weekly_hours × weeks-in-range). Cells get semantic heat tinting:
| Cell colour | Meaning |
|---|---|
| Amber | Under-tracked (< 80 % of expected weekly capacity) |
| Green | On target (80–110 %) |
| Red | Over-tracked (> 110 %) |
Surfaces "who's stretched, who's coasting" at a glance.
Utilization
KPI strip + per-member bar chart + table. Reports billable hours as a percentage of contracted capacity per member.
| KPI | Formula |
|---|---|
| Avg utilization | Average of all members' utilization % |
| Billable hours | Sum of billable hours in range |
| Capacity hours | default_weekly_hours × weeks-in-range × member-count |
The table breaks each member down: Total / Billable / Capacity / Utilization %. Bar colour: green ≥ 80 %, amber 50–79 %, red < 50 %.
The capacity denominator falls back to the workspace's default_weekly_hours when People isn't subscribed; with People, each employee's individual weekly_hours is used.
Saved views
Take any combination of filters + tab and click Save current view in the Saved views popover. It's persisted in the cross-cutting views table with a 'time_report' marker. Click any saved view to re-apply the snapshot.
Saved views can be:
- Private (per-user, the default) — only the creator sees them
- Shared (workspace-wide) — every workspace member sees them
For more advanced reports — charts beyond the six tabs — save a configured custom report from /time/reports/builder. See Custom Reports.
Scheduled email delivery
Every saved view can be emailed on a recurring schedule.
- Open the Saved views popover.
- Click the Clock icon on any view (the icon is highlighted when a schedule already exists).
- Pick frequency: daily (yesterday's data), weekly (Monday, last 7 days), monthly (1st, prior month).
- Enter up to 20 recipient emails, comma- or space-separated.
- Enabled toggle lets you pause delivery without losing the recipient list. Re-enable any time.
- Schedule to save.
What recipients receive: a clean HTML digest with the report's KPIs (Total / Billable / Revenue), top 5 projects, top 10 recent entries, and a link to the live report at the right tab and date range. Subject line = view name + period (e.g. "Q2 billable summary — last 7 days").
The cron fires at 06:20 UTC daily, decides per-schedule whether to fire today (always for daily, only Mondays for weekly, only the 1st for monthly), and stamps last_sent_at so it doesn't double-send if it retries.
Schedule manager
/time/reports/schedules (mail-icon link in the reports toolbar) lists every active and paused schedule in the workspace. Per row:
- View name (links back to the builder for custom reports, or to the tab for built-in views)
- Frequency badge (daily / weekly / monthly / paused)
- Recipient list
- Last sent timestamp + next-send projection
- Pause / Resume toggle (preserves recipient + frequency config)
- Delete
This is the surface for "we have 15 schedules, who's on the Q2 deliveries?" — a single pane vs. opening each saved view's clock icon.
Export
| Format | Where it works | Notes |
|---|---|---|
| CSV | Detailed (always wired); other tabs as their aggregated shape | Browser-side download via Blob URL |
| All six tabs | Server-rendered via Puppeteer; toast-tracked progress (5–15 s); no charts in the PDF — KPIs and tables only |
PDF generation runs the same Chrome stack as Finance invoices and Sales quotations. The exported HTML has JavaScript disabled in Puppeteer, so client-supplied content cannot execute scripts on the server.
Where the data flows
time_entries (filtered by query params)
│
├── TimeReportService.summaryRollup(filters)
│ → KPI cards, project donut, breakdown table
│
├── TimeReportService.detailedList(filters, page, limit)
│ → flat list, paginated
│
├── TimeReportService.weeklyPivot(filters)
│ → members × weeks heatmap
│
├── TimeReportService.profitabilityByProject(filters)
│ → revenue / cost / margin per project
│
├── TimeReportService.utilizationReport(filters, weeklyCapacity)
│ → utilization % per member
│
└── TimeCustomReportService.rollup(filters, primary, secondary?)
→ flexible 2-D map for the Custom Report BuilderEvery method shares the same filter shape and respects URL filters + approved-only semantics where applicable.
Where else reports live
/projects/[id]Budget tab — single-project actuals (uses the same rollup service)./people/employees/[id]Utilization tab — single-employee read-only view (lights up when People is subscribed)./dashboardwidgets — workspace billable % can be added as a dashboard widget.- CRM deal detail — projected cost + projected margin in the right rail (uses the same cost-rate snapshot).
/api/v1/time/reports/summary— the Summary tab payload over REST, useful for the browser extension and external dashboards.
Next steps
- Custom Reports — drag-drop builder for reports beyond the six tabs
- Time Pro — what unlocks Profitability + advanced approvals
- Rates & Billing — how bill rate + cost rate flow into reports
- Integrations — pull report data over the public API