feat(frontend): Dashboard + Orders UI + Vehicle Picker + Vehicles list

- Pinia stores: orders (CRUD, line management, totals recalc, stats) and vehicles (CRUD, search, marca/model cascade)
- useSync composable: auto-sync on window focus + periodic 60s interval
- VehiclePicker component: debounced autocomplete search by nr. inmatriculare or client name
- OrderLineForm component: manopera/material toggle with live total preview
- DashboardView: stats cards (orders, vehicles, revenue), recent orders list
- OrdersListView: filterable table (all/draft/validat/facturat), clickable rows
- OrderCreateView: vehicle picker + inline new vehicle form, tip deviz select, km/observatii
- OrderDetailView: order info, lines table with add/remove, totals, validate action
- VehiclesListView: searchable table, inline create form with marca/model cascade
- AppLayout: mobile hamburger menu with slide-in sidebar overlay

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 17:29:02 +02:00
parent 907b7be0fd
commit ad41956ea1
11 changed files with 1236 additions and 15 deletions

View File

@@ -0,0 +1,173 @@
import { defineStore } from 'pinia'
import { execSQL, notifyTableChanged } from '../db/database.js'
import { syncEngine } from '../db/sync.js'
import { useAuthStore } from './auth.js'
export const useOrdersStore = defineStore('orders', () => {
const auth = useAuthStore()
async function getAll(statusFilter = null) {
let sql = `SELECT * FROM orders WHERE tenant_id = ? ORDER BY created_at DESC`
const params = [auth.tenantId]
if (statusFilter) {
sql = `SELECT * FROM orders WHERE tenant_id = ? AND status = ? ORDER BY created_at DESC`
params.push(statusFilter)
}
return execSQL(sql, params)
}
async function getById(id) {
const rows = await execSQL(`SELECT * FROM orders WHERE id = ?`, [id])
return rows[0] || null
}
async function getLines(orderId) {
return execSQL(
`SELECT * FROM order_lines WHERE order_id = ? ORDER BY ordine, created_at`,
[orderId]
)
}
async function getRecentOrders(limit = 5) {
return execSQL(
`SELECT * FROM orders WHERE tenant_id = ? ORDER BY created_at DESC LIMIT ?`,
[auth.tenantId, limit]
)
}
async function create(data) {
const id = crypto.randomUUID()
const now = new Date().toISOString()
const nr = `CMD-${Date.now().toString(36).toUpperCase()}`
// Lookup vehicle info for denormalized fields
let clientNume = '', clientTelefon = '', nrAuto = '', marcaDenumire = '', modelDenumire = ''
if (data.vehicle_id) {
const [v] = await execSQL(`SELECT * FROM vehicles WHERE id = ?`, [data.vehicle_id])
if (v) {
clientNume = v.client_nume || ''
clientTelefon = v.client_telefon || ''
nrAuto = v.nr_inmatriculare || ''
const [marca] = await execSQL(`SELECT denumire FROM catalog_marci WHERE id = ?`, [v.marca_id])
const [model] = await execSQL(`SELECT denumire FROM catalog_modele WHERE id = ?`, [v.model_id])
marcaDenumire = marca?.denumire || ''
modelDenumire = model?.denumire || ''
}
}
await execSQL(
`INSERT INTO orders (id, tenant_id, nr_comanda, data_comanda, vehicle_id, tip_deviz_id, status, km_intrare, observatii, client_nume, client_telefon, nr_auto, marca_denumire, model_denumire, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
[id, auth.tenantId, nr, now, data.vehicle_id || null, data.tip_deviz_id || null,
'DRAFT', data.km_intrare || 0, data.observatii || '',
clientNume, clientTelefon, nrAuto, marcaDenumire, modelDenumire, now, now]
)
notifyTableChanged('orders')
await syncEngine.addToQueue('orders', id, 'INSERT', {
id, tenant_id: auth.tenantId, nr_comanda: nr, data_comanda: now,
vehicle_id: data.vehicle_id, tip_deviz_id: data.tip_deviz_id,
status: 'DRAFT', km_intrare: data.km_intrare || 0, observatii: data.observatii || '',
client_nume: clientNume, client_telefon: clientTelefon, nr_auto: nrAuto,
marca_denumire: marcaDenumire, model_denumire: modelDenumire
})
return id
}
async function addLine(orderId, line) {
const id = crypto.randomUUID()
const now = new Date().toISOString()
const total = line.tip === 'manopera'
? (line.ore || 0) * (line.pret_ora || 0)
: (line.cantitate || 0) * (line.pret_unitar || 0)
const maxOrdine = await execSQL(
`SELECT MAX(ordine) as mx FROM order_lines WHERE order_id = ?`, [orderId]
)
const ordine = (maxOrdine[0]?.mx || 0) + 1
await execSQL(
`INSERT INTO order_lines (id, order_id, tenant_id, tip, descriere, norma_id, ore, pret_ora, um, cantitate, pret_unitar, total, mecanic_id, ordine, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
[id, orderId, auth.tenantId, line.tip, line.descriere || '',
line.norma_id || null, line.ore || 0, line.pret_ora || 0,
line.um || 'buc', line.cantitate || 0, line.pret_unitar || 0,
total, line.mecanic_id || null, ordine, now, now]
)
notifyTableChanged('order_lines')
// Recalculate order totals
await recalcTotals(orderId)
await syncEngine.addToQueue('order_lines', id, 'INSERT', {
id, order_id: orderId, tenant_id: auth.tenantId,
tip: line.tip, descriere: line.descriere || '',
ore: line.ore || 0, pret_ora: line.pret_ora || 0,
um: line.um || 'buc', cantitate: line.cantitate || 0,
pret_unitar: line.pret_unitar || 0, total
})
return id
}
async function removeLine(lineId, orderId) {
await execSQL(`DELETE FROM order_lines WHERE id = ?`, [lineId])
notifyTableChanged('order_lines')
await recalcTotals(orderId)
await syncEngine.addToQueue('order_lines', lineId, 'DELETE', {})
}
async function recalcTotals(orderId) {
const [man] = await execSQL(
`SELECT COALESCE(SUM(total), 0) as s FROM order_lines WHERE order_id = ? AND tip = 'manopera'`, [orderId]
)
const [mat] = await execSQL(
`SELECT COALESCE(SUM(total), 0) as s FROM order_lines WHERE order_id = ? AND tip = 'material'`, [orderId]
)
const totalManopera = man?.s || 0
const totalMateriale = mat?.s || 0
const totalGeneral = totalManopera + totalMateriale
const now = new Date().toISOString()
await execSQL(
`UPDATE orders SET total_manopera=?, total_materiale=?, total_general=?, updated_at=? WHERE id=?`,
[totalManopera, totalMateriale, totalGeneral, now, orderId]
)
notifyTableChanged('orders')
await syncEngine.addToQueue('orders', orderId, 'UPDATE', {
total_manopera: totalManopera, total_materiale: totalMateriale, total_general: totalGeneral
})
}
async function validateOrder(orderId) {
const now = new Date().toISOString()
await execSQL(`UPDATE orders SET status='VALIDAT', updated_at=? WHERE id=?`, [now, orderId])
notifyTableChanged('orders')
await syncEngine.addToQueue('orders', orderId, 'UPDATE', { status: 'VALIDAT' })
}
async function getStats() {
const [total] = await execSQL(
`SELECT COUNT(*) as cnt FROM orders WHERE tenant_id = ?`, [auth.tenantId]
)
const [draft] = await execSQL(
`SELECT COUNT(*) as cnt FROM orders WHERE tenant_id = ? AND status = 'DRAFT'`, [auth.tenantId]
)
const [validat] = await execSQL(
`SELECT COUNT(*) as cnt FROM orders WHERE tenant_id = ? AND status = 'VALIDAT'`, [auth.tenantId]
)
const [totalVehicles] = await execSQL(
`SELECT COUNT(*) as cnt FROM vehicles WHERE tenant_id = ?`, [auth.tenantId]
)
const [revenue] = await execSQL(
`SELECT COALESCE(SUM(total_general), 0) as s FROM orders WHERE tenant_id = ? AND status = 'VALIDAT'`, [auth.tenantId]
)
return {
totalOrders: total?.cnt || 0,
draftOrders: draft?.cnt || 0,
validatedOrders: validat?.cnt || 0,
totalVehicles: totalVehicles?.cnt || 0,
totalRevenue: revenue?.s || 0,
}
}
return { getAll, getById, getLines, getRecentOrders, create, addLine, removeLine, validateOrder, getStats }
})