# Summary

**Status:** in production
**Lives at:** `/` · `app/(app)/page.tsx`, `app/(app)/summary-table.tsx` · Postgres function `summary_by_project` (`supabase/migrations/20260521140001_summary_rpc.sql`)

## Summary
The home page. Big headline: current month's dollar value of Adi-equivalent
work, with hours and over/under the 140-hour monthly target underneath.
Below that, a sortable per-project breakdown showing Prev / This / All-time
dollars and hours per cell.

## Why
2026-05-20 — "The sheet also acts as a summary of hours in the 'summary'
tab. I can check any given month (by default 'this month') and see where
we're at per project for hours as well as overall. There's a target
number of hours, 140, and it shows if we're over or under."
2026-05-21 — "In the top line summary, I'd prefer to have the main stat
the current cost and below that in the small print have the total hours
with (+/- hours) below. I don't need next-month stuff. I don't need
all-time in the top line summary."
2026-05-21 — "Why does it load slowly? I'll have at least 100x more
data."

## Behavior
- The H1 is the month name; the page subtitle notes the current base rate.
- "← previous month" / "This month" / "next month" navigation buttons.
- **Headline panel**:
  - `$X,XXX.XX` (very large) — total cost, summed from
    `time_entries.billout_cost_usd` (locked at import).
  - `HH:MM hours` (medium) — total Adi-equivalent hours.
  - `+/- X.XXh vs 140h target` (small, green if over, amber if under).
- **By project** table (sortable):
  - Columns: Project / Prev (last month) / This (current month) / All time.
  - Each cell is `$X.XX (HH:MM)`.
  - Default sort: This descending (by cost).
  - Footer row: totals across all projects.
- The by-project table hides any project where both Prev and This are
  zero (cuts down on long-dormant projects). All-time is still
  considered for sorting.
- For team-member / source-email / single-project slicing, use the
  Entries page filters — the totals card there mirrors the same
  source/billout/cost numbers.

## Constraints & edge cases
- Aggregation is a single Postgres function: `summary_by_project(
  org_id, this_start, this_end, prev_start, prev_end)`. It scans
  `time_entries` once and returns one row per project with
  `this_seconds / prev_seconds / all_seconds`.
- Supporting partial index: `(org_id, project, start_at)` where
  `converted_duration_seconds IS NOT NULL`.
- Adi's hourly rate is looked up from `team_members` where
  `email = 'info@adipramono.com'`. If missing, falls back to `$14`.
- Months use the project's local time (no TZ conversion; matches the
  naive timestamps stored in `time_entries`).
- Target is hardcoded at 140h (`TARGET_HOURS` constant). Should become
  configurable per-org eventually.

## Performance
- Pre-RPC: 7+ paginated queries to `time_entries`, ~700ms at 3,300
  rows. Did not scale.
- Post-RPC: single RPC call, ~160ms at 3,300 rows. Response size is
  constant in row count (~3 KB for ~80 projects). Expected to stay
  sub-second through hundreds of thousands of entries.

## Permissions
- Anyone with org access can view.

## Open considerations
- 140h target is hardcoded. Move to `organizations.monthly_hour_target`
  or similar.
- No drill-down — clicking a project doesn't take you to a filtered
  Entries view. Should add that link.
- No comparison metric (this vs prev as a delta or percentage) in the
  per-project rows.
- No "year to date" or other time slices yet.
- The RPC result is treated as `bigint` from Postgres but converted to
  Number on the JS side. At sub-million entries this is fine.

## Tests
- ✅ FMT-001 … FMT-008 — formatting helpers.
- 🟡 SUM-001 RPC returns one row per project with this/prev/all
  seconds.
- 🟡 SUM-002 Dollar amounts equal `(converted_seconds / 3600) ×
  adi_rate`.

## Changelog
- **2026-05-20** — Initial: 4-card top stats (this / prev / next / all),
  per-project table with 4 month columns.
- **2026-05-21** — Top stats restructured (single big headline panel),
  next-month dropped, all-time removed from the top, dollar amounts
  added, sortable headers, default sort = This.
- **2026-05-21** — Performance: replaced client-paginated aggregation
  with the Postgres `summary_by_project` RPC.
- **2026-05-21** — `summary_by_project` now returns cost (summed from
  the locked `billout_cost_usd` column).
- **2026-05-21** — Removed the "By team member" table; replaced by the
  filter-aware totals card on Entries (with pivot filters for project /
  user / source email). The `summary_by_team_member` RPC was dropped.
  The by-project table now hides projects with zero prev+this.
- **2026-05-22** — By-project table regrouped on (operator, client,
  project). The table shows three sortable columns instead of one
  combined "project" string. See decisions.md #026.
- **2026-05-24** — Each per-project cell now stacks both **cost** and
  **billout** (owner-only). RPC return shape extended with
  `*_billout_usd` columns.
- **2026-05-24** — Pivot filters added at the top of /summary —
  Operator / Client / Source email / User. Each pivots the RPC call
  via new optional parameters; results are reactive.
- **2026-05-24** — Project-level billout adjustments
  (`projects.billout_adjustment_pct` / `_amount`) flow into the RPC
  return as `*_billout_adj_usd`. The displayed billout column uses
  the adjusted value. See [adjustments.md](adjustments.md).
- **2026-05-25** — Post-eliminate-proportion: "billout hours" =
  source hours (i.e. the displayed Hours column uses
  `duration_seconds`, not `converted_duration_seconds`). The RPC's
  `billout_seconds` field is `sum(duration_seconds)` after the
  migration. See decisions.md #034.
