ui: ajustar tabelas de automações e sidebar
This commit is contained in:
parent
e4d0c95791
commit
469608a10b
3 changed files with 117 additions and 86 deletions
|
|
@ -83,10 +83,9 @@ const navigation: NavigationGroup[] = [
|
|||
children: [
|
||||
{ title: "Todos os tickets", url: "/tickets", icon: ClipboardList, 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: "Dispositivos", url: "/admin/devices", icon: MonitorCog, requiredRole: "admin" },
|
||||
{ title: "Empréstimos", url: "/emprestimos", icon: Package, requiredRole: "staff" },
|
||||
|
|
@ -108,7 +107,7 @@ const navigation: NavigationGroup[] = [
|
|||
},
|
||||
{
|
||||
title: "Administração",
|
||||
requiredRole: "admin",
|
||||
requiredRole: "agent",
|
||||
items: [
|
||||
{
|
||||
title: "Cadastros",
|
||||
|
|
@ -124,6 +123,7 @@ const navigation: NavigationGroup[] = [
|
|||
{ 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",
|
||||
url: "/admin/channels",
|
||||
|
|
|
|||
|
|
@ -37,7 +37,11 @@ const EVENT_LABELS: Record<string, string> = {
|
|||
}
|
||||
|
||||
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"]) {
|
||||
|
|
@ -85,7 +89,7 @@ export function AutomationRunsDialog({
|
|||
const rows = (results ?? []) as unknown as AutomationRunRow[]
|
||||
|
||||
return (
|
||||
<DialogContent className="max-w-5xl">
|
||||
<DialogContent className="max-w-6xl">
|
||||
<DialogHeader className="gap-4 pb-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="space-y-1">
|
||||
|
|
@ -116,15 +120,15 @@ export function AutomationRunsDialog({
|
|||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="overflow-x-auto">
|
||||
<Table className="w-full" style={{ tableLayout: "fixed", minWidth: "980px" }}>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white/90 shadow-sm overflow-hidden">
|
||||
<Table className="w-full table-fixed">
|
||||
<colgroup>
|
||||
<col style={{ width: "210px" }} />
|
||||
<col style={{ width: "260px" }} />
|
||||
<col style={{ width: "120px" }} />
|
||||
<col style={{ width: "140px" }} />
|
||||
<col style={{ width: "110px" }} />
|
||||
<col style={{ width: "280px" }} />
|
||||
<col style={{ width: "16%" }} />
|
||||
<col style={{ width: "22%" }} />
|
||||
<col style={{ width: "12%" }} />
|
||||
<col style={{ width: "14%" }} />
|
||||
<col style={{ width: "10%" }} />
|
||||
<col style={{ width: "26%" }} />
|
||||
</colgroup>
|
||||
<TableHeader className="bg-slate-100/80">
|
||||
<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 eventLabel = EVENT_LABELS[run.eventType] ?? run.eventType
|
||||
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 =
|
||||
run.status === "ERROR"
|
||||
? run.error ?? "Erro desconhecido"
|
||||
|
|
@ -179,12 +186,17 @@ export function AutomationRunsDialog({
|
|||
? "Ignorada"
|
||||
: "Condições não atendidas"
|
||||
: actionsCount > 0
|
||||
? `Aplicou ${actionsCount} ação(ões)`
|
||||
? actionsLabel
|
||||
: "Sem alterações"
|
||||
|
||||
return (
|
||||
<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">
|
||||
{run.ticket ? (
|
||||
<div className="space-y-0.5">
|
||||
|
|
@ -197,7 +209,11 @@ export function AutomationRunsDialog({
|
|||
"—"
|
||||
)}
|
||||
</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">
|
||||
<Badge variant={badge.variant} className="rounded-full">
|
||||
{badge.label}
|
||||
|
|
@ -238,4 +254,3 @@ export function AutomationRunsDialog({
|
|||
</DialogContent>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,12 @@ function triggerLabel(trigger: string) {
|
|||
}
|
||||
|
||||
function formatLastRun(timestamp: number | null) {
|
||||
if (!timestamp) return "—"
|
||||
return new Date(timestamp).toLocaleString("pt-BR")
|
||||
if (!timestamp) return null
|
||||
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) {
|
||||
|
|
@ -229,17 +233,17 @@ export function AutomationsManager() {
|
|||
Nenhuma automação cadastrada.
|
||||
</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table className="w-full" style={{ tableLayout: "fixed", minWidth: "980px" }}>
|
||||
<div className="rounded-3xl border border-slate-200 bg-white/90 shadow-sm overflow-hidden">
|
||||
<Table className="w-full table-fixed">
|
||||
<colgroup>
|
||||
<col style={{ width: "320px" }} />
|
||||
<col style={{ width: "190px" }} />
|
||||
<col style={{ width: "120px" }} />
|
||||
<col style={{ width: "100px" }} />
|
||||
<col style={{ width: "120px" }} />
|
||||
<col style={{ width: "230px" }} />
|
||||
<col style={{ width: "140px" }} />
|
||||
<col style={{ width: "70px" }} />
|
||||
<col style={{ width: "24%" }} />
|
||||
<col style={{ width: "16%" }} />
|
||||
<col style={{ width: "10%" }} />
|
||||
<col style={{ width: "7%" }} />
|
||||
<col style={{ width: "8%" }} />
|
||||
<col style={{ width: "15%" }} />
|
||||
<col style={{ width: "13%" }} />
|
||||
<col style={{ width: "7%" }} />
|
||||
</colgroup>
|
||||
<TableHeader className="bg-slate-100/80">
|
||||
<TableRow className="bg-transparent text-[11px] uppercase tracking-wide text-neutral-600 hover:bg-transparent">
|
||||
|
|
@ -270,64 +274,76 @@ export function AutomationsManager() {
|
|||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.map((row) => (
|
||||
<TableRow key={row.id} className="transition-colors hover:bg-cyan-50/30">
|
||||
<TableCell className="text-center font-semibold text-neutral-900 truncate" title={row.name}>
|
||||
{row.name}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Badge variant="secondary" className="rounded-full">
|
||||
{triggerLabel(row.trigger)}
|
||||
</Badge>
|
||||
{row.timing === "DELAYED" && row.delayMs ? (
|
||||
<Badge variant="outline" className="rounded-full">
|
||||
+{Math.round(row.delayMs / 60000)}m
|
||||
{filtered.map((row) => {
|
||||
const lastRun = formatLastRun(row.lastRunAt)
|
||||
return (
|
||||
<TableRow key={row.id} className="transition-colors hover:bg-cyan-50/30">
|
||||
<TableCell className="text-center font-semibold text-neutral-900 truncate" title={row.name}>
|
||||
{row.name}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Badge variant="secondary" className="rounded-full max-w-full truncate">
|
||||
{triggerLabel(row.trigger)}
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">
|
||||
{formatConditionsSummary(row.conditions)}
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">{row.actions?.length ?? 0}</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">{row.runCount ?? 0}</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">{formatLastRun(row.lastRunAt)}</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Switch
|
||||
checked={row.enabled}
|
||||
onCheckedChange={(checked) => handleToggle(row, checked)}
|
||||
className="data-[state=checked]:bg-black data-[state=unchecked]:bg-slate-300"
|
||||
/>
|
||||
<span className="text-xs font-medium text-neutral-700">{row.enabled ? "Ativa" : "Inativa"}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-9 w-9 rounded-full">
|
||||
<MoreHorizontal className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="rounded-xl">
|
||||
<DropdownMenuItem onClick={() => handleOpenRuns(row)} className="gap-2">
|
||||
<History className="size-4" />
|
||||
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>
|
||||
))}
|
||||
{row.timing === "DELAYED" && row.delayMs ? (
|
||||
<Badge variant="outline" className="rounded-full">
|
||||
+{Math.round(row.delayMs / 60000)}m
|
||||
</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">
|
||||
{formatConditionsSummary(row.conditions)}
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">{row.actions?.length ?? 0}</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">{row.runCount ?? 0}</TableCell>
|
||||
<TableCell className="text-center text-sm text-neutral-700">
|
||||
{lastRun ? (
|
||||
<div className="space-y-0.5">
|
||||
<div className="font-medium text-neutral-900">{lastRun.date}</div>
|
||||
<div className="text-xs text-neutral-600">{lastRun.time}</div>
|
||||
</div>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Switch
|
||||
checked={row.enabled}
|
||||
onCheckedChange={(checked) => handleToggle(row, checked)}
|
||||
className="data-[state=checked]:bg-black data-[state=unchecked]:bg-slate-300"
|
||||
/>
|
||||
<span className="text-xs font-medium text-neutral-700">{row.enabled ? "Ativa" : "Inativa"}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-9 w-9 rounded-full">
|
||||
<MoreHorizontal className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="rounded-xl">
|
||||
<DropdownMenuItem onClick={() => handleOpenRuns(row)} className="gap-2">
|
||||
<History className="size-4" />
|
||||
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>
|
||||
</Table>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue