fix: corrige acentuacao e melhora UX de softwares
All checks were successful
CI/CD Web + Desktop / Detect changes (push) Successful in 5s
CI/CD Web + Desktop / Deploy (VPS Linux) (push) Successful in 3m19s
CI/CD Web + Desktop / Deploy Convex functions (push) Has been skipped
Quality Checks / Lint, Test and Build (push) Successful in 3m49s

- Adiciona botao de limpar pesquisa em softwares
- Corrige paginacao com historico de cursores
- Corrige acentuacao em politicas de SLA (politica -> política)
- Corrige acentuacao em varios textos do frontend

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-18 08:23:16 -03:00
parent cfb72358bc
commit 73de65bbaf
3 changed files with 68 additions and 41 deletions

View file

@ -84,7 +84,8 @@
"Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\\src\\components\\ui\" /b)", "Bash(dir \"D:\\Projetos IA\\sistema-de-chamados\\src\\components\\ui\" /b)",
"Bash(timeout 120 bun:*)", "Bash(timeout 120 bun:*)",
"Bash(bun run tauri:build:*)", "Bash(bun run tauri:build:*)",
"Bash(git remote:*)" "Bash(git remote:*)",
"Bash(powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"D:/Projetos IA/sistema-de-chamados/scripts/test-windows-collection.ps1\")"
] ]
} }
} }

View file

@ -4,7 +4,7 @@ import { useState } from "react"
import { useQuery } from "convex/react" import { useQuery } from "convex/react"
import { formatDistanceToNow } from "date-fns" import { formatDistanceToNow } from "date-fns"
import { ptBR } from "date-fns/locale" import { ptBR } from "date-fns/locale"
import { Package, Search, ChevronLeft, ChevronRight } from "lucide-react" import { Package, Search, ChevronLeft, ChevronRight, Eraser } from "lucide-react"
import { api } from "@/convex/_generated/api" import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel" import type { Id } from "@/convex/_generated/dataModel"
@ -25,7 +25,10 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
const viewerId = convexUserId as Id<"users"> | undefined const viewerId = convexUserId as Id<"users"> | undefined
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const [cursor, setCursor] = useState<string | null>(null) const [cursorHistory, setCursorHistory] = useState<(string | null)[]>([null])
const [pageIndex, setPageIndex] = useState(0)
const currentCursor = cursorHistory[pageIndex] ?? null
const result = useQuery( const result = useQuery(
api.machineSoftware.listByMachine, api.machineSoftware.listByMachine,
@ -36,7 +39,7 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
machineId, machineId,
search: search.trim() || undefined, search: search.trim() || undefined,
limit: 30, limit: 30,
cursor: cursor ?? undefined, cursor: currentCursor ?? undefined,
} }
: "skip" : "skip"
) )
@ -45,17 +48,26 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
const handleSearch = (value: string) => { const handleSearch = (value: string) => {
setSearch(value) setSearch(value)
setCursor(null) setCursorHistory([null])
setPageIndex(0)
} }
const handleNextPage = () => { const handleNextPage = () => {
if (result?.nextCursor) { if (result?.nextCursor) {
setCursor(result.nextCursor) const nextIndex = pageIndex + 1
setCursorHistory((prev) => {
const updated = [...prev]
updated[nextIndex] = result.nextCursor
return updated
})
setPageIndex(nextIndex)
} }
} }
const handlePrevPage = () => { const handlePrevPage = () => {
setCursor(null) if (pageIndex > 0) {
setPageIndex(pageIndex - 1)
}
} }
if (!viewerId) { if (!viewerId) {
@ -76,14 +88,28 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
</div> </div>
</div> </div>
<div className="relative"> <div className="flex items-center gap-2">
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" /> <div className="relative flex-1">
<Input <Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
placeholder="Buscar por nome, versão ou fabricante..." <Input
value={search} placeholder="Buscar por nome, versão ou fabricante..."
onChange={(e) => handleSearch(e.target.value)} value={search}
className="pl-9" onChange={(e) => handleSearch(e.target.value)}
/> className="pl-9"
/>
</div>
{search && (
<Button
type="button"
variant="outline"
size="icon"
onClick={() => handleSearch("")}
className="size-10 shrink-0"
title="Limpar pesquisa"
>
<Eraser className="size-4" />
</Button>
)}
</div> </div>
{result === undefined ? ( {result === undefined ? (
@ -147,7 +173,7 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={handlePrevPage} onClick={handlePrevPage}
disabled={!cursor} disabled={pageIndex === 0}
className="h-8 w-8 p-0" className="h-8 w-8 p-0"
> >
<ChevronLeft className="size-4" /> <ChevronLeft className="size-4" />

View file

@ -138,11 +138,11 @@ export function SlasManager() {
const handleSave = async () => { const handleSave = async () => {
if (!name.trim()) { if (!name.trim()) {
toast.error("Informe um nome para a politica") toast.error("Informe um nome para a política")
return return
} }
if (!convexUserId) { if (!convexUserId) {
toast.error("Sessao nao sincronizada com o Convex") toast.error("Sessão não sincronizada com o Convex")
return return
} }
@ -151,7 +151,7 @@ export function SlasManager() {
setSaving(true) setSaving(true)
const toastId = editingSla ? "sla-edit" : "sla-create" const toastId = editingSla ? "sla-edit" : "sla-create"
toast.loading(editingSla ? "Salvando alteracoes..." : "Criando politica...", { id: toastId }) toast.loading(editingSla ? "Salvando alterações..." : "Criando política...", { id: toastId })
try { try {
if (editingSla) { if (editingSla) {
@ -164,7 +164,7 @@ export function SlasManager() {
timeToFirstResponse, timeToFirstResponse,
timeToResolution, timeToResolution,
}) })
toast.success("Politica atualizada", { id: toastId }) toast.success("Política atualizada", { id: toastId })
} else { } else {
await createSla({ await createSla({
tenantId, tenantId,
@ -174,12 +174,12 @@ export function SlasManager() {
timeToFirstResponse, timeToFirstResponse,
timeToResolution, timeToResolution,
}) })
toast.success("Politica criada", { id: toastId }) toast.success("Política criada", { id: toastId })
} }
closeDialog() closeDialog()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
toast.error(editingSla ? "Nao foi possivel atualizar a politica" : "Nao foi possivel criar a politica", { id: toastId }) toast.error(editingSla ? "Não foi possível atualizar a política" : "Não foi possível criar a política", { id: toastId })
} finally { } finally {
setSaving(false) setSaving(false)
} }
@ -213,7 +213,7 @@ export function SlasManager() {
<Card className="border-slate-200"> <Card className="border-slate-200">
<CardHeader> <CardHeader>
<CardTitle className="flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-neutral-600"> <CardTitle className="flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-neutral-600">
<IconTargetArrow className="size-4" /> Politicas globais <IconTargetArrow className="size-4" /> Políticas globais
</CardTitle> </CardTitle>
<CardDescription>Regras que valem para todas as empresas.</CardDescription> <CardDescription>Regras que valem para todas as empresas.</CardDescription>
</CardHeader> </CardHeader>
@ -245,23 +245,23 @@ export function SlasManager() {
</Card> </Card>
</div> </div>
{/* Politicas globais de SLA */} {/* Políticas globais de SLA */}
<Card className="border-slate-200"> <Card className="border-slate-200">
<CardHeader> <CardHeader>
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div> <div>
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-neutral-900"> <CardTitle className="flex items-center gap-2 text-lg font-semibold text-neutral-900">
<Globe className="size-5" /> <Globe className="size-5" />
Politicas globais de SLA Políticas globais de SLA
</CardTitle> </CardTitle>
<CardDescription className="mt-1"> <CardDescription className="mt-1">
Estas regras valem para todas as empresas e categorias. Sao sobrescritas por regras mais especificas Estas regras valem para todas as empresas e categorias. São sobrescritas por regras mais específicas
(por empresa ou por categoria). (por empresa ou por categoria).
</CardDescription> </CardDescription>
</div> </div>
<Button size="sm" onClick={openCreateDialog} disabled={!convexUserId} className="gap-2 shrink-0"> <Button size="sm" onClick={openCreateDialog} disabled={!convexUserId} className="gap-2 shrink-0">
<Plus className="size-4" /> <Plus className="size-4" />
Nova politica Nova política
</Button> </Button>
</div> </div>
</CardHeader> </CardHeader>
@ -275,9 +275,9 @@ export function SlasManager() {
) : slas.length === 0 ? ( ) : slas.length === 0 ? (
<div className="rounded-xl border border-dashed border-slate-300 bg-slate-50/80 p-6 text-center"> <div className="rounded-xl border border-dashed border-slate-300 bg-slate-50/80 p-6 text-center">
<Globe className="mx-auto size-8 text-slate-400" /> <Globe className="mx-auto size-8 text-slate-400" />
<p className="mt-2 text-sm font-medium text-neutral-700">Nenhuma politica global cadastrada</p> <p className="mt-2 text-sm font-medium text-neutral-700">Nenhuma política global cadastrada</p>
<p className="mt-1 text-xs text-neutral-500"> <p className="mt-1 text-xs text-neutral-500">
Crie politicas de SLA para definir metas de resposta e resolucao para os chamados. Crie políticas de SLA para definir metas de resposta e resolução para os chamados.
</p> </p>
</div> </div>
) : ( ) : (
@ -300,7 +300,7 @@ export function SlasManager() {
)} )}
<div className="flex gap-4 text-xs text-neutral-600"> <div className="flex gap-4 text-xs text-neutral-600">
<span>Resposta: {formatMinutes(policy.timeToFirstResponse)}</span> <span>Resposta: {formatMinutes(policy.timeToFirstResponse)}</span>
<span>Resolucao: {formatMinutes(policy.timeToResolution)}</span> <span>Resolução: {formatMinutes(policy.timeToResolution)}</span>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -328,30 +328,30 @@ export function SlasManager() {
<Dialog open={dialogOpen} onOpenChange={(open) => !open && closeDialog()}> <Dialog open={dialogOpen} onOpenChange={(open) => !open && closeDialog()}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle>{editingSla ? "Editar politica de SLA" : "Nova politica de SLA"}</DialogTitle> <DialogTitle>{editingSla ? "Editar política de SLA" : "Nova política de SLA"}</DialogTitle>
<DialogDescription> <DialogDescription>
{editingSla {editingSla
? "Altere os dados da politica. Ela continua valendo para todas as empresas e categorias." ? "Altere os dados da política. Ela continua valendo para todas as empresas e categorias."
: "Crie uma politica global que vale para todas as empresas e categorias. Voce pode criar regras mais especificas depois."} : "Crie uma política global que vale para todas as empresas e categorias. Você pode criar regras mais específicas depois."}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4 py-2"> <div className="space-y-4 py-2">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="sla-name">Nome da politica</Label> <Label htmlFor="sla-name">Nome da política</Label>
<Input <Input
id="sla-name" id="sla-name"
placeholder="Ex.: Atendimento padrao, Premium, Urgente..." placeholder="Ex.: Atendimento padrão, Premium, Urgente..."
value={name} value={name}
onChange={(event) => setName(event.target.value)} onChange={(event) => setName(event.target.value)}
autoFocus autoFocus
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="sla-description">Descricao (opcional)</Label> <Label htmlFor="sla-description">Descrição (opcional)</Label>
<textarea <textarea
id="sla-description" id="sla-description"
className="min-h-[80px] w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-neutral-700 shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900/10" className="min-h-[80px] w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-neutral-700 shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-900/10"
placeholder="Quando esta politica deve ser usada, para quais tipos de chamado..." placeholder="Quando esta política deve ser usada, para quais tipos de chamado..."
value={description} value={description}
onChange={(event) => setDescription(event.target.value)} onChange={(event) => setDescription(event.target.value)}
/> />
@ -384,7 +384,7 @@ export function SlasManager() {
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Tempo para resolucao</Label> <Label>Tempo para resolução</Label>
<p className="text-xs text-neutral-500">Quanto tempo a equipe tem para resolver o chamado por completo.</p> <p className="text-xs text-neutral-500">Quanto tempo a equipe tem para resolver o chamado por completo.</p>
<div className="flex gap-2"> <div className="flex gap-2">
<Input <Input
@ -412,8 +412,8 @@ export function SlasManager() {
</div> </div>
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3"> <div className="rounded-lg border border-blue-200 bg-blue-50 p-3">
<p className="text-xs text-blue-800"> <p className="text-xs text-blue-800">
<strong>Dica:</strong> Esta politica e global e sera aplicada a todos os chamados que nao tiverem uma <strong>Dica:</strong> Esta política é global e será aplicada a todos os chamados que o tiverem uma
regra mais especifica (por empresa ou categoria). Use as secoes abaixo para criar regras personalizadas. regra mais específica (por empresa ou categoria). Use as seções abaixo para criar regras personalizadas.
</p> </p>
</div> </div>
</div> </div>
@ -422,7 +422,7 @@ export function SlasManager() {
Cancelar Cancelar
</Button> </Button>
<Button onClick={handleSave} disabled={saving || !name.trim()}> <Button onClick={handleSave} disabled={saving || !name.trim()}>
{saving ? "Salvando..." : editingSla ? "Salvar alteracoes" : "Criar politica"} {saving ? "Salvando..." : editingSla ? "Salvar alterações" : "Criar política"}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>