This commit is contained in:
2026-07-27 15:26:17 -05:00
parent 32c8ccc3ed
commit 6cb13650ed
16 changed files with 1924 additions and 148 deletions

View File

@@ -6,6 +6,7 @@ Module 3: recursive file listing, PDF streaming from the NAS and the ported
pdf.js viewer.
Module 4: the buyer side — buyers, contacts, NDA rounds, deals, the guided
"New inquiry" flow with duplicate detection and the deal status transitions.
Module 5: notes, todos and the "Today" view with the follow-up workflow.
The UI and all domain constants are English.
@@ -109,7 +110,7 @@ directory aborts the scan with an error naming the path.
3. Copy the project folder to the AI machine, run `docker compose up -d --build`
4. Restore the dump: `docker compose exec -T db psql -U bizmatch bizmatch < backup.sql`
## API (as of module 4)
## API (as of module 5)
| Method | Path | Purpose | Session |
| ------ | --------------------------- | ------------------------------------------------ | ------- |
@@ -136,6 +137,18 @@ directory aborts the scan with an error naming the path.
| POST | /api/ndas/:id/deals | add a business to an existing round | yes |
| POST | /api/deals/:id/status | `{status, comment?}` — transition + note | yes |
| GET | /api/deals/:id/notes | notes of one deal, newest first | yes |
| POST | /api/deals/:id/follow-up-sent | `{comment?, rearm}` — note + re-arm or clear | yes |
| POST | /api/notes | create a note on exactly one reference object | yes |
| GET | /api/notes | notes of one object, `?…_id=` (+`include_related`) | yes |
| PATCH | /api/notes/:id | edit `text` / `highlight` | yes |
| DELETE | /api/notes/:id | delete a note | yes |
| POST | /api/todos | create a todo | yes |
| GET | /api/todos | `?assigned_to=&status=` + one ref id as scope | yes |
| PATCH | /api/todos/:id | text, due_at, assigned_to, kind, document_id | yes |
| POST | /api/todos/:id/done | close it (`done_by`/`done_at` = session, now) | yes |
| POST | /api/todos/:id/reopen | reopen it and clear both | yes |
| POST | /api/documents | pin a business file so a REVIEW todo can link it | yes |
| GET | /api/today | `?staff_id=` — the day's work + nav counts | yes |
Everything except health, staff (GET+POST) and login requires the session
cookie; without it the API answers `401`.
@@ -196,17 +209,62 @@ alphabetical.
* `.pdf` is served as `application/pdf` (inline), anything else as
`application/octet-stream` with `Content-Disposition: attachment`.
### Notes, todos and Today (module 5)
A **note** hangs off exactly one object (buyer, deal, business or NDA round), a
**todo** off at most one — the DB enforces both, and the API mirrors it so a
wrong body gets a `400` instead of a constraint violation. Every note and todo
row carries a `context` object `{type, id, label, buyer_id, business_id}`: the
label is the business name, the buyer's company/contact name or
`Round <date>`, and the two ids let the UI link straight to the right page.
`GET /api/notes?buyer_id=…&include_related=true` additionally folds in the
notes of that buyer's rounds and deals, which is what the buyer page shows.
`GET /api/today` is the daily workqueue, narrowed to one person with
`?staff_id=` and covering the whole team without it:
* **todos** — OPEN, `due_at <= today`, each flagged `overdue` when
`due_at < today`
* **follow_ups** — deals with `follow_up_at <= today` that are not `ENDED`;
"mine" means `created_by`
* **pending_ndas** — rounds still `SENT` after `NDA_REMINDER_DAYS` (env,
default 14); "mine" means `created_by`
* **counts** — the numbers behind the nav badge
Follow-ups and pending NDAs are **virtual**: they are derived from the deal and
nda rows on every request and never materialise as todo rows, so there is
nothing to keep in sync or clean up. Answering one is
`POST /api/deals/:id/follow-up-sent`, which always writes a note and then
either re-arms the reminder for another 14 days (`rearm: true`) or clears
`follow_up_at``409` on a deal that is already ENDED. Giving up on the deal
instead is the normal `POST /api/deals/:id/status` with `ENDED`.
`POST /api/documents` is a stopgap for the REVIEW todo's file picker: it pins
one business file (validated through the same resolver the file streaming uses)
as a `document` row so `todo.document_id` can point at it. Real document
management follows in module 6.
## Frontend
`web/` is a Vite + React + TypeScript app with Tailwind v4 (no router, no state
library). The header carries the two nav entries **Businesses** and **Buyers**;
routing is a hand-rolled `{view, id}` state in `App.tsx`. Views:
library). The header carries the nav entries **Today**, **Businesses** and
**Buyers**; routing is a hand-rolled `{view, id}` state in `App.tsx`. Today is
the landing view and its nav entry shows a red badge with the signed-in user's
overdue + due todos. There is no polling, so the badge is refreshed on mount,
on every view change and after any action that can move an item off the list.
Views:
* login ("Who is working?")
* today — "My day" / "Team" tabs (Team groups todos by assignee and follow-ups
by their creator) over three sections: todos (overdue in red, checkbox to
complete, context chip opens the buyer or business), follow-ups due (with
"Follow-up sent…" → comment + "wait another 14 days" / "stop waiting", and
"End deal…" → the status dialog preset to ENDED) and NDA signatures pending.
Empty state: "Nothing due. Enjoy your coffee."
* business list (tabs with counts, search, "Scan NAS now")
* business detail — a master-detail split filling the viewport: file table
left, PDF viewer right, plus a collapsed "Buyer activity" panel linking to
the buyers who were introduced to this business
left, PDF viewer right, plus collapsed "Buyer activity", "Notes" and "Todos"
panels above it
* buyer list (status chips with counts, search over company/contact/e-mail,
"New inquiry")
* new inquiry — contact + business picker; while typing a known name, e-mail or
@@ -216,11 +274,16 @@ routing is a hand-rolled `{view, id}` state in `App.tsx`. Views:
historic deals in their real state.
* buyer detail — status header with Deactivate/Reactivate (warns about the open
deals it would end), inline-editable identity panel, contacts with a primary
star, and the NDA rounds newest first: editable round fields, the deals of
star, a notes panel covering the buyer *and* their rounds and deals, a todos
panel, and the NDA rounds newest first: editable round fields, the deals of
the round with an action menu (next step, "End deal", plus a "Correct to…"
section) that opens a comment dialog, and a collapsed read-only notes list
section) that opens a comment dialog, and a collapsed "Notes & todos" section
per deal.
Notes are written in a composer at the top of every notes panel; the red flag
button marks a note as important, and flagged notes get a red left border and a
light red background wherever they appear.
In dev, Vite proxies `/api` to `http://localhost:8090`. In production the
Fastify app serves `web/dist` via `@fastify/static` with an SPA fallback to
`index.html` for all non-`/api` routes; the Dockerfile builds the frontend in
@@ -263,21 +326,26 @@ migrations/ numbered SQL migrations
001_init.sql full schema
002_buyer_fields.sql buyer-side fields from the NDA form + intake sheet
003_…_nullable.sql interested_in_updates becomes tri-state
004_reset_….sql one-time reset of that column to NULL
src/
config.ts env configuration
db.ts pg pool, query helpers, withTransaction
session.ts the staff-id cookie
http.ts input coercion + PATCH/reference helpers for the routes
migrate.ts migration runner (transactional, advisory lock)
business-scan.ts NAS scan, recursive listing, safe file path resolution
server.ts Fastify app (health, staff, login, businesses, file, static)
buyer-routes.ts buyers, contacts, NDA rounds, deals, the inquiry flow
workflow-routes.ts notes, todos, documents, the Today view, follow-ups
web/
scripts/copy-pdfjs.mjs pdfjs-dist -> public/pdfjs/ (predev + prebuild)
public/viewer/ standalone, unbundled pdf.js viewer page
public/pdfjs/ generated, git-ignored pdf.js runtime
src/api.ts typed API client
src/App.tsx session gate + nav + view switch
src/App.tsx session gate + nav (with the Today badge) + view switch
src/components.tsx shared bits (badges, inline fields, business picker, dialog)
src/views/ Login, Businesses, BusinessDetail, Buyers, BuyerDetail, NewInquiry
src/workflow.tsx notes panel, todo list and the add-todo dialog
src/views/ Login, Today, Businesses, BusinessDetail, Buyers,
BuyerDetail, NewInquiry
viewer-phase1/ reference copy of the phase-1 desktop viewer
```