Files
lead-scraper/components/shared/ProgressCard.tsx
Timo Uttenweiler 8dc135a8f7 fix: Indeterminate Progress-Anzeige bei Anymailfinder Bulk-Warten
- Wenn current=0 und total>0: Shimmer-Animation statt leerer Balken
- Bouncing Dots + "Warte auf Anymailfinder-Server..." Text
- Hinweis "dauert ca. 1–3 Min. für X Domains"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:44:53 +01:00

92 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { cn } from "@/lib/utils";
interface ProgressCardProps {
title: string;
current: number;
total: number;
subtitle?: string;
status?: "running" | "complete" | "failed" | "idle";
}
export function ProgressCard({ title, current, total, subtitle, status = "running" }: ProgressCardProps) {
const pct = total > 0 ? Math.round((current / total) * 100) : 0;
// Indeterminate = läuft aber noch kein Fortschritt (z.B. warten auf Anymailfinder Bulk)
const indeterminate = status === "running" && current === 0 && total > 0;
return (
<div className="bg-[#111118] border border-[#1e1e2e] rounded-xl p-5 space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-medium text-white">{title}</h3>
{subtitle && <p className="text-xs text-gray-500 mt-0.5">{subtitle}</p>}
</div>
<StatusBadge status={status} />
</div>
<div className="space-y-2">
<div className="flex justify-between text-xs text-gray-400">
{indeterminate ? (
<>
<span className="flex items-center gap-1.5">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: "0ms" }} />
<span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="inline-block w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: "300ms" }} />
<span className="text-blue-300 ml-1">Warte auf Anymailfinder-Server...</span>
</span>
<span className="text-gray-600">{total} Domains</span>
</>
) : (
<>
<span>{current.toLocaleString()} / {total.toLocaleString()}</span>
<span>{pct}%</span>
</>
)}
</div>
<div className="h-2 bg-[#1e1e2e] rounded-full overflow-hidden relative">
{indeterminate ? (
<div className="absolute inset-0 overflow-hidden rounded-full">
<div className="h-full w-1/3 bg-gradient-to-r from-transparent via-blue-500 to-transparent animate-[shimmer_1.5s_ease-in-out_infinite] rounded-full" />
</div>
) : (
<div
className={cn(
"h-full rounded-full transition-all duration-500",
status === "failed"
? "bg-red-500"
: status === "complete"
? "bg-green-500"
: "bg-gradient-to-r from-blue-500 to-purple-600"
)}
style={{ width: `${Math.max(pct, status === "running" ? 2 : 0)}%` }}
/>
)}
</div>
{indeterminate && (
<p className="text-[11px] text-gray-600">
Batch-Verarbeitung läuft auf Anymailfinder-Servern. Dauert ca. 13 Min. für {total} Domains.
</p>
)}
</div>
</div>
);
}
export function StatusBadge({ status }: { status: string }) {
const config: Record<string, { label: string; color: string; dot: string }> = {
running: { label: "Läuft", color: "bg-blue-500/10 text-blue-400 border-blue-500/20", dot: "bg-blue-400 animate-pulse" },
complete: { label: "Abgeschlossen", color: "bg-green-500/10 text-green-400 border-green-500/20", dot: "bg-green-400" },
failed: { label: "Fehlgeschlagen", color: "bg-red-500/10 text-red-400 border-red-500/20", dot: "bg-red-400" },
pending: { label: "Ausstehend", color: "bg-yellow-500/10 text-yellow-400 border-yellow-500/20", dot: "bg-yellow-400" },
idle: { label: "Bereit", color: "bg-gray-500/10 text-gray-400 border-gray-500/20", dot: "bg-gray-400" },
};
const c = config[status] || config.idle;
return (
<span className={`inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium border ${c.color}`}>
<span className={`w-1.5 h-1.5 rounded-full ${c.dot}`} />
{c.label}
</span>
);
}