feat: refine OT flow, reports, theming and sidebar UX
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
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'
|
||||
|
||||
const STATUS_LABEL: Record<WorkOrder['status'], string> = {
|
||||
quote: 'Orçamento',
|
||||
open: 'Aberta',
|
||||
open: 'Orçamento Aprovado',
|
||||
in_progress: 'Em Curso',
|
||||
completed: 'Concluída',
|
||||
invoiced: 'Faturada',
|
||||
@@ -54,23 +55,27 @@ function DashboardCard({
|
||||
title,
|
||||
value,
|
||||
subtitle,
|
||||
isLight,
|
||||
}: {
|
||||
title: string
|
||||
value: string
|
||||
subtitle: string
|
||||
isLight: boolean
|
||||
}) {
|
||||
return (
|
||||
<article className="rounded-2xl border border-slate-700/70 bg-slate-900/65 p-4 transition-colors hover:border-slate-500/80">
|
||||
<p className="text-[11px] uppercase tracking-[0.14em] text-slate-400">{title}</p>
|
||||
<p className="mt-2 text-2xl font-semibold text-white [font-family:'Space_Grotesk',ui-sans-serif,sans-serif]">
|
||||
<article className={`rounded-2xl border p-4 transition-colors ${isLight ? 'border-slate-300 bg-white hover:border-slate-400' : 'border-slate-700/70 bg-slate-900/65 hover:border-slate-500/80'}`}>
|
||||
<p className={`text-[11px] uppercase tracking-[0.14em] ${isLight ? 'text-slate-500' : 'text-slate-400'}`}>{title}</p>
|
||||
<p className={`mt-2 text-2xl font-semibold [font-family:'Space_Grotesk',ui-sans-serif,sans-serif] ${isLight ? 'text-slate-900' : 'text-white'}`}>
|
||||
{value}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-slate-400">{subtitle}</p>
|
||||
<p className={`mt-1 text-xs ${isLight ? 'text-slate-600' : 'text-slate-400'}`}>{subtitle}</p>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { theme } = useTheme('ui_theme', 'dark')
|
||||
const isLight = theme === 'light'
|
||||
const [period, setPeriod] = useState<PeriodKey>('month')
|
||||
|
||||
const workOrdersQ = useQuery<WorkOrder[]>({
|
||||
@@ -160,8 +165,8 @@ export default function DashboardPage() {
|
||||
.sort((a, b) => +new Date(b.updated_at) - +new Date(a.updated_at))
|
||||
.slice(0, 6)
|
||||
|
||||
const recentDocs = [...invoicesPeriod]
|
||||
.sort((a, b) => +new Date(b.issued_at) - +new Date(a.issued_at))
|
||||
const recentExpenses = [...expensesPeriod]
|
||||
.sort((a, b) => +new Date(b.date) - +new Date(a.date))
|
||||
.slice(0, 6)
|
||||
|
||||
return {
|
||||
@@ -174,7 +179,7 @@ export default function DashboardPage() {
|
||||
monthDocs,
|
||||
activeStaff,
|
||||
recentOrders,
|
||||
recentDocs,
|
||||
recentExpenses,
|
||||
periodOrdersCount: workOrdersPeriod.length,
|
||||
periodDocsCount: invoicesPeriod.length,
|
||||
}
|
||||
@@ -186,14 +191,14 @@ export default function DashboardPage() {
|
||||
|
||||
return (
|
||||
<section className="space-y-6 [font-family:'Sora',ui-sans-serif,sans-serif]">
|
||||
<header className="relative overflow-hidden rounded-2xl border border-slate-700/60 bg-gradient-to-br from-slate-900 via-slate-900 to-sky-950/35 p-6">
|
||||
<header className={`relative overflow-hidden rounded-2xl border p-6 ${isLight ? 'border-slate-300 bg-gradient-to-br from-white via-slate-50 to-sky-50' : 'border-slate-700/60 bg-gradient-to-br from-slate-900 via-slate-900 to-sky-950/35'}`}>
|
||||
<div className="absolute -right-16 -top-16 h-44 w-44 rounded-full bg-cyan-600/10 blur-2xl" />
|
||||
<div className="absolute -left-12 bottom-0 h-32 w-32 rounded-full bg-amber-500/10 blur-2xl" />
|
||||
<p className="text-xs uppercase tracking-[0.2em] text-slate-400">Painel Operacional</p>
|
||||
<h1 className="mt-1 text-3xl font-semibold text-white [font-family:'Space_Grotesk',ui-sans-serif,sans-serif]">
|
||||
<p className={`text-xs uppercase tracking-[0.2em] ${isLight ? 'text-slate-500' : 'text-slate-400'}`}>Painel Operacional</p>
|
||||
<h1 className={`mt-1 text-3xl font-semibold [font-family:'Space_Grotesk',ui-sans-serif,sans-serif] ${isLight ? 'text-slate-900' : 'text-white'}`}>
|
||||
Dashboard da Oficina
|
||||
</h1>
|
||||
<p className="mt-2 max-w-2xl text-sm text-slate-300">
|
||||
<p className={`mt-2 max-w-2xl text-sm ${isLight ? 'text-slate-600' : 'text-slate-300'}`}>
|
||||
Visão rápida do trabalho em curso, faturação e despesas para apoiar as decisões do dia.
|
||||
</p>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
@@ -203,8 +208,12 @@ export default function DashboardPage() {
|
||||
onClick={() => setPeriod(opt.key)}
|
||||
className={`rounded-md border px-3 py-1.5 text-xs transition-colors ${
|
||||
period === opt.key
|
||||
? 'border-cyan-400/80 bg-cyan-500/20 text-cyan-100'
|
||||
: 'border-slate-700 bg-slate-900/80 text-slate-300 hover:border-slate-500 hover:text-white'
|
||||
? isLight
|
||||
? 'border-sky-300 bg-sky-100 text-sky-900'
|
||||
: 'border-cyan-400/80 bg-cyan-500/20 text-cyan-100'
|
||||
: isLight
|
||||
? 'border-slate-300 bg-white text-slate-700 hover:border-slate-400 hover:text-slate-900'
|
||||
: 'border-slate-700 bg-slate-900/80 text-slate-300 hover:border-slate-500 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{opt.label}
|
||||
@@ -214,7 +223,7 @@ export default function DashboardPage() {
|
||||
</header>
|
||||
|
||||
{isLoading && (
|
||||
<p className="rounded-xl border border-slate-700 bg-slate-900/60 px-4 py-3 text-sm text-slate-300">
|
||||
<p className={`rounded-xl border px-4 py-3 text-sm ${isLight ? 'border-slate-300 bg-white text-slate-600' : 'border-slate-700 bg-slate-900/60 text-slate-300'}`}>
|
||||
A carregar métricas do dashboard...
|
||||
</p>
|
||||
)}
|
||||
@@ -230,78 +239,82 @@ export default function DashboardPage() {
|
||||
title="OTs Ativas"
|
||||
value={String(metrics.activeOrders)}
|
||||
subtitle={`No período (${metrics.periodOrdersCount} OTs)`}
|
||||
isLight={isLight}
|
||||
/>
|
||||
<DashboardCard
|
||||
title="Clientes"
|
||||
value={String(clients.length)}
|
||||
subtitle="Base total de clientes"
|
||||
isLight={isLight}
|
||||
/>
|
||||
<DashboardCard
|
||||
title="Técnicos Ativos"
|
||||
value={`${metrics.activeStaff}/${staff.length}`}
|
||||
subtitle="Recursos disponíveis"
|
||||
isLight={isLight}
|
||||
/>
|
||||
<DashboardCard
|
||||
title="Despesas do Período"
|
||||
value={currency(metrics.periodExpenses)}
|
||||
subtitle="Somatório no filtro selecionado"
|
||||
isLight={isLight}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<article className="rounded-2xl border border-slate-700/70 bg-slate-900/65 p-5">
|
||||
<article className={`rounded-2xl border p-5 ${isLight ? 'border-slate-300 bg-white' : 'border-slate-700/70 bg-slate-900/65'}`}>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-base font-semibold text-white">Entradas Recebidas vs Despesas</h2>
|
||||
<span className="text-xs text-slate-400">
|
||||
<h2 className={`text-base font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>Entradas Recebidas vs Despesas</h2>
|
||||
<span className={`text-xs ${isLight ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
{PERIOD_OPTIONS.find((p) => p.key === period)?.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="rounded-lg border border-emerald-800/60 bg-emerald-950/20 p-3">
|
||||
<p className="text-xs uppercase tracking-wide text-emerald-300">Entradas (faturas)</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-white">{currency(metrics.periodEntries)}</p>
|
||||
<div className={`rounded-lg border p-3 ${isLight ? 'border-emerald-200 bg-emerald-50' : 'border-emerald-800/60 bg-emerald-950/20'}`}>
|
||||
<p className={`text-xs uppercase tracking-wide ${isLight ? 'text-emerald-700' : 'text-emerald-300'}`}>Entradas (faturas)</p>
|
||||
<p className={`mt-1 text-2xl font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>{currency(metrics.periodEntries)}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-rose-800/60 bg-rose-950/20 p-3">
|
||||
<p className="text-xs uppercase tracking-wide text-rose-300">Despesas</p>
|
||||
<p className="mt-1 text-2xl font-semibold text-white">{currency(metrics.periodExpenses)}</p>
|
||||
<div className={`rounded-lg border p-3 ${isLight ? 'border-rose-200 bg-rose-50' : 'border-rose-800/60 bg-rose-950/20'}`}>
|
||||
<p className={`text-xs uppercase tracking-wide ${isLight ? 'text-rose-700' : 'text-rose-300'}`}>Despesas</p>
|
||||
<p className={`mt-1 text-2xl font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>{currency(metrics.periodExpenses)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-3">
|
||||
<div>
|
||||
<div className="mb-1 flex justify-between text-xs">
|
||||
<span className="text-slate-300">Entradas recebidas</span>
|
||||
<span className="font-mono text-emerald-300">{currency(metrics.periodEntries)}</span>
|
||||
<span className={isLight ? 'text-slate-700' : 'text-slate-300'}>Entradas recebidas</span>
|
||||
<span className={`font-mono ${isLight ? 'text-emerald-700' : 'text-emerald-300'}`}>{currency(metrics.periodEntries)}</span>
|
||||
</div>
|
||||
<div className="h-3 rounded-full bg-slate-800">
|
||||
<div className={`h-3 rounded-full ${isLight ? 'bg-slate-200' : 'bg-slate-800'}`}>
|
||||
<div className="h-3 rounded-full bg-gradient-to-r from-emerald-500 to-teal-400" style={{ width: `${entriesBarWidth}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-1 flex justify-between text-xs">
|
||||
<span className="text-slate-300">Despesas registadas</span>
|
||||
<span className="font-mono text-rose-300">{currency(metrics.periodExpenses)}</span>
|
||||
<span className={isLight ? 'text-slate-700' : 'text-slate-300'}>Despesas registadas</span>
|
||||
<span className={`font-mono ${isLight ? 'text-rose-700' : 'text-rose-300'}`}>{currency(metrics.periodExpenses)}</span>
|
||||
</div>
|
||||
<div className="h-3 rounded-full bg-slate-800">
|
||||
<div className={`h-3 rounded-full ${isLight ? 'bg-slate-200' : 'bg-slate-800'}`}>
|
||||
<div className="h-3 rounded-full bg-gradient-to-r from-rose-500 to-orange-400" style={{ width: `${expensesBarWidth}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-sm text-slate-300">
|
||||
Saldo estimado: <span className={metrics.periodEntries - metrics.periodExpenses >= 0 ? 'text-emerald-300' : 'text-rose-300'}>
|
||||
<p className={`mt-4 text-sm ${isLight ? 'text-slate-700' : 'text-slate-300'}`}>
|
||||
Saldo estimado: <span className={metrics.periodEntries - metrics.periodExpenses >= 0 ? (isLight ? 'text-emerald-700' : 'text-emerald-300') : (isLight ? 'text-rose-700' : 'text-rose-300')}>
|
||||
{currency(metrics.periodEntries - metrics.periodExpenses)}
|
||||
</span>
|
||||
</p>
|
||||
{(entriesLoading || entriesError) && (
|
||||
<p className="mt-2 text-xs text-slate-400">
|
||||
<p className={`mt-2 text-xs ${isLight ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
{entriesLoading ? 'A calcular totais de entradas...' : 'Algumas entradas não puderam ser calculadas.'}
|
||||
</p>
|
||||
)}
|
||||
</article>
|
||||
|
||||
<div className="grid items-stretch gap-4 xl:grid-cols-2">
|
||||
<article className="h-full rounded-2xl border border-slate-700/70 bg-slate-900/65 p-5">
|
||||
<article className={`h-full rounded-2xl border p-5 ${isLight ? 'border-slate-300 bg-white' : 'border-slate-700/70 bg-slate-900/65'}`}>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-base font-semibold text-white">Fluxo das Ordens de Trabalho</h2>
|
||||
<Link to="/app/work-orders" className="text-xs text-cyan-300 hover:text-cyan-200">
|
||||
<h2 className={`text-base font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>Fluxo das Ordens de Trabalho</h2>
|
||||
<Link to="/app/work-orders" className={`text-xs ${isLight ? 'text-sky-700 hover:text-sky-800' : 'text-cyan-300 hover:text-cyan-200'}`}>
|
||||
Ver todas
|
||||
</Link>
|
||||
</div>
|
||||
@@ -313,10 +326,10 @@ export default function DashboardPage() {
|
||||
return (
|
||||
<div key={status}>
|
||||
<div className="mb-1 flex justify-between text-xs">
|
||||
<span className="text-slate-300">{STATUS_LABEL[status]}</span>
|
||||
<span className="font-mono text-slate-400">{count}</span>
|
||||
<span className={isLight ? 'text-slate-700' : 'text-slate-300'}>{STATUS_LABEL[status]}</span>
|
||||
<span className={`font-mono ${isLight ? 'text-slate-500' : 'text-slate-400'}`}>{count}</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-slate-800">
|
||||
<div className={`h-2 rounded-full ${isLight ? 'bg-slate-200' : 'bg-slate-800'}`}>
|
||||
<div className="h-2 rounded-full bg-gradient-to-r from-cyan-500 to-emerald-500" style={{ width: `${width}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -325,14 +338,14 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article className="h-full rounded-2xl border border-slate-700/70 bg-slate-900/65 p-5">
|
||||
<h2 className="text-base font-semibold text-white">Faturação e Prioridades</h2>
|
||||
<article className={`h-full rounded-2xl border p-5 ${isLight ? 'border-slate-300 bg-white' : 'border-slate-700/70 bg-slate-900/65'}`}>
|
||||
<h2 className={`text-base font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>Faturação e Prioridades</h2>
|
||||
<div className="mt-4 space-y-3 text-sm">
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-900/70 px-3 py-2">
|
||||
<p className="text-slate-400">Documentos emitidos no período</p>
|
||||
<p className="text-xl font-semibold text-white">{metrics.periodDocsCount}</p>
|
||||
<div className={`rounded-lg border px-3 py-2 ${isLight ? 'border-slate-200 bg-slate-100' : 'border-slate-700 bg-slate-900/70'}`}>
|
||||
<p className={isLight ? 'text-slate-600' : 'text-slate-400'}>Documentos emitidos no período</p>
|
||||
<p className={`text-xl font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>{metrics.periodDocsCount}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-amber-800/70 bg-amber-950/30 px-3 py-2 text-amber-100">
|
||||
<div className={`rounded-lg border px-3 py-2 ${isLight ? 'border-amber-200 bg-amber-50 text-amber-900' : 'border-amber-800/70 bg-amber-950/30 text-amber-100'}`}>
|
||||
<p className="text-xs uppercase tracking-wide">Ação recomendada</p>
|
||||
<p className="mt-1">
|
||||
{metrics.byStatus.completed > 0
|
||||
@@ -340,7 +353,7 @@ export default function DashboardPage() {
|
||||
: 'Sem OTs concluídas pendentes de faturação.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-cyan-900/70 bg-cyan-950/25 px-3 py-2 text-cyan-100">
|
||||
<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
|
||||
@@ -348,9 +361,9 @@ export default function DashboardPage() {
|
||||
: 'Não existem OTs pendentes em orçamento.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-900/70 px-3 py-2">
|
||||
<p className="text-slate-400">Contexto mensal</p>
|
||||
<p className="text-slate-200 mt-1 text-xs">
|
||||
<div className={`rounded-lg border px-3 py-2 ${isLight ? 'border-slate-200 bg-slate-100' : 'border-slate-700 bg-slate-900/70'}`}>
|
||||
<p className={isLight ? 'text-slate-600' : 'text-slate-400'}>Contexto mensal</p>
|
||||
<p className={`mt-1 text-xs ${isLight ? 'text-slate-700' : 'text-slate-200'}`}>
|
||||
Entradas no mês: {currency(metrics.monthEntries)} | Despesas no mês: {currency(metrics.monthExpenses)} | Documentos: {metrics.monthDocs}
|
||||
</p>
|
||||
</div>
|
||||
@@ -359,24 +372,24 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 xl:grid-cols-2">
|
||||
<article className="rounded-2xl border border-slate-700/70 bg-slate-900/65 p-5">
|
||||
<article className={`rounded-2xl border p-5 ${isLight ? 'border-slate-300 bg-white' : 'border-slate-700/70 bg-slate-900/65'}`}>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h2 className="text-base font-semibold text-white">Atividade Recente de OTs</h2>
|
||||
<Link to="/app/work-orders" className="text-xs text-cyan-300 hover:text-cyan-200">
|
||||
<h2 className={`text-base font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>Atividade Recente de OTs</h2>
|
||||
<Link to="/app/work-orders" className={`text-xs ${isLight ? 'text-sky-700 hover:text-sky-800' : 'text-cyan-300 hover:text-cyan-200'}`}>
|
||||
Abrir OTs
|
||||
</Link>
|
||||
</div>
|
||||
{metrics.recentOrders.length === 0 ? (
|
||||
<p className="text-sm text-slate-400">Ainda não existem ordens de trabalho.</p>
|
||||
<p className={`text-sm ${isLight ? 'text-slate-600' : 'text-slate-400'}`}>Ainda não existem ordens de trabalho.</p>
|
||||
) : (
|
||||
<ul className="divide-y divide-slate-800">
|
||||
<ul className={`divide-y ${isLight ? 'divide-slate-200' : 'divide-slate-800'}`}>
|
||||
{metrics.recentOrders.map((wo) => (
|
||||
<li key={wo.id} className="flex items-center justify-between py-2">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">OT #{wo.number}</p>
|
||||
<p className="text-xs text-slate-400">{STATUS_LABEL[wo.status]}</p>
|
||||
<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>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">
|
||||
<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))}
|
||||
</span>
|
||||
</li>
|
||||
@@ -385,27 +398,30 @@ export default function DashboardPage() {
|
||||
)}
|
||||
</article>
|
||||
|
||||
<article className="rounded-2xl border border-slate-700/70 bg-slate-900/65 p-5">
|
||||
<article className={`rounded-2xl border p-5 ${isLight ? 'border-slate-300 bg-white' : 'border-slate-700/70 bg-slate-900/65'}`}>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h2 className="text-base font-semibold text-white">Documentos Recentes</h2>
|
||||
<Link to="/app/invoices" className="text-xs text-cyan-300 hover:text-cyan-200">
|
||||
Abrir Faturação
|
||||
<h2 className={`text-base font-semibold ${isLight ? 'text-slate-900' : 'text-white'}`}>Despesas Recentes</h2>
|
||||
<Link to="/app/expenses" className={`text-xs ${isLight ? 'text-sky-700 hover:text-sky-800' : 'text-cyan-300 hover:text-cyan-200'}`}>
|
||||
Abrir Despesas
|
||||
</Link>
|
||||
</div>
|
||||
{metrics.recentDocs.length === 0 ? (
|
||||
<p className="text-sm text-slate-400">Sem documentos emitidos.</p>
|
||||
{metrics.recentExpenses.length === 0 ? (
|
||||
<p className={`text-sm ${isLight ? 'text-slate-600' : 'text-slate-400'}`}>Sem despesas no período selecionado.</p>
|
||||
) : (
|
||||
<ul className="divide-y divide-slate-800">
|
||||
{metrics.recentDocs.map((doc) => (
|
||||
<li key={doc.id} className="flex items-center justify-between py-2">
|
||||
<ul className={`divide-y ${isLight ? 'divide-slate-200' : 'divide-slate-800'}`}>
|
||||
{metrics.recentExpenses.map((exp) => (
|
||||
<li key={exp.id} className="flex items-center justify-between py-2">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-white">
|
||||
{doc.type === 'invoice' ? 'Fatura' : 'Orçamento'} #{doc.number}
|
||||
<p className={`text-sm font-medium ${isLight ? 'text-slate-900' : 'text-white'}`}>
|
||||
{currency(exp.amount)}
|
||||
</p>
|
||||
<p className={`text-xs ${isLight ? 'text-slate-600' : 'text-slate-400'}`}>
|
||||
{exp.type === 'fuel' ? 'Combustível' : exp.type === 'parts' ? 'Peças' : exp.type === 'tools' ? 'Ferramentas' : 'Outros'}
|
||||
{exp.description ? ` · ${exp.description}` : ''}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 font-mono">OT {doc.work_order_id.slice(0, 8)}...</p>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">
|
||||
{new Intl.DateTimeFormat('pt-PT', { dateStyle: 'short' }).format(new Date(doc.issued_at))}
|
||||
<span className={`text-xs ${isLight ? 'text-slate-500' : 'text-slate-500'}`}>
|
||||
{new Intl.DateTimeFormat('pt-PT', { dateStyle: 'short' }).format(new Date(exp.date))}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user