since3-portal/modules/shared/ui/components/product-check.tsx
2026-09-29 15:20:50 +02:00

78 lines
2.8 KiB
TypeScript

"use client";
import { useEffect, useRef, useState, useTransition } from "react";
import { LoaderCircleIcon, RefreshCwIcon } from "lucide-react";
import { cn } from "cn";
import { Button } from "@/components/ui/button";
import { checkProduct, checkStaleProducts } from "@/modules/shared/actions/product-checks";
import { formatDateTime } from "@/modules/shared/lib/info-format";
import type { Product } from "@/modules/shared/schemas/info";
export function ProductCheckLine({
profileId,
productId,
lastCheck,
}: {
profileId: string;
productId: string;
lastCheck: Product["lastCheck"];
}) {
const [pending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
function handleCheck() {
setError(null);
startTransition(async () => {
const response = await checkProduct(profileId, productId);
if ("error" in response) setError(response.error);
});
}
return (
<div className="flex items-center gap-1 text-small">
<span aria-live="polite" className={cn(lastCheck && !lastCheck.ok ? "text-signal-error" : "text-muted-foreground")}>
{pending
? "Wird geprüft..."
: error ??
(lastCheck
? `${lastCheck.ok ? "Erreichbar" : lastCheck.error ?? "Nicht erreichbar"}${
lastCheck.ok && lastCheck.responseMs !== null ? ` · ${lastCheck.responseMs} ms` : ""
} · geprüft ${formatDateTime(lastCheck.checkedAt)}`
: "Noch nicht geprüft")}
</span>
<Button
type="button"
variant="ghost"
size="icon-sm"
className="size-6"
onClick={handleCheck}
disabled={pending}
aria-label="Erreichbarkeit jetzt prüfen"
title="Erreichbarkeit jetzt prüfen"
>
<RefreshCwIcon className={cn("size-3.5", pending && "animate-spin")} />
</Button>
</div>
);
}
export function AutoCheck({ profileId }: { profileId: string }) {
const [running, setRunning] = useState(true);
const started = useRef(false);
useEffect(() => {
if (started.current) return;
started.current = true;
checkStaleProducts(profileId)
.catch(() => undefined)
.finally(() => setRunning(false));
}, [profileId]);
if (!running) return null;
return (
<span className="inline-flex items-center gap-1.5 text-small text-muted-foreground" aria-live="polite">
<LoaderCircleIcon className="size-3.5 animate-spin" /> Erreichbarkeit wird geprüft...
</span>
);
}