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(timeout 120 bun:*)",
"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 { formatDistanceToNow } from "date-fns"
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 type { Id } from "@/convex/_generated/dataModel"
@ -25,7 +25,10 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
const viewerId = convexUserId as Id<"users"> | undefined
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(
api.machineSoftware.listByMachine,
@ -36,7 +39,7 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
machineId,
search: search.trim() || undefined,
limit: 30,
cursor: cursor ?? undefined,
cursor: currentCursor ?? undefined,
}
: "skip"
)
@ -45,17 +48,26 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
const handleSearch = (value: string) => {
setSearch(value)
setCursor(null)
setCursorHistory([null])
setPageIndex(0)
}
const handleNextPage = () => {
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 = () => {
setCursor(null)
if (pageIndex > 0) {
setPageIndex(pageIndex - 1)
}
}
if (!viewerId) {
@ -76,14 +88,28 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
</div>
</div>
<div className="relative">
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
<Input
placeholder="Buscar por nome, versão ou fabricante..."
value={search}
onChange={(e) => handleSearch(e.target.value)}
className="pl-9"
/>
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-neutral-400" />
<Input
placeholder="Buscar por nome, versão ou fabricante..."
value={search}
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>
{result === undefined ? (
@ -147,7 +173,7 @@ export function DeviceSoftwareList({ machineId }: DeviceSoftwareListProps) {
variant="ghost"
size="sm"
onClick={handlePrevPage}
disabled={!cursor}
disabled={pageIndex === 0}
className="h-8 w-8 p-0"
>
<ChevronLeft className="size-4" />

View file

@ -138,11 +138,11 @@ export function SlasManager() {
const handleSave = async () => {
if (!name.trim()) {
toast.error("Informe um nome para a politica")
toast.error("Informe um nome para a política")
return
}
if (!convexUserId) {
toast.error("Sessao nao sincronizada com o Convex")
toast.error("Sessão não sincronizada com o Convex")
return
}
@ -151,7 +151,7 @@ export function SlasManager() {
setSaving(true)
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 {
if (editingSla) {
@ -164,7 +164,7 @@ export function SlasManager() {
timeToFirstResponse,
timeToResolution,
})
toast.success("Politica atualizada", { id: toastId })
toast.success("Política atualizada", { id: toastId })
} else {
await createSla({
tenantId,
@ -174,12 +174,12 @@ export function SlasManager() {
timeToFirstResponse,
timeToResolution,
})
toast.success("Politica criada", { id: toastId })
toast.success("Política criada", { id: toastId })
}
closeDialog()
} catch (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 {
setSaving(false)
}
@ -213,7 +213,7 @@ export function SlasManager() {
<Card className="border-slate-200">
<CardHeader>
<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>
<CardDescription>Regras que valem para todas as empresas.</CardDescription>
</CardHeader>
@ -245,23 +245,23 @@ export function SlasManager() {
</Card>
</div>
{/* Politicas globais de SLA */}
{/* Políticas globais de SLA */}
<Card className="border-slate-200">
<CardHeader>
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<CardTitle className="flex items-center gap-2 text-lg font-semibold text-neutral-900">
<Globe className="size-5" />
Politicas globais de SLA
Políticas globais de SLA
</CardTitle>
<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).
</CardDescription>
</div>
<Button size="sm" onClick={openCreateDialog} disabled={!convexUserId} className="gap-2 shrink-0">
<Plus className="size-4" />
Nova politica
Nova política
</Button>
</div>
</CardHeader>
@ -275,9 +275,9 @@ export function SlasManager() {
) : slas.length === 0 ? (
<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" />
<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">
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>
</div>
) : (
@ -300,7 +300,7 @@ export function SlasManager() {
)}
<div className="flex gap-4 text-xs text-neutral-600">
<span>Resposta: {formatMinutes(policy.timeToFirstResponse)}</span>
<span>Resolucao: {formatMinutes(policy.timeToResolution)}</span>
<span>Resolução: {formatMinutes(policy.timeToResolution)}</span>
</div>
</div>
<div className="flex items-center gap-2">
@ -328,30 +328,30 @@ export function SlasManager() {
<Dialog open={dialogOpen} onOpenChange={(open) => !open && closeDialog()}>
<DialogContent className="max-w-2xl">
<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>
{editingSla
? "Altere os dados da politica. 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."}
? "Altere os dados da política. Ela continua valendo para todas as empresas e categorias."
: "Crie uma política global que vale para todas as empresas e categorias. Você pode criar regras mais específicas depois."}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
<div className="space-y-2">
<Label htmlFor="sla-name">Nome da politica</Label>
<Label htmlFor="sla-name">Nome da política</Label>
<Input
id="sla-name"
placeholder="Ex.: Atendimento padrao, Premium, Urgente..."
placeholder="Ex.: Atendimento padrão, Premium, Urgente..."
value={name}
onChange={(event) => setName(event.target.value)}
autoFocus
/>
</div>
<div className="space-y-2">
<Label htmlFor="sla-description">Descricao (opcional)</Label>
<Label htmlFor="sla-description">Descrição (opcional)</Label>
<textarea
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"
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}
onChange={(event) => setDescription(event.target.value)}
/>
@ -384,7 +384,7 @@ export function SlasManager() {
</div>
</div>
<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>
<div className="flex gap-2">
<Input
@ -412,8 +412,8 @@ export function SlasManager() {
</div>
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3">
<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
regra mais especifica (por empresa ou categoria). Use as secoes abaixo para criar regras personalizadas.
<strong>Dica:</strong> Esta política é global e será aplicada a todos os chamados que o tiverem uma
regra mais específica (por empresa ou categoria). Use as seções abaixo para criar regras personalizadas.
</p>
</div>
</div>
@ -422,7 +422,7 @@ export function SlasManager() {
Cancelar
</Button>
<Button onClick={handleSave} disabled={saving || !name.trim()}>
{saving ? "Salvando..." : editingSla ? "Salvar alteracoes" : "Criar politica"}
{saving ? "Salvando..." : editingSla ? "Salvar alterações" : "Criar política"}
</Button>
</DialogFooter>
</DialogContent>