feat(reports): simplify dashboards, align OT flow labels, and normalize filters

This commit is contained in:
Luciano Milani
2026-07-04 20:54:13 +01:00
parent dc8c1bd69a
commit 4994abf4e2
14 changed files with 358 additions and 154 deletions
+12 -18
View File
@@ -4,17 +4,9 @@ import { useQueries, useQuery } from '@tanstack/react-query'
import { useTheme } from '@/hooks/useTheme'
import { apiFetch } from '@/lib/api'
import type { Client, Expense, Invoice, Staff, WorkOrder, WorkOrderDetail } from '@/lib/types'
import { WORK_ORDER_STATUS_LABEL } from '@/lib/workOrderStatus'
const STATUS_LABEL: Record<WorkOrder['status'], string> = {
quote: 'Orçamento',
open: 'Orçamento Aprovado',
in_progress: 'Em Curso',
completed: 'Concluída',
invoiced: 'Faturada',
cancelled: 'Cancelada',
}
const STATUS_ORDER: WorkOrder['status'][] = ['quote', 'open', 'in_progress', 'completed', 'invoiced', 'cancelled']
const STATUS_ORDER: WorkOrder['status'][] = ['quote', 'in_progress', 'completed', 'invoiced', 'cancelled']
type PeriodKey = 'month' | '30d' | '90d' | 'year'
const PERIOD_OPTIONS: { key: PeriodKey; label: string }[] = [
@@ -149,7 +141,8 @@ export default function DashboardPage() {
const byStatus = Object.fromEntries(STATUS_ORDER.map((s) => [s, 0])) as Record<WorkOrder['status'], number>
for (const wo of workOrdersPeriod) byStatus[wo.status] += 1
const activeOrders = byStatus.quote + byStatus.open + byStatus.in_progress
const activeOrders = byStatus.quote + byStatus.in_progress
const awaitingInvoice = workOrdersPeriod.filter((wo) => wo.status === 'completed').length
const periodExpenses = expensesPeriod.reduce((sum, e) => sum + e.amount, 0)
const monthExpenses = expensesMonth.reduce((sum, e) => sum + e.amount, 0)
const monthDocs = invoicesMonth.length
@@ -172,6 +165,7 @@ export default function DashboardPage() {
return {
byStatus,
activeOrders,
awaitingInvoice,
periodExpenses,
periodEntries,
monthEntries,
@@ -236,7 +230,7 @@ export default function DashboardPage() {
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<DashboardCard
title="OTs Ativas"
title="Orçamento + Iniciar Trabalho"
value={String(metrics.activeOrders)}
subtitle={`No período (${metrics.periodOrdersCount} OTs)`}
isLight={isLight}
@@ -326,7 +320,7 @@ export default function DashboardPage() {
return (
<div key={status}>
<div className="mb-1 flex justify-between text-xs">
<span className={isLight ? 'text-slate-700' : 'text-slate-300'}>{STATUS_LABEL[status]}</span>
<span className={isLight ? 'text-slate-700' : 'text-slate-300'}>{WORK_ORDER_STATUS_LABEL[status]}</span>
<span className={`font-mono ${isLight ? 'text-slate-500' : 'text-slate-400'}`}>{count}</span>
</div>
<div className={`h-2 rounded-full ${isLight ? 'bg-slate-200' : 'bg-slate-800'}`}>
@@ -355,16 +349,16 @@ export default function DashboardPage() {
>
<p className="text-xs uppercase tracking-wide">Ação recomendada</p>
<p className="mt-1">
{metrics.byStatus.completed > 0
? `${metrics.byStatus.completed} OT(s) concluída(s) pronta(s) para faturar.`
: 'Sem OTs concluídas pendentes de faturação.'}
{metrics.awaitingInvoice > 0
? `${metrics.awaitingInvoice} OT(s) em "Concluir OT" pronta(s) para faturar.`
: 'Sem OTs finalizadas pendentes de emissão de fatura.'}
</p>
</div>
<div className={`rounded-lg border px-3 py-2 ${isLight ? 'border-cyan-200 bg-cyan-50 text-cyan-900' : 'border-cyan-900/70 bg-cyan-950/25 text-cyan-100'}`}>
<p className="text-xs uppercase tracking-wide">Em orçamento</p>
<p className="mt-1">
{metrics.byStatus.quote > 0
? `${metrics.byStatus.quote} OT(s) em orçamento aguardam aprovação.`
? `${metrics.byStatus.quote} OT(s) em "Orçamento" prontas para "Iniciar Trabalho".`
: 'Não existem OTs pendentes em orçamento.'}
</p>
</div>
@@ -394,7 +388,7 @@ export default function DashboardPage() {
<li key={wo.id} className="flex items-center justify-between py-2">
<div>
<p className={`text-sm font-medium ${isLight ? 'text-slate-900' : 'text-white'}`}>OT #{wo.number}</p>
<p className={`text-xs ${isLight ? 'text-slate-600' : 'text-slate-400'}`}>{STATUS_LABEL[wo.status]}</p>
<p className={`text-xs ${isLight ? 'text-slate-600' : 'text-slate-400'}`}>{WORK_ORDER_STATUS_LABEL[wo.status]}</p>
</div>
<span className={`text-xs ${isLight ? 'text-slate-500' : 'text-slate-500'}`}>
{new Intl.DateTimeFormat('pt-PT', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(wo.updated_at))}