ui: ajustar tabelas de automações e sidebar

This commit is contained in:
esdrasrenan 2025-12-13 11:59:34 -03:00
parent e4d0c95791
commit 469608a10b
3 changed files with 117 additions and 86 deletions

View file

@ -83,10 +83,9 @@ const navigation: NavigationGroup[] = [
children: [ children: [
{ title: "Todos os tickets", url: "/tickets", icon: ClipboardList, requiredRole: "staff" }, { title: "Todos os tickets", url: "/tickets", icon: ClipboardList, requiredRole: "staff" },
{ title: "Resolvidos", url: "/tickets/resolved", icon: ShieldCheck, requiredRole: "staff" }, { title: "Resolvidos", url: "/tickets/resolved", icon: ShieldCheck, requiredRole: "staff" },
{ title: "Modo Play", url: "/play", icon: PlayCircle, requiredRole: "staff" },
], ],
}, },
{ title: "Automações", url: "/automations", icon: Waypoints, requiredRole: "agent" },
{ title: "Modo Play", url: "/play", icon: PlayCircle, requiredRole: "staff" },
{ title: "Agenda", url: "/agenda", icon: CalendarDays, requiredRole: "staff" }, { title: "Agenda", url: "/agenda", icon: CalendarDays, requiredRole: "staff" },
{ title: "Dispositivos", url: "/admin/devices", icon: MonitorCog, requiredRole: "admin" }, { title: "Dispositivos", url: "/admin/devices", icon: MonitorCog, requiredRole: "admin" },
{ title: "Empréstimos", url: "/emprestimos", icon: Package, requiredRole: "staff" }, { title: "Empréstimos", url: "/emprestimos", icon: Package, requiredRole: "staff" },
@ -108,7 +107,7 @@ const navigation: NavigationGroup[] = [
}, },
{ {
title: "Administração", title: "Administração",
requiredRole: "admin", requiredRole: "agent",
items: [ items: [
{ {
title: "Cadastros", title: "Cadastros",
@ -124,6 +123,7 @@ const navigation: NavigationGroup[] = [
{ title: "Templates de relatórios", url: "/admin/report-templates", icon: LayoutTemplate, requiredRole: "admin" }, { title: "Templates de relatórios", url: "/admin/report-templates", icon: LayoutTemplate, requiredRole: "admin" },
], ],
}, },
{ title: "Automações", url: "/automations", icon: Waypoints, requiredRole: "agent" },
{ {
title: "Orquestração", title: "Orquestração",
url: "/admin/channels", url: "/admin/channels",

View file

@ -37,7 +37,11 @@ const EVENT_LABELS: Record<string, string> = {
} }
function formatDateTime(timestamp: number) { function formatDateTime(timestamp: number) {
return new Date(timestamp).toLocaleString("pt-BR") const date = new Date(timestamp)
return {
date: date.toLocaleDateString("pt-BR"),
time: date.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit", second: "2-digit" }),
}
} }
function statusBadge(status: AutomationRunRow["status"]) { function statusBadge(status: AutomationRunRow["status"]) {
@ -85,7 +89,7 @@ export function AutomationRunsDialog({
const rows = (results ?? []) as unknown as AutomationRunRow[] const rows = (results ?? []) as unknown as AutomationRunRow[]
return ( return (
<DialogContent className="max-w-5xl"> <DialogContent className="max-w-6xl">
<DialogHeader className="gap-4 pb-2"> <DialogHeader className="gap-4 pb-2">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<div className="space-y-1"> <div className="space-y-1">
@ -116,15 +120,15 @@ export function AutomationRunsDialog({
</DialogHeader> </DialogHeader>
<div className="space-y-3"> <div className="space-y-3">
<div className="overflow-x-auto"> <div className="rounded-3xl border border-slate-200 bg-white/90 shadow-sm overflow-hidden">
<Table className="w-full" style={{ tableLayout: "fixed", minWidth: "980px" }}> <Table className="w-full table-fixed">
<colgroup> <colgroup>
<col style={{ width: "210px" }} /> <col style={{ width: "16%" }} />
<col style={{ width: "260px" }} /> <col style={{ width: "22%" }} />
<col style={{ width: "120px" }} /> <col style={{ width: "12%" }} />
<col style={{ width: "140px" }} /> <col style={{ width: "14%" }} />
<col style={{ width: "110px" }} /> <col style={{ width: "10%" }} />
<col style={{ width: "280px" }} /> <col style={{ width: "26%" }} />
</colgroup> </colgroup>
<TableHeader className="bg-slate-100/80"> <TableHeader className="bg-slate-100/80">
<TableRow className="bg-transparent text-[11px] uppercase tracking-wide text-neutral-600 hover:bg-transparent"> <TableRow className="bg-transparent text-[11px] uppercase tracking-wide text-neutral-600 hover:bg-transparent">
@ -171,6 +175,9 @@ export function AutomationRunsDialog({
const badge = statusBadge(run.status) const badge = statusBadge(run.status)
const eventLabel = EVENT_LABELS[run.eventType] ?? run.eventType const eventLabel = EVENT_LABELS[run.eventType] ?? run.eventType
const actionsCount = run.actionsApplied?.length ?? 0 const actionsCount = run.actionsApplied?.length ?? 0
const actionsLabel =
actionsCount === 1 ? "Aplicou 1 ação" : `Aplicou ${actionsCount} ações`
const createdAtLabel = formatDateTime(run.createdAt)
const details = const details =
run.status === "ERROR" run.status === "ERROR"
? run.error ?? "Erro desconhecido" ? run.error ?? "Erro desconhecido"
@ -179,12 +186,17 @@ export function AutomationRunsDialog({
? "Ignorada" ? "Ignorada"
: "Condições não atendidas" : "Condições não atendidas"
: actionsCount > 0 : actionsCount > 0
? `Aplicou ${actionsCount} ação(ões)` ? actionsLabel
: "Sem alterações" : "Sem alterações"
return ( return (
<TableRow key={run.id} className="transition-colors hover:bg-cyan-50/30"> <TableRow key={run.id} className="transition-colors hover:bg-cyan-50/30">
<TableCell className="text-center text-sm text-neutral-700">{formatDateTime(run.createdAt)}</TableCell> <TableCell className="text-center text-sm text-neutral-700">
<div className="space-y-0.5">
<div className="font-medium text-neutral-900">{createdAtLabel.date}</div>
<div className="text-xs text-neutral-600">{createdAtLabel.time}</div>
</div>
</TableCell>
<TableCell className="text-center text-sm text-neutral-700"> <TableCell className="text-center text-sm text-neutral-700">
{run.ticket ? ( {run.ticket ? (
<div className="space-y-0.5"> <div className="space-y-0.5">
@ -197,7 +209,11 @@ export function AutomationRunsDialog({
"—" "—"
)} )}
</TableCell> </TableCell>
<TableCell className="text-center text-sm text-neutral-700">{eventLabel}</TableCell> <TableCell className="text-center text-sm text-neutral-700">
<span className="truncate" title={eventLabel}>
{eventLabel}
</span>
</TableCell>
<TableCell className="text-center"> <TableCell className="text-center">
<Badge variant={badge.variant} className="rounded-full"> <Badge variant={badge.variant} className="rounded-full">
{badge.label} {badge.label}
@ -238,4 +254,3 @@ export function AutomationRunsDialog({
</DialogContent> </DialogContent>
) )
} }

View file

@ -51,8 +51,12 @@ function triggerLabel(trigger: string) {
} }
function formatLastRun(timestamp: number | null) { function formatLastRun(timestamp: number | null) {
if (!timestamp) return "—" if (!timestamp) return null
return new Date(timestamp).toLocaleString("pt-BR") const date = new Date(timestamp)
return {
date: date.toLocaleDateString("pt-BR"),
time: date.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit", second: "2-digit" }),
}
} }
function formatConditionsSummary(conditions: unknown | null) { function formatConditionsSummary(conditions: unknown | null) {
@ -229,17 +233,17 @@ export function AutomationsManager() {
Nenhuma automação cadastrada. Nenhuma automação cadastrada.
</p> </p>
) : ( ) : (
<div className="overflow-x-auto"> <div className="rounded-3xl border border-slate-200 bg-white/90 shadow-sm overflow-hidden">
<Table className="w-full" style={{ tableLayout: "fixed", minWidth: "980px" }}> <Table className="w-full table-fixed">
<colgroup> <colgroup>
<col style={{ width: "320px" }} /> <col style={{ width: "24%" }} />
<col style={{ width: "190px" }} /> <col style={{ width: "16%" }} />
<col style={{ width: "120px" }} /> <col style={{ width: "10%" }} />
<col style={{ width: "100px" }} /> <col style={{ width: "7%" }} />
<col style={{ width: "120px" }} /> <col style={{ width: "8%" }} />
<col style={{ width: "230px" }} /> <col style={{ width: "15%" }} />
<col style={{ width: "140px" }} /> <col style={{ width: "13%" }} />
<col style={{ width: "70px" }} /> <col style={{ width: "7%" }} />
</colgroup> </colgroup>
<TableHeader className="bg-slate-100/80"> <TableHeader className="bg-slate-100/80">
<TableRow className="bg-transparent text-[11px] uppercase tracking-wide text-neutral-600 hover:bg-transparent"> <TableRow className="bg-transparent text-[11px] uppercase tracking-wide text-neutral-600 hover:bg-transparent">
@ -270,64 +274,76 @@ export function AutomationsManager() {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{filtered.map((row) => ( {filtered.map((row) => {
<TableRow key={row.id} className="transition-colors hover:bg-cyan-50/30"> const lastRun = formatLastRun(row.lastRunAt)
<TableCell className="text-center font-semibold text-neutral-900 truncate" title={row.name}> return (
{row.name} <TableRow key={row.id} className="transition-colors hover:bg-cyan-50/30">
</TableCell> <TableCell className="text-center font-semibold text-neutral-900 truncate" title={row.name}>
<TableCell className="text-center"> {row.name}
<div className="flex items-center justify-center gap-2"> </TableCell>
<Badge variant="secondary" className="rounded-full"> <TableCell className="text-center">
{triggerLabel(row.trigger)} <div className="flex items-center justify-center gap-2">
</Badge> <Badge variant="secondary" className="rounded-full max-w-full truncate">
{row.timing === "DELAYED" && row.delayMs ? ( {triggerLabel(row.trigger)}
<Badge variant="outline" className="rounded-full">
+{Math.round(row.delayMs / 60000)}m
</Badge> </Badge>
) : null} {row.timing === "DELAYED" && row.delayMs ? (
</div> <Badge variant="outline" className="rounded-full">
</TableCell> +{Math.round(row.delayMs / 60000)}m
<TableCell className="text-center text-sm text-neutral-700"> </Badge>
{formatConditionsSummary(row.conditions)} ) : null}
</TableCell> </div>
<TableCell className="text-center text-sm text-neutral-700">{row.actions?.length ?? 0}</TableCell> </TableCell>
<TableCell className="text-center text-sm text-neutral-700">{row.runCount ?? 0}</TableCell> <TableCell className="text-center text-sm text-neutral-700">
<TableCell className="text-center text-sm text-neutral-700">{formatLastRun(row.lastRunAt)}</TableCell> {formatConditionsSummary(row.conditions)}
<TableCell className="text-center"> </TableCell>
<div className="flex items-center justify-center gap-2"> <TableCell className="text-center text-sm text-neutral-700">{row.actions?.length ?? 0}</TableCell>
<Switch <TableCell className="text-center text-sm text-neutral-700">{row.runCount ?? 0}</TableCell>
checked={row.enabled} <TableCell className="text-center text-sm text-neutral-700">
onCheckedChange={(checked) => handleToggle(row, checked)} {lastRun ? (
className="data-[state=checked]:bg-black data-[state=unchecked]:bg-slate-300" <div className="space-y-0.5">
/> <div className="font-medium text-neutral-900">{lastRun.date}</div>
<span className="text-xs font-medium text-neutral-700">{row.enabled ? "Ativa" : "Inativa"}</span> <div className="text-xs text-neutral-600">{lastRun.time}</div>
</div> </div>
</TableCell> ) : (
<TableCell className="text-center"> "—"
<DropdownMenu> )}
<DropdownMenuTrigger asChild> </TableCell>
<Button variant="ghost" size="icon" className="h-9 w-9 rounded-full"> <TableCell className="text-center">
<MoreHorizontal className="size-4" /> <div className="flex items-center justify-center gap-2">
</Button> <Switch
</DropdownMenuTrigger> checked={row.enabled}
<DropdownMenuContent align="end" className="rounded-xl"> onCheckedChange={(checked) => handleToggle(row, checked)}
<DropdownMenuItem onClick={() => handleOpenRuns(row)} className="gap-2"> className="data-[state=checked]:bg-black data-[state=unchecked]:bg-slate-300"
<History className="size-4" /> />
Histórico <span className="text-xs font-medium text-neutral-700">{row.enabled ? "Ativa" : "Inativa"}</span>
</DropdownMenuItem> </div>
<DropdownMenuItem onClick={() => handleEdit(row)} className="gap-2"> </TableCell>
<Pencil className="size-4" /> <TableCell className="text-center">
Editar <DropdownMenu>
</DropdownMenuItem> <DropdownMenuTrigger asChild>
<DropdownMenuItem variant="destructive" onClick={() => handleDelete(row)} className="gap-2"> <Button variant="ghost" size="icon" className="h-9 w-9 rounded-full">
<Trash2 className="size-4" /> <MoreHorizontal className="size-4" />
Excluir </Button>
</DropdownMenuItem> </DropdownMenuTrigger>
</DropdownMenuContent> <DropdownMenuContent align="end" className="rounded-xl">
</DropdownMenu> <DropdownMenuItem onClick={() => handleOpenRuns(row)} className="gap-2">
</TableCell> <History className="size-4" />
</TableRow> Histórico
))} </DropdownMenuItem>
<DropdownMenuItem onClick={() => handleEdit(row)} className="gap-2">
<Pencil className="size-4" />
Editar
</DropdownMenuItem>
<DropdownMenuItem variant="destructive" onClick={() => handleDelete(row)} className="gap-2">
<Trash2 className="size-4" />
Excluir
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
)
})}
</TableBody> </TableBody>
</Table> </Table>
</div> </div>