feat(admin): exibir mensagem de erro no detalhe da máquina quando Convex/fallback falham e oferecer retry; prioriza caminho Convex e esclarece 404
This commit is contained in:
parent
ee1f19f7f2
commit
42942350dc
1 changed files with 77 additions and 4 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useEffect, useMemo, useRef, useState } from "react"
|
import { useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { useQuery } from "convex/react"
|
import { useQuery } from "convex/react"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
import { api } from "@/convex/_generated/api"
|
import { api } from "@/convex/_generated/api"
|
||||||
import {
|
import {
|
||||||
MachineDetails,
|
MachineDetails,
|
||||||
|
|
@ -10,9 +11,11 @@ import {
|
||||||
} from "@/components/admin/machines/admin-machines-overview"
|
} from "@/components/admin/machines/admin-machines-overview"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
import type { Id } from "@/convex/_generated/dataModel"
|
import type { Id } from "@/convex/_generated/dataModel"
|
||||||
|
|
||||||
export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||||
|
const router = useRouter()
|
||||||
const single = useQuery(
|
const single = useQuery(
|
||||||
(api as any).machines.getById,
|
(api as any).machines.getById,
|
||||||
machineId ? ({ id: machineId as Id<"machines">, includeMetadata: true } as const) : ("skip" as const)
|
machineId ? ({ id: machineId as Id<"machines">, includeMetadata: true } as const) : ("skip" as const)
|
||||||
|
|
@ -23,6 +26,8 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
||||||
|
|
||||||
// Fallback via HTTP in caso de o Convex React demorar/ficar preso em loading
|
// Fallback via HTTP in caso de o Convex React demorar/ficar preso em loading
|
||||||
const [fallback, setFallback] = useState<Record<string, unknown> | null>(null)
|
const [fallback, setFallback] = useState<Record<string, unknown> | null>(null)
|
||||||
|
const [loadError, setLoadError] = useState<string | null>(null)
|
||||||
|
const [retryTick, setRetryTick] = useState(0)
|
||||||
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
const shouldLoad = single === undefined && !fallback && Boolean(machineId)
|
const shouldLoad = single === undefined && !fallback && Boolean(machineId)
|
||||||
|
|
||||||
|
|
@ -34,16 +39,41 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = (await res.json()) as Record<string, unknown>
|
const data = (await res.json()) as Record<string, unknown>
|
||||||
setFallback(data)
|
setFallback(data)
|
||||||
|
setLoadError(null)
|
||||||
|
} else {
|
||||||
|
// Not found (404) ou erro de servidor
|
||||||
|
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 parse error
|
||||||
|
}
|
||||||
|
setLoadError(message)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
// ignore
|
setLoadError("Erro de rede ao carregar os dados da máquina.")
|
||||||
}
|
}
|
||||||
}, 300)
|
}, 600)
|
||||||
return () => {
|
return () => {
|
||||||
if (timer.current) clearTimeout(timer.current)
|
if (timer.current) clearTimeout(timer.current)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [shouldLoad, machineId])
|
}, [shouldLoad, machineId, retryTick])
|
||||||
|
|
||||||
|
// Timeout de proteção: se depois de X segundos ainda estiver carregando e sem fallback, mostra erro claro
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shouldLoad) return
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setLoadError(
|
||||||
|
"Tempo esgotado ao consultar os dados (Convex). Verifique sua conexão e tente novamente."
|
||||||
|
)
|
||||||
|
}, 10_000)
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [shouldLoad, machineId, retryTick])
|
||||||
|
|
||||||
const machine: MachinesQueryItem | null = useMemo(() => {
|
const machine: MachinesQueryItem | null = useMemo(() => {
|
||||||
const source = single ?? fallback
|
const source = single ?? fallback
|
||||||
|
|
@ -51,6 +81,19 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
||||||
return normalizeMachineItem(source)
|
return normalizeMachineItem(source)
|
||||||
}, [single, fallback])
|
}, [single, fallback])
|
||||||
const isLoading = single === undefined && !fallback
|
const isLoading = single === undefined && !fallback
|
||||||
|
const isNotFound = single === null && !fallback
|
||||||
|
|
||||||
|
const onRetry = () => {
|
||||||
|
setLoadError(null)
|
||||||
|
setFallback(null)
|
||||||
|
setRetryTick((t) => t + 1)
|
||||||
|
// força revalidação de RSC/convex subscription
|
||||||
|
try {
|
||||||
|
router.refresh()
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -64,5 +107,35 @@ export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: s
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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} />
|
return <MachineDetails machine={machine} />
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue