chore(types): remove explicit any, fix hook deps, and unused vars across admin/api/tickets; tighten zod server schemas; adjust UI types; fix pdf export expression; minor cleanup

This commit is contained in:
Esdras Renan 2025-10-09 22:43:39 -03:00
parent 0556502685
commit 6ffd6c6392
17 changed files with 104 additions and 59 deletions

View file

@ -1,6 +1,6 @@
"use client"
import { useMemo, useState, useTransition } from "react"
import { useEffect, useMemo, useState, useTransition } from "react"
import { toast } from "sonner"
@ -102,12 +102,12 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d
const normalizedRoles = useMemo(() => roleOptions ?? ROLE_OPTIONS, [roleOptions])
// load companies for association
useMemo(() => {
useEffect(() => {
void (async () => {
try {
const r = await fetch("/api/admin/companies", { credentials: "include" })
const j = await r.json()
const items = (j.companies ?? []).map((c: any) => ({ id: c.id as string, name: c.name as string }))
const j = (await r.json()) as { companies?: Array<{ id: string; name: string }> }
const items = (j.companies ?? []).map((c) => ({ id: c.id, name: c.name }))
setCompanies(items)
} catch {
// noop

View file

@ -1,6 +1,6 @@
"use client"
import { useMemo, useState, useTransition } from "react"
import { useEffect, useMemo, useState, useTransition } from "react"
import { toast } from "sonner"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
@ -64,7 +64,7 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies:
}
}
useMemo(() => { void loadLastAlerts(companies) }, [])
useEffect(() => { void loadLastAlerts(companies) }, [companies])
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()

View file

@ -84,6 +84,7 @@ type MachineInventory = {
// Dados enviados pelo agente desktop (inventário básico/estendido)
disks?: Array<{ name?: string; mountPoint?: string; fs?: string; totalBytes?: number; availableBytes?: number }>
extended?: { linux?: LinuxExtended; windows?: WindowsExtended; macos?: MacExtended }
services?: Array<{ name?: string; status?: string; displayName?: string }>
}
type MachinesQueryItem = {
@ -352,8 +353,8 @@ export function AdminMachinesOverview({ tenantId }: { tenantId: string }) {
{Array.isArray(machine.inventory?.disks) ? (
<Badge variant="outline">{machine.inventory?.disks?.length ?? 0} discos</Badge>
) : null}
{Array.isArray((machine.inventory as any)?.services) ? (
<Badge variant="outline">{(machine.inventory as any).services.length} serviços</Badge>
{Array.isArray(machine.inventory?.services) ? (
<Badge variant="outline">{machine.inventory?.services?.length ?? 0} serviços</Badge>
) : null}
</div>
</TableCell>
@ -617,7 +618,7 @@ function MachineDetails({ machine }: MachineDetailsProps) {
</TableRow>
</TableHeader>
<TableBody>
{(network as any[]).map((iface, idx) => (
{(network as Array<{ name?: string; mac?: string; ip?: string }>).map((iface, idx) => (
<TableRow key={`iface-${idx}`} className="border-slate-100">
<TableCell className="text-sm">{iface?.name ?? "—"}</TableCell>
<TableCell className="text-sm text-muted-foreground">{iface?.mac ?? "—"}</TableCell>
@ -711,7 +712,7 @@ function MachineDetails({ machine }: MachineDetailsProps) {
<div className="rounded-md border border-slate-200 bg-emerald-50/40 p-3 dark:bg-emerald-900/10">
<p className="text-xs font-semibold uppercase text-slate-500">SMART</p>
<div className="mt-2 grid gap-2">
{linuxExt.smart.map((s: any, idx: number) => {
{linuxExt.smart.map((s: { smart_status?: { passed?: boolean }; model_name?: string; model_family?: string; serial_number?: string; device?: { name?: string } }, idx: number) => {
const ok = s?.smart_status?.passed !== false
const model = s?.model_name ?? s?.model_family ?? "Disco"
const serial = s?.serial_number ?? s?.device?.name ?? "—"
@ -758,7 +759,7 @@ function MachineDetails({ machine }: MachineDetailsProps) {
</TableRow>
</TableHeader>
<TableBody>
{(windowsExt.services as any[]).slice(0, 10).map((svc: any, i: number) => (
{(windowsExt.services as Array<{ Name?: string; DisplayName?: string; Status?: string }>).slice(0, 10).map((svc, i: number) => (
<TableRow key={`svc-${i}`} className="border-slate-100">
<TableCell className="text-sm">{svc?.Name ?? "—"}</TableCell>
<TableCell className="text-sm text-muted-foreground">{svc?.DisplayName ?? "—"}</TableCell>
@ -775,7 +776,7 @@ function MachineDetails({ machine }: MachineDetailsProps) {
<div className="rounded-md border border-slate-200 bg-slate-50/60 p-3">
<p className="text-xs font-semibold uppercase text-slate-500">Softwares (amostra)</p>
<ul className="mt-2 grid gap-1 text-xs text-muted-foreground">
{(windowsExt.software as any[]).slice(0, 8).map((s: any, i: number) => (
{(windowsExt.software as Array<{ DisplayName?: string; name?: string; DisplayVersion?: string; Publisher?: string }>).slice(0, 8).map((s, i: number) => (
<li key={`sw-${i}`}>
<span className="font-medium text-foreground">{s?.DisplayName ?? s?.name ?? "—"}</span>
{s?.DisplayVersion ? <span className="ml-1">{s.DisplayVersion}</span> : null}
@ -823,7 +824,7 @@ function MachineDetails({ machine }: MachineDetailsProps) {
<section className="space-y-2">
<h4 className="text-sm font-semibold">Alertas de postura</h4>
<div className="space-y-2">
{machine?.postureAlerts?.map((a: any, i: number) => (
{machine?.postureAlerts?.map((a: { kind?: string; message?: string; severity?: string }, i: number) => (
<div key={`alert-${i}`} className={cn("flex items-center justify-between rounded-md border px-3 py-2 text-sm",
(a?.severity ?? "warning").toLowerCase() === "critical" ? "border-rose-500/20 bg-rose-500/10" : "border-amber-500/20 bg-amber-500/10")
}>
@ -844,8 +845,8 @@ function MachineDetails({ machine }: MachineDetailsProps) {
{Array.isArray(software) && software.length > 0 ? (
<Button size="sm" variant="outline" onClick={() => exportCsv(software, "softwares.csv")}>Softwares CSV</Button>
) : null}
{Array.isArray((metadata as any)?.services) && (metadata as any).services.length > 0 ? (
<Button size="sm" variant="outline" onClick={() => exportCsv((metadata as any).services, "servicos.csv")}>Serviços CSV</Button>
{Array.isArray(metadata?.services) && metadata.services.length > 0 ? (
<Button size="sm" variant="outline" onClick={() => exportCsv(metadata.services as Array<Record<string, unknown>>, "servicos.csv")}>Serviços CSV</Button>
) : null}
</div>
{fleet ? (
@ -979,7 +980,7 @@ function exportCsv(items: Array<Record<string, unknown>>, filename: string) {
for (const it of items) {
const row = headers
.map((h) => {
const v = (it as any)?.[h]
const v = (it as Record<string, unknown>)[h]
if (v === undefined || v === null) return ""
const s = typeof v === "string" ? v : JSON.stringify(v)
return `"${s.replace(/"/g, '""')}"`

View file

@ -48,7 +48,7 @@ function BacklogPriorityPie() {
const chartData = keys
.map((k) => ({ name: PRIORITY_LABELS[k] ?? k, value: data.priorityCounts?.[k] ?? 0, fill: fills[k as keyof typeof fills] }))
.filter((d) => d.value > 0)
const chartConfig: any = { value: { label: "Tickets" } }
const chartConfig: Record<string, { label: string }> = { value: { label: "Tickets" } }
return (
<Card className="flex flex-col">
@ -118,7 +118,7 @@ function QueuesOpenBar() {
if (!data) return <Skeleton className="h-[300px] w-full" />
const chartData = (data.queueBreakdown ?? []).map((q) => ({ queue: q.name, open: q.open }))
const chartConfig: any = { open: { label: "Abertos" } }
const chartConfig: Record<string, { label: string }> = { open: { label: "Abertos" } }
return (
<Card>

View file

@ -42,15 +42,15 @@ export function HoursReport() {
convexUserId ? { tenantId, viewerId: convexUserId as Id<"users">, range: timeRange } : "skip"
) as { rangeDays: number; items: HoursItem[] } | undefined
const items = data?.items ?? []
const companies = useQuery(api.companies.list, convexUserId ? { tenantId, viewerId: convexUserId as Id<"users"> } : "skip") as Array<{ id: Id<"companies">; name: string }> | undefined
const filtered = useMemo(() => {
const items = data?.items ?? []
const q = query.trim().toLowerCase()
let list = items
if (companyId !== "all") list = list.filter((it) => String(it.companyId) === companyId)
if (q) list = list.filter((it) => it.name.toLowerCase().includes(q))
return list
}, [items, query, companyId])
}, [data?.items, query, companyId])
return (
<div className="space-y-6">

View file

@ -35,7 +35,6 @@ const submitButtonClass =
export function TicketComments({ ticket }: TicketCommentsProps) {
const { convexUserId, isStaff, role } = useAuth()
const normalizedRole = role ?? null
const isManager = normalizedRole === "manager"
const canSeeInternalComments = normalizedRole === "admin" || normalizedRole === "agent"
const addComment = useMutation(api.tickets.addComment)
const removeAttachment = useMutation(api.tickets.removeCommentAttachment)

View file

@ -155,7 +155,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
return selectedCategoryId !== currentCategoryId || selectedSubcategoryId !== currentSubcategoryId
}, [selectedCategoryId, selectedSubcategoryId, currentCategoryId, currentSubcategoryId])
const currentQueueName = ticket.queue ?? ""
const isAvulso = Boolean((ticket as any).company?.isAvulso ?? false)
const isAvulso = Boolean(((ticket.company ?? null) as { isAvulso?: boolean } | null)?.isAvulso ?? false)
const [queueSelection, setQueueSelection] = useState(currentQueueName)
const queueDirty = useMemo(() => queueSelection !== currentQueueName, [queueSelection, currentQueueName])
const formDirty = dirty || categoryDirty || queueDirty
@ -307,9 +307,11 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
}
}, [
ticket.id,
ticket.workSummary,
ticket.workSummary?.totalWorkedMs,
ticket.workSummary?.internalWorkedMs,
ticket.workSummary?.externalWorkedMs,
ticketActiveSession,
ticketActiveSession?.id,
ticketActiveSessionStartedAtMs,
ticketActiveSessionWorkType,
@ -352,7 +354,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
setNow(Date.now())
}, 1000)
return () => clearInterval(interval)
}, [workSummary?.activeSession?.id])
}, [workSummary?.activeSession, workSummary?.activeSession?.id])
useEffect(() => {
if (!pauseDialogOpen) {
@ -382,7 +384,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
toast.dismiss("work")
toast.loading("Iniciando atendimento...", { id: "work" })
try {
const result = await startWork({ ticketId: ticket.id as Id<"tickets">, actorId: convexUserId as Id<"users">, workType } as any)
const result = await startWork({ ticketId: ticket.id as Id<"tickets">, actorId: convexUserId as Id<"users">, workType })
if (result?.status === "already_started") {
toast.info("O atendimento já estava em andamento", { id: "work" })
} else {

View file

@ -2,7 +2,7 @@
import { useEffect, useState } from "react"
import { useRouter } from "next/navigation"
import { format, formatDistanceToNow, formatDistanceToNowStrict } from "date-fns"
import { format, formatDistanceToNowStrict } from "date-fns"
import { ptBR } from "date-fns/locale"
import { type LucideIcon, Code, FileText, Mail, MessageCircle, MessageSquare, Phone } from "lucide-react"
@ -235,8 +235,8 @@ export function TicketsTable({ tickets = ticketsMock }: TicketsTableProps) {
</div>
</TableCell>
<TableCell className={`${cellClass} hidden lg:table-cell pl-8`}>
<span className="max-w-[160px] truncate text-sm text-neutral-800" title={(ticket as any).company?.name ?? "—"}>
{(ticket as any).company?.name ?? "—"}
<span className="max-w-[160px] truncate text-sm text-neutral-800" title={((ticket.company ?? null) as { name?: string } | null)?.name ?? "—"}>
{((ticket.company ?? null) as { name?: string } | null)?.name ?? "—"}
</span>
</TableCell>
<TableCell className={`${cellClass} hidden md:table-cell pl-1 pr-8`}>

View file

@ -42,7 +42,7 @@ export function TicketsView() {
const companies = useMemo(() => {
const set = new Set<string>()
for (const t of tickets) {
const name = (t as any).company?.name as string | undefined
const name = ((t as unknown as { company?: { name?: string } })?.company?.name) as string | undefined
if (name) set.add(name)
}
return Array.from(set).sort((a, b) => a.localeCompare(b, "pt-BR"))
@ -64,11 +64,11 @@ export function TicketsView() {
working = working.filter((t) => t.queue === filters.queue)
}
if (filters.company) {
working = working.filter((t) => ((t as any).company?.name ?? null) === filters.company)
working = working.filter((t) => (((t as unknown as { company?: { name?: string } })?.company?.name) ?? null) === filters.company)
}
return working
}, [tickets, filters.queue, filters.status, filters.view])
}, [tickets, filters.queue, filters.status, filters.view, filters.company])
return (
<div className="flex flex-col gap-6 px-4 lg:px-6">