2026-05-16 17:42:04 +01:00
2026-05-30 12:14:46 +01:00
2026-05-16 17:06:27 +01:00
2026-05-16 15:21:27 +01:00
2026-05-30 11:54:38 +01:00
2026-05-30 11:54:38 +01:00
2026-05-30 11:54:38 +01:00
2026-05-16 12:01:26 +01:00
2026-05-16 12:02:15 +01:00
2026-05-16 12:02:15 +01:00
2026-05-16 12:02:15 +01:00
2026-05-16 12:02:15 +01:00
2026-05-16 15:32:56 +01:00
2026-05-16 15:32:56 +01:00
2026-05-30 11:54:38 +01:00
2026-05-16 12:02:15 +01:00
2026-05-30 11:54:38 +01:00
2026-05-16 12:02:15 +01:00
2026-05-16 16:19:15 +01:00

FieldOps — MAI CALL v0.1

Modular industrial SaaS monorepo. MAI CALL v0.1 is the first shipped feature: a full maintenance-request loop that runs on factory Wi-Fi (and survives losing it).

What's here

apps/
  operator-pwa/      Next.js 15 — operator mobile console (port 3000)
  admin-web/         Next.js 15 — maintenance queue for admin/supervisor (port 3001)
packages/
  db/                Prisma 6 schema + multi-tenant extension
  api/               tRPC v11 routers (maintenanceRequest, workstation, user, storage)
  storage/           ObjectStorage abstraction + MinIO/S3 implementation
  ui/                Shared shadcn-style components
  domain/            Pure domain logic (reserved for later modules)
  config/            TypeScript / ESLint / Tailwind presets
e2e/                 Playwright happy-path test

Prerequisites

Tool Version Notes
Node.js 22 LTS Required by Next.js 15
pnpm 11+ npm i -g pnpm@11 or corepack enable pnpm
Docker Desktop any recent Provides Postgres 16 + MinIO
Git any

Quick-start (< 15 min)

Run these commands from the repo root in order.

# 1. Copy and configure the environment file
cp .env.example .env
#    The defaults work out of the box for local dev.
#    AUTH_DEV_AUTOLOGIN is already "true" in .env — leave it.

# 2. Install dependencies
pnpm install

# 3. Start Postgres + MinIO (creates the fieldops bucket automatically)
docker compose up -d

# 4. Apply the database schema
pnpm db:migrate

# 5. Seed the demo tenant, 3 operators, and 3 workstations
pnpm db:seed

# 6. Start both apps
pnpm --filter @repo/operator-pwa dev &
pnpm --filter @repo/admin-web dev
URL What it is
http://localhost:3000 Operator PWA
http://localhost:3001 Admin / Manutenção queue
http://localhost:9001 MinIO console (see credentials below)

Demo flow

As an operator (port 3000)

  1. Open http://localhost:3000.
    With AUTH_DEV_AUTOLOGIN=true you land on the home page as admin@demo.local. To simulate a real operator log-in, navigate to http://localhost:3000/select-operator, tap op1@demo.local, then enter PIN 1111 on the keypad. (op2 = 2222, op3 = 3333)
  2. Tap Pedir manutenção.
  3. Select a workstation, optionally attach a photo, write a description, and tap Enviar pedido.
  4. The page shows "Pedido enviado" once the sync completes (usually within 12 seconds when online).

Offline test:
Chrome DevTools → Network → Offline → create 3 requests → Network → Online.
The requests sync automatically within ~10 s; "Tudo sincronizado" appears.

⚠️ The offline navigation test only works against a production build of operator-pwa. In pnpm dev mode, Next.js generates page chunks on demand, so the service worker has nothing to precache and navigation to /maintenance/new fails with Failed to fetch. See the Troubleshooting section for the workaround.

As admin / maintenance supervisor (port 3001)

  1. Open http://localhost:3001.
    With AUTH_DEV_AUTOLOGIN=true you land on the maintenance queue automatically. Without it, you see a login form — use admin@demo.local / admin1234.
  2. The queue refreshes every 5 s; new requests appear automatically.
  3. Click Aceitar to claim a request (status: Em curso).
  4. Click Marcar resolvido, optionally add a note, click Confirmar (status: Resolvido).
  5. The document tab title shows (N) FieldOps — Manutenção when there are open requests.

MinIO (photo storage)

Setting Value
API endpoint http://localhost:9000
Web console http://localhost:9001
Root user fieldops
Root password fieldops123
Bucket fieldops

Photos are stored as presigned-URL objects under tenants/{tenantId}/maintenance/{uuid}.jpg.

To back up locally:

# Install mc (MinIO client) then:
mc alias set local http://localhost:9000 fieldops fieldops123
mc mirror local/fieldops ./backup-photos

Running the E2E tests

# One-time browser install (downloads ~170 MB of Chromium)
pnpm --filter @repo/e2e install-browsers

# Run the happy-path test (starts both dev servers automatically)
pnpm test:e2e

The Playwright config force-sets AUTH_DEV_AUTOLOGIN=true for the child servers, so the test does not depend on the developer's .env.
Expected: 1 passed in ~30 s.


Known limitations — v0.1 (demo only)

Limitation Detail Target
No real authentication in dev AUTH_DEV_AUTOLOGIN=true lets anyone in as admin (dev/test only — ignored when NODE_ENV=production). Real auth is implemented: admin uses email + password, operators use list + PIN.
Operator picker + PIN, not TAG/card Operator identity is chosen from a list and confirmed with a PIN, rather than read from an RFID badge. MY QUALITY module
No multi-tenant onboarding UI Tenants are created via pnpm db:seed / SQL only. when 2nd customer onboards
No SLAs, alerts, or timers DomainEvent rows are written and ready; reporting is not built yet. v0.2
Single photo per request No video, audio, or multiple photos. when pilot asks
Safari / iOS Background Sync Background Sync API is not supported on Safari; sync falls back to main-thread polling every 10 s when the tab is open. acceptable for pilot
No push notifications Polling at 5 s on the admin-web tab is the notification mechanism. if pilot requires it
Dev-only storage MinIO runs in Docker. No backup cron, no lifecycle policy, no cloud migration yet. before pilot
No i18n Hardcoded Portuguese (Mangualde plant). v0.2 with pilot
No observability Structured logs via Pino; no trace/metric pipeline. when pilot requires it

⚠️ NEVER deploy with AUTH_DEV_AUTOLOGIN=true. That flag is a back door for local dev and CI only. It is ignored at the code level when NODE_ENV=production, so a misconfigured .env in production won't open a hole. The chokepoints are apps/*/lib/auth.ts → resolveUser() and apps/*/middleware.ts.


Common commands

Command What it does
pnpm install Install all workspace deps
docker compose up -d Start Postgres + MinIO (detached)
docker compose down Stop services (data persists in volumes)
docker compose down -v Stop and delete all data
pnpm --filter @repo/operator-pwa dev Operator PWA only (port 3000)
pnpm --filter @repo/admin-web dev Admin web only (port 3001)
pnpm db:migrate Apply pending Prisma migrations
pnpm db:seed Re-seed demo data (idempotent, safe to re-run)
pnpm db:reset Drop, recreate, migrate, seed
pnpm db:studio Open Prisma Studio at http://localhost:5555
pnpm typecheck Typecheck every package
pnpm test:e2e Run the happy-path Playwright test
pnpm format Prettier write across the workspace
pnpm tsx scripts/storage-smoke.ts Verify MinIO presigned upload/download
pnpm tsx scripts/maintenance-smoke.ts Verify the full create→claim→resolve cycle
pnpm tsx scripts/auth-smoke.ts Verify hashing, PIN/password login, and lockout

Architecture notes

Multi-tenancy

Every Prisma model except Tenant carries a tenantId column. Tenant scoping is enforced at the Prisma layer via an extension in packages/db/src/tenant-extension.ts. Application code always goes through ctx.db.* (scoped) — the unscoped ctx.prisma is a code-review red flag.

Note on $transaction: The interactive-callback form of $transaction receives a Prisma client that does NOT support $extends. Tenant scoping inside transactions is done by manually injecting tenantId into each where/data clause. See packages/api/src/routers/maintenance-request.ts for the pattern.

Offline sync

The operator PWA stores pending requests in IndexedDB (Dexie 4). The sync loop (lib/queue/sync.ts) runs in the main thread — triggered by the online event, visibilitychange, and a 10 s polling interval. The Background Sync API is registered opportunistically (works in Chrome, ignored elsewhere). Communication between the sync loop and UI is via BroadcastChannel('mai-call-sync').

Storage

Photos are uploaded directly from the browser to MinIO via S3 presigned PUT URLs. The tRPC layer signs the URL; the browser performs the PUT. The object key is returned and stored in the MaintenanceRequest row. packages/storage implements the ObjectStorage interface with MinIO (via AWS SDK v3 S3 protocol) and is portable to AWS S3, Cloudflare R2, or Wasabi by changing the endpoint env var.


Troubleshooting

UNAUTHORIZED on every pageAUTH_DEV_AUTOLOGIN is false in your .env. Set it to true for local dev, or sign in via the operator picker.

Tenant not found — the seed was wiped. Run pnpm db:seed.

DATABASE_URL not found.env is missing or Docker Postgres is not running. Run docker compose up -d then retry.

ERR_PNPM_IGNORED_BUILDS after adding a package — pnpm 11 blocks postinstall scripts by default. Add the package to pnpm-workspace.yaml allowBuilds:.

Playwright: port already in use — kill leftover dev servers:

# Windows
Get-Process node | Stop-Process -Force
# macOS / Linux
pkill -f 'next dev'

MinIO photos not loading in admin-web — verify MinIO is running (docker compose ps) and that the fieldops bucket exists (docker logs fieldops-minio-init-1).

Offline navigation fails with Failed to fetch / NetworkOnly — the PWA service worker can only serve pages it has precached, and pnpm dev builds pages on demand (no precache). To test the full offline flow, run the operator PWA in production mode:

# Stop the running `pnpm dev` for operator-pwa first, then:
pnpm --filter @repo/operator-pwa build
pnpm --filter @repo/operator-pwa start

Load http://localhost:3000 once while online (so the SW precaches the shell), reload the tab to activate the new SW, then switch DevTools to Offline. The admin-web can stay in dev mode — only the operator PWA needs the production build for offline navigation. The IndexedDB sync queue itself works in both modes; only the page-navigation layer requires precache.


License

Internal. All rights reserved.

Description
No description provided
Readme 1.7 MiB
Languages
TypeScript 68.6%
JavaScript 30.2%
CSS 1.2%