5.8 KiB
5.8 KiB
TechXCar — Plan 3: Core Workshop Pages
Date: 2026-06-22 Status: Approved
Scope
Build the 3 missing tenant-facing pages: Clients, Catalog, Work Orders. Backend has all 31 endpoints ready. Frontend needs hooks, pages, modals, and routing wired up.
Architecture
Approach: hooks-per-domain + pages. Each domain has a dedicated hook file in frontend/src/hooks/ that wraps TanStack Query. Pages consume hooks and stay presentational.
New files
frontend/src/
├── hooks/
│ ├── useClients.ts
│ ├── useCatalog.ts
│ └── useWorkOrders.ts
├── components/
│ └── ui/
│ └── dialog.tsx ← shadcn Dialog (shared by all modals)
└── pages/app/
├── clients/
│ ├── ClientsPage.tsx
│ └── ClientDetailPage.tsx
├── catalog/
│ └── CatalogPage.tsx
└── work-orders/
├── WorkOrdersPage.tsx
└── WorkOrderDetailPage.tsx
New routes (added to App.tsx)
/app/clients → ClientsPage
/app/clients/:id → ClientDetailPage
/app/catalog → CatalogPage
/app/work-orders → WorkOrdersPage
/app/work-orders/:id → WorkOrderDetailPage
Domain: Clients
useClients.ts
| Hook | Method | Endpoint |
|---|---|---|
useClients() |
GET | /clients |
useClient(id) |
GET | /clients/:id |
useCreateClient() |
POST | /clients |
useUpdateClient() |
PUT | /clients/:id |
useDeleteClient() |
DELETE | /clients/:id |
useClientVehicles(clientId) |
GET | /clients/:id/vehicles |
useCreateVehicle(clientId) |
POST | /clients/:id/vehicles |
useUpdateVehicle() |
PUT | /vehicles/:id |
All mutations call queryClient.invalidateQueries on success.
ClientsPage
- Table columns: Nome, NIF, Telefone, Email, Data criação
- "Novo Cliente" button → modal (create)
- Edit icon per row → same modal prefilled
- Modal fields: nome (required), NIF, telefone, email, morada, notas
ClientDetailPage
- Header: client name + fields (NIF, phone, email, address, notes) + Edit button
- "Veículos" section below: table with matrícula, marca, modelo, ano
- "Adicionar Veículo" → modal: matrícula (required), marca (required), modelo (required), ano, VIN, quilómetros, notas
- Edit icon per vehicle row → same modal prefilled
- Back link →
/app/clients
Domain: Catalog
useCatalog.ts
| Hook | Method | Endpoint |
|---|---|---|
useCatalog() |
GET | /catalog |
useCreateCatalogItem() |
POST | /catalog |
useUpdateCatalogItem() |
PUT | /catalog/:id |
useDeleteCatalogItem() |
DELETE | /catalog/:id |
CatalogPage
- Table columns: Código, Nome, Categoria, Unidade, Preço Base, Estado (badge)
- "Novo Item" button → modal (create)
- Edit icon per row → modal prefilled
- Delete: button toggles to "Confirmar?" before calling DELETE
- Modal fields: código, nome (required), categoria, unidade (select: un/hora/litro/kg), preço base (number), activo (checkbox)
Domain: Work Orders
useWorkOrders.ts
| Hook | Method | Endpoint |
|---|---|---|
useWorkOrders(status?) |
GET | /work-orders?status= |
useWorkOrder(id) |
GET | /work-orders/:id |
useCreateWorkOrder() |
POST | /work-orders |
useTransitionWorkOrder() |
POST | /work-orders/:id/transition |
useAddWOItem() |
POST | /work-orders/:id/items |
useRemoveWOItem() |
DELETE | /work-orders/:id/items/:itemId |
useAddStaffHours() |
POST | /work-orders/:id/staff-hours |
useRemoveStaffHours() |
DELETE | /work-orders/:id/staff-hours/:shId |
WorkOrdersPage
- Table columns: Nº OT, Cliente, Veículo, Estado (badge colorido), Data
- Status filter: tabs (Todas / Abertas / Em Progresso / Concluídas / Faturadas / Canceladas)
- "Nova OT" → modal: cliente (searchable dropdown from
/clients), veículo (filtered by client), notas internas, notas cliente - Click row → navigate to
/app/work-orders/:id
WorkOrderDetailPage
Two-column layout:
Left column — info + transitions:
- OT number, status badge, client name, vehicle plate + brand/model
- Internal notes, client notes (editable inline via PUT)
- State stepper:
Aberta → Em Progresso → Concluída → Faturada - Transition buttons: advance to next state, or "Cancelar OT" (any state except invoiced)
- Back link →
/app/work-orders
Right column — items + hours:
- "Peças / Serviços" table: descrição, qty, preço unitário, desconto %, total
- "Adicionar Item" → modal: select catalog item (searchable), qty, unit price (prefilled from catalog), discount %
- Remove icon per row (DELETE)
- "Horas de Técnico" table: técnico (staff_id), horas, custo/hora, total
- "Adicionar Horas" → modal: staff_id (text for now — staff module in Plan 4), horas, custo/hora
- Remove icon per row (DELETE)
- Totals row at bottom: subtotal peças + subtotal horas + total geral
State badge colours
| Status | Colour |
|---|---|
| open | slate |
| in_progress | blue |
| completed | green |
| invoiced | purple |
| cancelled | red |
Shared components
dialog.tsx— shadcn Dialog, used by all create/edit modals. Install via shadcn CLI or copy pattern from existing ui components.- All pages follow existing dark slate theme:
bg-slate-950root,bg-slate-900panels,border-slate-700/800, white text. - Loading states:
<p className="text-slate-400">A carregar...</p> - Empty states:
<p className="text-slate-500 text-sm">Nenhum registo.</p> - Error states: show
ApiError.messagein a red banner.
Out of scope (Plan 4+)
- Staff management (staff_id is free text for now)
- PDF invoice generation
- Notifications (Telegram / email)
- Dashboard statistics
- Billing / invoicing module