chore: expand reports coverage and upgrade next

This commit is contained in:
codex-bot 2025-10-31 17:27:51 -03:00
parent 2fb587b01d
commit 8b82284e8c
21 changed files with 2952 additions and 2713 deletions

View file

@ -102,6 +102,12 @@ type MachineTicketSummary = {
assignee: { name: string | null; email: string | null } | null
}
type MachineOpenTicketsSummary = {
totalOpen: number
hasMore: boolean
tickets: MachineTicketSummary[]
}
type DetailLineProps = {
label: string
@ -1454,9 +1460,14 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
const machineAlertsHistory = alertsHistory ?? []
const openTickets = useQuery(
machine ? api.machines.listOpenTickets : "skip",
machine ? { machineId: machine.id as Id<"machines">, limit: 8 } : ("skip" as const)
) as MachineTicketSummary[] | undefined
const machineTickets = openTickets ?? []
machine ? { machineId: machine.id as Id<"machines">, limit: 6 } : ("skip" as const)
) as MachineOpenTicketsSummary | undefined
const machineTickets = openTickets?.tickets ?? []
const totalOpenTickets = openTickets?.totalOpen ?? machineTickets.length
const displayLimit = 3
const displayedMachineTickets = machineTickets.slice(0, displayLimit)
const hasAdditionalOpenTickets = totalOpenTickets > displayedMachineTickets.length
const machineTicketsHref = machine ? `/admin/machines/${machine.id}/tickets` : null
const metadata = machine?.inventory ?? null
const metrics = machine?.metrics ?? null
const metricsCapturedAt = useMemo(() => getMetricsTimestamp(metrics), [metrics])
@ -2356,45 +2367,65 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
</div>
<div className="rounded-2xl border border-[color:var(--accent)] bg-[color:var(--accent)]/80 px-4 py-4">
<div className="grid gap-3 sm:grid-cols-[1fr_auto] sm:items-center">
<div className="space-y-1">
<h4 className="text-sm font-semibold text-accent-foreground">Tickets abertos por esta máquina</h4>
{machineTickets.length === 0 ? (
<p className="text-xs text-[color:var(--accent-foreground)]/80">Nenhum chamado em aberto registrado diretamente por esta máquina.</p>
<div className="space-y-2">
<div className="flex flex-wrap items-center justify-between gap-2">
<h4 className="text-sm font-semibold text-accent-foreground">Tickets abertos por esta máquina</h4>
{machineTicketsHref ? (
<Link
href={machineTicketsHref}
className="text-xs font-semibold text-accent-foreground underline-offset-4 transition hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent-foreground)] focus-visible:ring-offset-2"
>
Ver todos
</Link>
) : null}
</div>
{totalOpenTickets === 0 ? (
<p className="text-xs text-[color:var(--accent-foreground)]/80">
Nenhum chamado em aberto registrado diretamente por esta máquina.
</p>
) : (
<ul className="space-y-2">
{machineTickets.map((ticket) => {
const priorityMeta = getTicketPriorityMeta(ticket.priority)
return (
<li key={ticket.id}>
<Link
href={`/tickets/${ticket.id}`}
className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-[color:var(--accent)] bg-white px-3 py-2 text-sm shadow-sm transition hover:-translate-y-0.5 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2"
>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-neutral-900">
#{ticket.reference} · {ticket.subject}
</p>
<p className="text-xs text-neutral-500">
Atualizado {formatRelativeTime(new Date(ticket.updatedAt))}
</p>
</div>
<div className="flex items-center gap-2">
<Badge className={cn("rounded-full px-3 py-1 text-xs font-semibold", priorityMeta.badgeClass)}>
{priorityMeta.label}
</Badge>
<TicketStatusBadge status={ticket.status} className="h-7 px-3 text-xs font-semibold" />
</div>
</Link>
</li>
)
})}
</ul>
<div className="space-y-2">
{hasAdditionalOpenTickets ? (
<p className="text-[10px] font-semibold uppercase tracking-[0.14em] text-[color:var(--accent-foreground)]/70">
Mostrando últimos {Math.min(displayLimit, totalOpenTickets)} de {totalOpenTickets} chamados
em aberto
</p>
) : null}
<ul className="space-y-2">
{displayedMachineTickets.map((ticket) => {
const priorityMeta = getTicketPriorityMeta(ticket.priority)
return (
<li key={ticket.id}>
<Link
href={`/tickets/${ticket.id}`}
className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-[color:var(--accent)] bg-white px-3 py-2 text-sm shadow-sm transition hover:-translate-y-0.5 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)] focus-visible:ring-offset-2"
>
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-neutral-900">
#{ticket.reference} · {ticket.subject}
</p>
<p className="text-xs text-neutral-500">
Atualizado {formatRelativeTime(new Date(ticket.updatedAt))}
</p>
</div>
<div className="flex items-center gap-2">
<Badge className={cn("rounded-full px-3 py-1 text-xs font-semibold", priorityMeta.badgeClass)}>
{priorityMeta.label}
</Badge>
<TicketStatusBadge status={ticket.status} className="h-7 px-3 text-xs font-semibold" />
</div>
</Link>
</li>
)
})}
</ul>
</div>
)}
</div>
<div className="self-center justify-self-end">
<div className="flex h-12 min-w-[72px] items-center justify-center rounded-2xl border border-[color:var(--accent)] bg-white px-5 shadow-sm sm:min-w-[88px]">
<span className="text-2xl font-semibold leading-none text-accent-foreground tabular-nums sm:text-3xl">
{machineTickets.length}
{totalOpenTickets}
</span>
</div>
</div>