chore(types): remove anys and harden Convex data fetch
- Strongly type company-service and API routes - Fix Next.js searchParams (promise) in admin/machines page - Add vitest module marker + stub for tsconfig-paths/register - Use Convex query in client as primary fallback for machine details - Replace any casts in admin machines components Build + lint are clean locally; details page no longer skeleton-loops.
This commit is contained in:
parent
eee0f432e7
commit
c640e288b1
8 changed files with 76 additions and 90 deletions
|
|
@ -13,16 +13,15 @@ import { Card, CardContent } from "@/components/ui/card"
|
|||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import type { Id } from "@/convex/_generated/dataModel"
|
||||
import { ConvexHttpClient } from "convex/browser"
|
||||
|
||||
export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||
export function AdminMachineDetailsClient({ tenantId: _tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||
const router = useRouter()
|
||||
const single = useQuery(
|
||||
(api as any).machines.getById,
|
||||
machineId ? ({ id: machineId as Id<"machines">, includeMetadata: true } as const) : ("skip" as const)
|
||||
) as
|
||||
| Record<string, unknown>
|
||||
| null
|
||||
| undefined
|
||||
const queryArgs = machineId
|
||||
? ({ id: machineId as Id<"machines">, includeMetadata: true } as const)
|
||||
: "skip"
|
||||
|
||||
const single = useQuery(api.machines.getById, queryArgs)
|
||||
|
||||
// Fallback via HTTP in caso de o Convex React demorar/ficar preso em loading
|
||||
const [fallback, setFallback] = useState<Record<string, unknown> | null>(null)
|
||||
|
|
@ -35,23 +34,46 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
|||
if (!shouldLoad) return
|
||||
timer.current = setTimeout(async () => {
|
||||
try {
|
||||
const res = await fetch(`/api/admin/machines/${machineId}/details`, { credentials: "include" })
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as Record<string, unknown>
|
||||
setFallback(data)
|
||||
setLoadError(null)
|
||||
} else {
|
||||
// Not found (404) ou erro de servidor
|
||||
let message = `Falha ao carregar (HTTP ${res.status})`
|
||||
// 1) Tenta via Convex direto do browser (independe do servidor)
|
||||
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL
|
||||
if (convexUrl) {
|
||||
try {
|
||||
const payload = (await res.json()) as Record<string, unknown>
|
||||
if (typeof payload?.error === "string" && payload.error) {
|
||||
message = payload.error
|
||||
const http = new ConvexHttpClient(convexUrl)
|
||||
const data = (await http.query(api.machines.getById, {
|
||||
id: machineId as Id<"machines">,
|
||||
includeMetadata: true,
|
||||
})) as Record<string, unknown> | null
|
||||
if (data) {
|
||||
setFallback(data)
|
||||
setLoadError(null)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// ignore parse error
|
||||
// continua para o plano B
|
||||
}
|
||||
setLoadError(message)
|
||||
}
|
||||
|
||||
// 2) Plano B: rota do servidor (útil em ambientes sem Convex público)
|
||||
try {
|
||||
const res = await fetch(`/api/admin/machines/${machineId}/details`, { credentials: "include" })
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as Record<string, unknown>
|
||||
setFallback(data)
|
||||
setLoadError(null)
|
||||
} else {
|
||||
let message = `Falha ao carregar (HTTP ${res.status})`
|
||||
try {
|
||||
const payload = (await res.json()) as Record<string, unknown>
|
||||
if (typeof payload?.error === "string" && payload.error) {
|
||||
message = payload.error
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
setLoadError(message)
|
||||
}
|
||||
} catch {
|
||||
setLoadError("Erro de rede ao carregar os dados da máquina.")
|
||||
}
|
||||
} catch (err) {
|
||||
setLoadError("Erro de rede ao carregar os dados da máquina.")
|
||||
|
|
@ -60,7 +82,6 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
|||
return () => {
|
||||
if (timer.current) clearTimeout(timer.current)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [shouldLoad, machineId, retryTick])
|
||||
|
||||
// Timeout de proteção: se depois de X segundos ainda estiver carregando e sem fallback, mostra erro claro
|
||||
|
|
@ -72,7 +93,6 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
|||
)
|
||||
}, 10_000)
|
||||
return () => clearTimeout(timeout)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [shouldLoad, machineId, retryTick])
|
||||
|
||||
const machine: MachinesQueryItem | null = useMemo(() => {
|
||||
|
|
@ -80,7 +100,7 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
|||
if (source === undefined || source === null) return source as null
|
||||
return normalizeMachineItem(source)
|
||||
}, [single, fallback])
|
||||
const isLoading = single === undefined && !fallback
|
||||
const isLoading = single === undefined && !fallback && !loadError
|
||||
const isNotFound = single === null && !fallback
|
||||
|
||||
const onRetry = () => {
|
||||
|
|
@ -94,48 +114,6 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
|||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="space-y-3 p-6">
|
||||
<Skeleton className="h-6 w-64" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
<Skeleton className="h-48 w-full" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
if (isNotFound) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="space-y-3 p-6">
|
||||
<p className="text-sm font-medium text-red-600">Máquina não encontrada</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Verifique o identificador e tente novamente.
|
||||
</p>
|
||||
<div className="pt-2">
|
||||
<Button size="sm" onClick={onRetry}>Tentar novamente</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
if (loadError && !machine) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="space-y-3 p-6">
|
||||
<p className="text-sm font-medium text-red-600">Falha ao carregar os dados da máquina</p>
|
||||
<p className="text-sm text-muted-foreground">{loadError}</p>
|
||||
<div className="pt-2 flex items-center gap-2">
|
||||
<Button size="sm" onClick={onRetry}>Tentar novamente</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return <MachineDetails machine={machine} />
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,13 @@ import type { Id } from "@/convex/_generated/dataModel"
|
|||
import { api } from "@/convex/_generated/api"
|
||||
import { useAuth } from "@/lib/auth-client"
|
||||
|
||||
export function MachineBreadcrumbs({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||
export function MachineBreadcrumbs({ tenantId: _tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||
const { convexUserId } = useAuth()
|
||||
const item = useQuery(
|
||||
(api as any).machines.getById,
|
||||
machineId ? ({ id: machineId as Id<"machines">, includeMetadata: false } as const) : ("skip" as const)
|
||||
) as
|
||||
| { hostname: string }
|
||||
| null
|
||||
| undefined
|
||||
const queryArgs = machineId && convexUserId
|
||||
? ({ id: machineId as Id<"machines">, includeMetadata: false } as const)
|
||||
: "skip"
|
||||
|
||||
const item = useQuery(api.machines.getById, queryArgs)
|
||||
const hostname = useMemo(() => item?.hostname ?? "Detalhe", [item])
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue