Untitled

 avatar
unknown
plain_text
2 months ago
6.1 kB
3
Indexable
// components/download-all.tsx
import { useState, useEffect, useRef } from "react";
import { Download } from "lucide-react";
import { motion } from "framer-motion";
import { useParams } from "next/navigation";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";

export function DownloadAll() {
  const [isDownloading, setIsDownloading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [startTime, setStartTime] = useState<number | null>(null);
  const [statusMessage, setStatusMessage] = useState("");
  const workerRef = useRef<Worker | null>(null);
  const params = useParams();
  const clientId = params.clientId as string;

  useEffect(() => {
    if (typeof window !== 'undefined') {
      workerRef.current = new Worker('/zip-worker.js');
      
      workerRef.current.onmessage = (e) => {
        const { type, payload } = e.data;
        
        switch (type) {
          case 'progress':
            setProgress(Math.min(90, payload.percent));
            setStatusMessage(`Verarbeite ${payload.filename} (${payload.processed} von ${payload.total})`);
            break;
            
          case 'generating':
            setProgress(payload.percent);
            setStatusMessage(payload.message);
            break;
            
          case 'complete':
            if (payload instanceof Blob && payload.size > 0) {
              handleDownloadComplete(payload);
            } else {
              handleError('Generated ZIP is empty');
            }
            break;
            
          case 'fileError':
            console.error(`Error processing ${payload.filename}: ${payload.error}`);
            toast.error(`Fehler beim Verarbeiten von ${payload.filename}`);
            break;
            
          case 'error':
            handleError(payload);
            break;
            
          case 'processing':
            setStatusMessage(payload);
            break;
        }
      };
    }

    return () => {
      workerRef.current?.terminate();
    };
  }, []);

  const handleDownloadComplete = (blob: Blob) => {
    console.log(`ZIP file size: ${(blob.size / 1024 / 1024).toFixed(2)} MB`);
    
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = `${clientId}-fotos.zip`;
    
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    setTimeout(() => {
      URL.revokeObjectURL(url);
      setProgress(100);
      setStatusMessage("Download abgeschlossen!");
      toast.success("Download erfolgreich");
      
      setTimeout(() => {
        setIsDownloading(false);
        setProgress(0);
        setStartTime(null);
        setStatusMessage("");
      }, 3000);
    }, 1000);
  };

  const handleError = (error: string) => {
    console.error("Download failed:", error);
    setIsDownloading(false);
    setProgress(0);
    setStartTime(null);
    setStatusMessage("");
    toast.error("Download fehlgeschlagen", {
      description: error || "Bitte versuchen Sie es später erneut."
    });
  };

  const handleDownloadAll = async () => {
    try {
      setIsDownloading(true);
      setProgress(0);
      setStartTime(Date.now());
      setStatusMessage("Sammle Dateien...");

      toast.info("Download wird vorbereitet", {
        description: "Die Bilder werden zusammengefasst. Dies kann einige Minuten dauern."
      });

      const response = await fetch(`/api/download-urls/${clientId}`);
      if (!response.ok) throw new Error('Failed to get download URLs');
      const { files } = await response.json();

      console.log(`Starting download of ${files.length} files...`);
      
      // Start the worker with all files
      workerRef.current?.postMessage({
        type: 'start',
        payload: { files }
      });

    } catch (error) {
      handleError(error.message);
    }
  };

  const formatTimeRemaining = (seconds: number) => {
    if (seconds < 60) return `${Math.round(seconds)} Sekunden`;
    const minutes = Math.ceil(seconds / 60);
    return `${minutes} ${minutes === 1 ? 'Minute' : 'Minuten'}`;
  };

  const getStatusMessage = () => {
    if (statusMessage) return statusMessage;
    if (!startTime) return "Wird vorbereitet...";
    if (progress >= 100) return "Download abgeschlossen!";
    
    const elapsedSeconds = (Date.now() - startTime) / 1000;
    if (progress > 0) {
      const estimatedTotalSeconds = (elapsedSeconds / progress) * 100;
      const remainingSeconds = Math.max(0, estimatedTotalSeconds - elapsedSeconds);
      return `Download läuft... ${progress.toFixed(1)}% (ca. ${formatTimeRemaining(remainingSeconds)} verbleibend)`;
    }
    
    return "Download startet...";
  };

  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      className="mb-12 rounded-lg border bg-card p-4 text-card-foreground shadow-sm"
    >
      <div className="flex flex-col items-center gap-4 text-center sm:flex-row sm:justify-between sm:text-left">
        <div>
          <h2 className="text-lg font-semibold">Alle Fotos herunterladen</h2>
          <p className="text-sm text-muted-foreground">
            Erhalten Sie alle Fotos in Originalqualität in einer einzigen Datei
          </p>
        </div>
        <div className="flex flex-col gap-2 min-w-[200px]">
          {isDownloading ? (
            <>
              <Progress value={progress} className="w-full" />
              <p className="text-sm text-muted-foreground text-center">
                {getStatusMessage()}
              </p>
            </>
          ) : (
            <Button size="lg" onClick={handleDownloadAll}>
              <Download className="mr-2 h-4 w-4" />
              Alle herunterladen
            </Button>
          )}
        </div>
      </div>
    </motion.div>
  );
}
Editor is loading...
Leave a Comment