diff --git a/.planning/phases/14-pax8-ui-surface/14-04-SUMMARY.md b/.planning/phases/14-pax8-ui-surface/14-04-SUMMARY.md new file mode 100644 index 0000000..e2fa292 --- /dev/null +++ b/.planning/phases/14-pax8-ui-surface/14-04-SUMMARY.md @@ -0,0 +1,112 @@ +--- +phase: 14-pax8-ui-surface +plan: 04 +subsystem: frontend +tags: [next.js, react, pax8, datatable, detailmodal, navigation] + +# Dependency graph +requires: + - phase: 14-pax8-ui-surface + plan: 01 + provides: "GET /api/pax8/companies, GET /api/pax8/companies/[id]" + - phase: 14-pax8-ui-surface + plan: 03 + provides: "DetailModal kind='pax8_company' support + subscriptions cost-breakdown table" +provides: + - "app/pax8/page.tsx — top-level /pax8 page: PageHeader + Companies/Needs Review Tabs shell, Companies tab fully wired (DataTable + drill-down modal)" + - "Top-level PAX8 nav entry visible to all authenticated users" +affects: ["14-05 (fills the Needs Review TabsContent placeholder)", "14-06 (manual verification of the full page)"] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Fetch-then-open drill-down: row click fetches /api/pax8/companies/[id] before setting selectedCompany and opening DetailModal, keeping the loading state on the DataTable rather than an empty dialog" + - "DataTable column-key -> API sort-value map (SORT_KEY_MAP) for columns whose key doesn't match the API's whitelisted sort param 1:1 (matched -> match, activeSubscriptionCount -> subscriptions, location -> city)" + +key-files: + created: + - app/pax8/page.tsx + modified: + - components/navigation/app-navigation.tsx + +key-decisions: + - "Companies tab uses page size 25 (1-based page state converted to offset = (page-1)*pageSize for the API's limit/offset contract)" + - "The 'location' column combines city, stateOrProvince, country into one display string but maps to the API's 'city' sort key (no combined-location sort exists server-side)" + - "Needs Review TabsContent is a single-line muted placeholder with an explicit '/* Needs Review tab implemented in Plan 14-05 */' comment, per the plan's interface-first hand-off" + - "Nav icon: ShoppingCart (not previously used at the top level) — added to the existing lucide-react import in app-navigation.tsx" + +requirements-completed: [PAX8-13] + +# Metrics +duration: ~20min +completed: 2026-07-11 +--- + +# Phase 14 Plan 04: /pax8 Page Shell + Companies Tab + Nav Entry Summary + +**New top-level `/pax8` page: a PageHeader + Companies/Needs Review Tabs shell with the Companies tab fully wired to a sortable/searchable/paginated DataTable of PAX8 companies whose row-click fetches the subscriptions/cost-breakdown drill-down and opens the extended DetailModal; plus a top-level PAX8 nav entry visible to every authenticated user.** + +## Performance + +- **Duration:** ~20 min +- **Tasks:** 2 completed +- **Files modified:** 2 (1 new, 1 modified) + +## Accomplishments + +- `app/pax8/page.tsx` — a `'use client'` page rendering `PageHeader` (title "PAX8", `breadcrumbs=[{label:'PAX8'}]`, `accent`) followed by a `container mx-auto px-6 py-6 space-y-6` wrapper holding a `Tabs` (`companies` / `needs-review`). +- Companies tab: `fetchCompanies(page, search?, sort?, order?)` converts the DataTable's 1-based `page` to the API's `offset = (page-1)*pageSize` (page size 25), maps DataTable column keys to the API's whitelisted `sort` values via `SORT_KEY_MAP`, and renders a `DataTable` with 4 columns — `name` (sortable), `matched` (matched Autotask company name as a primary-colored span, or a secondary `Badge` reading "Unmatched"), `activeSubscriptionCount` (labeled "Subs", `font-mono tabular-nums`), and `location` (city/state/country joined, em-dash fallback). +- Row click (`handleRowClick`) fetches `/api/pax8/companies/${id}` first, then sets `selectedCompany` to `{ ...company, subscriptions, costTotal }` and opens `DetailModal` with `kind="pax8_company"` — fetch-then-open, keeping the loading indicator on the DataTable rather than an empty dialog. +- Empty-state copy matches UI-SPEC exactly: "No PAX8 companies synced yet" / "Run the PAX8 sync from /admin/integrations, then refresh this page." +- Needs Review `TabsContent` is a clearly marked placeholder (`{/* Needs Review tab implemented in Plan 14-05 */}` + a muted "Loading…" line) — not blank, not implemented, ready for Plan 05. +- `components/navigation/app-navigation.tsx` — added one top-level `navigationItems` entry (`title: 'PAX8'`, `href: '/pax8'`, `icon: ShoppingCart`, description) placed right after "Configuration Items". Not added to the `Engagement`/`Admin` super-admin `visibleItems` gate, so it's visible to every authenticated user on both the desktop `NavigationMenu` and mobile `Sheet` (both consume the same array) — satisfying D-07. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: /pax8 page shell + Companies tab (DataTable + DetailModal drill-down)** - `51470f8` (feat) +2. **Task 2: Add top-level PAX8 nav entry** - `543ac39` (feat) + +## Files Created/Modified + +- `app/pax8/page.tsx` — new top-level page: tab shell + Companies tab (DataTable + fetch-then-open DetailModal drill-down) + Needs Review placeholder +- `components/navigation/app-navigation.tsx` — added top-level PAX8 nav entry + `ShoppingCart` import + +## Decisions Made + +- Page size fixed at 25 for the Companies tab (no page-size selector this phase). +- `location` column is a display-only composite of `city`/`stateOrProvince`/`country`; its sort maps to the API's `city` sort key since no combined-location server-side sort exists. +- Chose `ShoppingCart` for the nav icon — not previously used at the top level of `navigationItems`. + +## Deviations from Plan + +None — plan executed exactly as written. Both tasks matched their `` and `` blocks without needing any Rule 1-4 deviation. + +## Verification + +- `npx tsc --noEmit --pretty` — passes (run after each task). +- Grep-confirmed: `value="companies"` and `value="needs-review"` both present; `kind="pax8_company"` present; `{/* Needs Review tab implemented in Plan 14-05 */}` present verbatim; no `swr`/`react-query` imports; `/api/pax8/companies` and `/api/pax8/companies/${row.id}` both fetched; `'/pax8'` present in `app-navigation.tsx`; `visibleItems` filter block unchanged (still only special-cases `'Engagement'`/`'Admin'`). +- Manual verification (page loads, row opens modal, nav item visible) is deferred to Plan 06 per this plan's own `` block. + +## Threat Flags + +None — this plan's threat model (T-14-03 info disclosure via `requireAuth()`-gated fetches, T-14-06 XSS via React auto-escaping, T-14-08 nav visibility accepted by design, T-14-SC zero new packages) is fully addressed by construction. No new network endpoints, auth paths, or schema changes were introduced — this plan only consumes existing Plan 01 routes and Plan 03's DetailModal extension. + +## Known Stubs + +- **Needs Review tab** (`app/pax8/page.tsx`, `TabsContent value="needs-review"`): renders a static muted "Loading…" line with no data source wired. This is the plan's intended interface-first hand-off — Plan 14-05 replaces this block with the full review-card UI. Not a defect; explicitly scoped out of this plan's ``. + +## User Setup Required + +None — no external service configuration required. + +## Next Phase Readiness + +- Plan 14-05 can now implement the Needs Review `TabsContent` in `app/pax8/page.tsx` without touching the Companies tab or the tab shell. +- Plan 14-06 (manual verification) can load `/pax8`, confirm the Companies tab lists real companies, click a row to see the cost-breakdown modal, and confirm the PAX8 nav item appears for an authenticated user. + +--- +*Phase: 14-pax8-ui-surface* +*Completed: 2026-07-11*