ui: melhorias de UX em várias telas

- Truncate com ellipsis na coluna Empresa (tickets-table)
- Botão excluir em templates de checklist + mutation remove no backend
- Botões Editar/Arquivar com size="sm" em checklist templates
- Hover com borda no botão "Tornar opcional" do checklist
- Botão Resetar em devices com estilo padrão (remove amarelo)
- Botão "Encerrar" no modo apresentação do dashboard
- Sidebar abre automaticamente ao sair do fullscreen

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-13 22:42:37 -03:00
parent 06388b3688
commit 245d5dc15b
6 changed files with 91 additions and 28 deletions

View file

@ -4000,7 +4000,7 @@ export function DeviceDetails({ device }: DeviceDetailsProps) {
<Button
size="sm"
variant="outline"
className="gap-2 border-dashed border-amber-300 text-amber-700 hover:border-amber-400 hover:bg-amber-100/60 hover:text-amber-900"
className="gap-2 border-dashed"
onClick={handleResetAgent}
disabled={isResettingAgent}
>

View file

@ -93,6 +93,7 @@ import {
Sparkles,
Table2,
Trash2,
X,
} from "lucide-react"
import { IconPencil } from "@tabler/icons-react"
import { getMetricDefinition, getMetricOptionsForRole } from "@/components/dashboards/metric-catalog"
@ -702,16 +703,17 @@ export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }
const handleFullscreenChange = () => {
const currentlyFullscreen = Boolean(document.fullscreenElement)
setIsFullscreen(currentlyFullscreen)
if (!currentlyFullscreen && previousSidebarStateRef.current) {
const previous = previousSidebarStateRef.current
setOpen(previous.open)
setOpenMobile(previous.openMobile)
if (!currentlyFullscreen) {
// Ao sair do fullscreen, sempre abre o sidebar em desktop
if (!isMobile) {
setOpen(true)
}
previousSidebarStateRef.current = null
}
}
document.addEventListener("fullscreenchange", handleFullscreenChange)
return () => document.removeEventListener("fullscreenchange", handleFullscreenChange)
}, [setOpen, setOpenMobile])
}, [setOpen, isMobile])
const handleToggleFullscreen = useCallback(
async (options?: { requestedByUser?: boolean }) => {
@ -1265,6 +1267,7 @@ export function DashboardBuilder({ dashboardId, editable = true, mode = "edit" }
activeIndex={activeSectionIndex}
onChange={setActiveSectionIndex}
fullscreen={isFullscreen}
onExitPresentation={() => handleToggleFullscreen({ requestedByUser: true })}
/>
) : null}
@ -2342,11 +2345,13 @@ function TvSectionIndicator({
activeIndex,
onChange,
fullscreen = false,
onExitPresentation,
}: {
sections: DashboardSection[]
activeIndex: number
onChange: (index: number) => void
fullscreen?: boolean
onExitPresentation?: () => void
}) {
if (sections.length === 0) return null
return (
@ -2363,23 +2368,34 @@ function TvSectionIndicator({
Seção {activeIndex + 1} de {sections.length}: {sections[activeIndex]?.title ?? "Sem título"}
</span>
</div>
<div className="flex items-center gap-1">
{sections.map((section, index) => (
<div className="flex items-center gap-3">
<div className="flex items-center gap-1">
{sections.map((section, index) => (
<button
key={section.id}
onClick={() => onChange(index)}
className={cn(
"h-2.5 w-2.5 rounded-full transition",
fullscreen
? index === activeIndex
? "bg-white"
: "bg-white/40 hover:bg-white/70"
: index === activeIndex
? "bg-sidebar-accent-foreground"
: "bg-sidebar-accent-foreground/40 hover:bg-sidebar-accent-foreground/70",
)}
/>
))}
</div>
{fullscreen && onExitPresentation ? (
<button
key={section.id}
onClick={() => onChange(index)}
className={cn(
"h-2.5 w-2.5 rounded-full transition",
fullscreen
? index === activeIndex
? "bg-white"
: "bg-white/40 hover:bg-white/70"
: index === activeIndex
? "bg-sidebar-accent-foreground"
: "bg-sidebar-accent-foreground/40 hover:bg-sidebar-accent-foreground/70",
)}
/>
))}
onClick={onExitPresentation}
className="flex items-center gap-1.5 rounded-md bg-white/10 px-2.5 py-1 text-xs font-medium text-white/90 transition hover:bg-white/20 hover:text-white"
>
<X className="size-3.5" />
Encerrar
</button>
) : null}
</div>
</div>
)

View file

@ -266,6 +266,7 @@ export function ChecklistTemplatesManager() {
) as Array<{ id: Id<"companies">; name: string }> | undefined
const updateTemplate = useMutation(api.checklistTemplates.update)
const removeTemplate = useMutation(api.checklistTemplates.remove)
const companyOptions = useMemo<CompanyRow[]>(
() => (companies ?? []).map((c) => ({ id: c.id, name: c.name })).sort((a, b) => a.name.localeCompare(b.name, "pt-BR")),
@ -303,6 +304,22 @@ export function ChecklistTemplatesManager() {
}
}
const handleDelete = async (tpl: ChecklistTemplateRow) => {
if (!viewerId) return
const ok = confirm(`Excluir o template "${tpl.name}"? Esta ação não pode ser desfeita.`)
if (!ok) return
try {
await removeTemplate({
tenantId,
actorId: viewerId,
templateId: tpl.id,
})
toast.success("Template excluído.")
} catch (error) {
toast.error(error instanceof Error ? error.message : "Falha ao excluir template.")
}
}
return (
<div className="space-y-6">
<Card className="border border-slate-200">
@ -366,12 +383,22 @@ export function ChecklistTemplatesManager() {
) : null}
</div>
<div className="flex items-center gap-2">
<Button type="button" variant="outline" onClick={() => handleEdit(tpl)}>
<Button type="button" variant="outline" size="sm" onClick={() => handleEdit(tpl)}>
Editar
</Button>
<Button type="button" variant="outline" onClick={() => handleToggleArchived(tpl)}>
<Button type="button" variant="outline" size="sm" onClick={() => handleToggleArchived(tpl)}>
{tpl.isArchived ? "Restaurar" : "Arquivar"}
</Button>
<Button
type="button"
variant="ghost"
size="sm"
className="text-slate-500 hover:bg-red-50 hover:text-red-700"
onClick={() => handleDelete(tpl)}
title="Excluir template"
>
<Trash2 className="size-4" />
</Button>
</div>
</div>
</div>

View file

@ -376,7 +376,7 @@ export function TicketChecklistCard({
<Button
type="button"
variant="ghost"
className="h-9 px-2 text-xs text-neutral-700 hover:bg-slate-100 hover:text-neutral-900 active:bg-slate-200/70 focus-visible:bg-slate-100"
className="h-9 px-2 text-xs text-neutral-700 hover:bg-slate-100 hover:text-neutral-900 hover:border hover:border-slate-300 active:bg-slate-200/70 focus-visible:bg-slate-100"
onClick={async () => {
if (!actorId) return
try {

View file

@ -250,8 +250,8 @@ export function TicketsTable({ tickets, enteringIds }: TicketsTableProps) {
</div>
</TableCell>
<TableCell className={`${cellClass} hidden lg:table-cell overflow-hidden`}>
<div className="flex flex-col gap-1 truncate">
<span className="font-semibold text-neutral-800" title={ticket.requester.name}>
<div className="flex min-w-0 flex-col gap-1">
<span className="truncate font-semibold text-neutral-800" title={ticket.requester.name}>
{ticket.requester.name}
</span>
{ticket.company?.name ? (
@ -362,4 +362,4 @@ export function TicketsTable({ tickets, enteringIds }: TicketsTableProps) {
</CardContent>
</Card>
)
}
}