Skip to content
richardbwangPublic

About

Farm activity dashboard — hands-free voice logs, live review dashboard. Live demo ↓

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Toph — farm activity dashboard

Toph lets farm workers log field work by voice and lets the farm manager review those logs on a desktop dashboard. This repository is the dashboard from the LavaLab F26 Dev Challenge Figma, implemented as a full-stack app: a Postgres database whose tables mirror the shape of the page, session-based login, and a working voice-log pipeline behind the "Play Recording" button.

Try it — nothing to install

Live site: https://toph-woad.vercel.app · sign in as admin@baysranch.com / toph-demo

It is deployed on Vercel with a persistent Postgres database on Neon, so there is nothing to build or run locally. A 60-second tour:

  1. Dashboard — expand a row (click it or View): the recording plays, the field appears on the satellite map, tags and review status are live.
  2. Filters — use Sort / Filter / Search and watch the URL change; refresh the page and the view is still there.
  3. Record (sidebar, or https://toph-woad.vercel.app/record) — tap Start recording, allow the microphone, and answer the five questions out loud; the phone reads them to you and files the log by itself. It appears on the dashboard within a few seconds, without a reload.
  4. Audit Manager — every review, tag and upload you just did is there.

Works in Chrome or Safari on a laptop or a phone (Firefox has no speech recognition, so the recorder falls back to typing). Running it locally is described further down for anyone who wants to read the code with the app.

The Toph dashboard

What's in the box

Area What it does
Dashboard (/dashboard) The Figma frame. Stat cards, the "New Employee Logs" table, Sort / Filter / Search, expandable rows with waveform playback, tags, review status, transcript summary and a satellite map of the field. Updates live: a log filed from a phone appears without a reload.
Activity Logs The same table over every log, every status, newest first, with a status column.
Record (/record) The worker side, hands-free after one tap: the phone reads each of five questions aloud, transcribes the answer live, moves on when the worker pauses or says "next", and files the log through the API.
Map, Employees, Audit Manager, Reports, Performance Secondary pages driven by the same data: fields on a map, per-worker stats, an append-only audit trail, a monthly report with CSV export.
Auth Email + password sign-in, server-side sessions, admin vs. worker roles, "Switch User" and "Log Out" from the sidebar.

Everything on the page is read from Postgres on each request. Refresh, add a tag, mark a log reviewed, file a new voice log — it all persists.

Stack

  • Next.js 16 (App Router) + React 19 + TypeScript — one codebase renders the UI on the server, exposes the write API (Server Actions and Route Handlers) and deploys to Vercel with no separate backend.
  • Tailwind CSS 4 — the Figma values (Geist, #f2f2f2 rules, 14/16/20px radii, 0.88px waveform lines…) live as tokens in src/app/globals.css.
  • Postgres + Drizzle ORM — typed schema in src/db/schema.ts, plain-SQL migrations in drizzle/, hosted on Neon in production.
  • Hand-rolled session auth — bcrypt password hashes, an opaque token in an httpOnly cookie, SHA-256 of the token in the sessions table.
  • Leaflet with Esri World Imagery tiles for the field maps.
  • Claude (optional) turns a transcript into a structured log; a keyword heuristic keeps the demo working without an API key.

See DESIGN.md for why each of these was chosen and what was considered instead.

Running it locally

Prerequisites: Node 20+, a Postgres database (local, Neon, or Supabase).

git clone https://github.com/richardbwang/toph.git && cd toph
npm install
cp .env.example .env          # then set DATABASE_URL
npm run db:migrate            # creates the tables (drizzle/*.sql)
npm run db:seed               # loads the Bays Ranch demo data
npm run dev                   # http://localhost:3000

Sign in as admin@baysranch.com / toph-demo (every seeded account uses the same demo password; the login page lists them).

Environment variables

Variable Required Purpose
DATABASE_URL yes Postgres connection string. Local: postgresql://user:pass@localhost:5432/toph. Neon strings work as-is (?sslmode=require turns on verified TLS).
ANTHROPIC_API_KEY no Enables Claude for turning voice-log answers into a structured log. Without it a keyword heuristic is used.
ANTHROPIC_MODEL no Pins the model for the step above. Unset, the app asks the Models API for the newest Sonnet once per server process (the Settings page shows which one), so a retired model alias never silently breaks the pipeline.

Scripts

Script What it does
npm run dev Development server.
npm run build Applies pending migrations, then builds for production (this is what Vercel runs).
npm run db:generate Diff src/db/schema.ts against the last migration and write a new SQL migration.
npm run db:migrate Apply migrations in drizzle/.
npm run db:seed Reset and load the demo dataset (idempotent).
npm run db:studio Browse the database in Drizzle Studio.
npm run audio:generate Re-synthesise the demo voice clips (needs ffmpeg, espeak-ng, mbrola, festival — not required to run the app; the clips are committed).
npm test Unit tests for the timezone and spoken-time logic (src/lib/time.test.ts, Node's built-in test runner).
npm run lint / npm run typecheck ESLint / tsc --noEmit.

How the pieces fit

browser ──GET /dashboard?period=month&sort=date-asc──▶ Server Component
                                                        │ requireUser()  (session cookie → users/farms)
                                                        │ listLogs(farm, filters) ──▶ Postgres
                                                        ▼
                                                   HTML with the rows
        ◀── click "Add Tag" ── Server Action addTag() ──▶ INSERT log_tags + audit_events
                                                        └ revalidatePath → page re-renders

phone ──POST /api/recordings (audio + answers + peaks)─▶ Route Handler
                                                        │ INSERT recordings (status PROCESSING)
                                                        │ extractLog()  (Claude or heuristic)
                                                        │ INSERT activity_logs (status NEW)
                                                        ▼
                                              appears on the dashboard
  • State lives in the URL. Search, sort, period, status, activity and field filters are query parameters (src/lib/filters.ts, src/lib/url.ts). The server renders the right rows on the first request; refresh, back button and shareable links all work for free.
  • Reads are in src/lib/queries.ts (Drizzle query builder, farm-scoped).
  • Writes are Server Actions in src/lib/actions.ts (tags, review status, delete, login/logout) and the ingest Route Handler in src/app/api/recordings/route.ts. Every write re-checks the session, the farm and the role, and appends an audit_events row.
  • Live updates: pages poll GET /api/pulse every 5 s while visible (the farm's audit-event count, one indexed row) and re-render only when it changed (src/components/live-refresh.tsx).
  • Time is stored as absolute instants (timestamptz) and always displayed in the farm's timezone (src/lib/time.ts), so "Today" means today in California even though Vercel runs in UTC. Spoken times ("ten to twelve") are clock times with no date; they resolve to the most recent past instant with that wall-clock time, so a log filed just after midnight about the morning lands on the right day (resolveSpokenRange, unit-tested).

Data model

farms ──┬── users (role: ADMIN | WORKER, status)
        ├── fields (GeoJSON boundary + centre)
        ├── recordings (audio, transcript, waveform peaks, status)
        │      └── activity_logs 0..1  (activity, field, start/end, product, accuracy, status NEW|REVIEWED|FLAGGED)
        │                └── log_tags ── tags
        ├── audit_events (append-only)
        └── sessions (via users)

The full schema with comments is in src/db/schema.ts; the SQL Postgres actually runs is in drizzle/0000_init.sql.

How the dashboard numbers are defined:

Figure Definition
Todays Recordings · n New Recordings captured today (farm timezone) · those whose log is still NEW.
Active Workers Users with role WORKER and status ACTIVE.
Response Accuracy Average response_accuracy (0–100, how many guided questions got a usable answer) over this month's logs.
New Employee Logs (n) Logs matching the current filters — by default status NEW, this month, oldest first.
Dashboard badge NEW logs that arrived today.

Deploying

  1. Push this repository to GitHub.
  2. In Vercel: Add New → Project → Import the repo (defaults are fine).
  3. In the project: Storage → Create Database → Neon (Postgres). Vercel injects DATABASE_URL into the project automatically.
  4. Optionally add ANTHROPIC_API_KEY under Settings → Environment Variables.
  5. Deploy. The build runs drizzle-kit migrate before next build, so the tables exist on the first deploy.
  6. Load the demo data once: DATABASE_URL="<the Neon URL>" npm run db:seed from your machine (copy the URL from the Storage tab).

Project layout

src/
  app/                 routes (App Router)
    (app)/             everything behind login — layout.tsx renders the sidebar
    api/               Route Handlers: recording ingest, audio streaming, CSV export
    login/             sign-in page
  components/          UI: sidebar, logs table, filter chips, audio player, map, recorder…
  db/                  schema.ts (tables), index.ts (connection), seed.ts, seed-data.ts
  lib/                 auth, queries, actions, filters, time, extraction, voice-log
  proxy.ts             cheap cookie check that bounces anonymous visitors to /login
drizzle/               SQL migrations
public/audio/          demo voice clips + waveform peaks
scripts/gen-audio.ts   how those clips were made

About

Farm activity dashboard — hands-free voice logs, live review dashboard. Live demo ↓

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages