This commit is contained in:
Luciano Milani
2026-07-02 12:47:55 +01:00
commit 5de37bb512
132 changed files with 28495 additions and 0 deletions
@@ -0,0 +1,175 @@
# 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-950` root, `bg-slate-900` panels, `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.message` in 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