import { useMutation, useQuery } from "@tanstack/react-query"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Clapperboard, Loader2, CheckCircle2, XCircle, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; import { apiRequest, queryClient, getErrorMessage } from "@/lib/queryClient"; import type { Post } from "@shared/schema"; type OngoingReel = Post & { generationStep?: string | null }; /** Libellé de l'étape en cours, telle que rapportée par la file de rendu. */ const STEP_LABELS: Record = { prepare: "Préparation…", voice: "Voix et préparation de la vidéo…", render: "Montage : sous-titres, logo, effets…", store: "Enregistrement dans la médiathèque…", publish: "Publication…", }; function getStageLabel(post: OngoingReel): string { if (post.generationStatus === "pending") return "En attente : un autre Reel est en cours…"; return (post.generationStep && STEP_LABELS[post.generationStep]) || "Démarrage du traitement…"; } interface OngoingReelsProps { /** Compact layout for mobile */ compact?: boolean; } export default function OngoingReels({ compact = false }: OngoingReelsProps) { const { data: ongoingPosts = [] } = useQuery({ queryKey: ["/api/reels/ongoing"], // Cette route ne renvoie que les générations en cours : tant qu'elle est // vide il n'y a pas de barre de progression à animer. On garde une veille // lente, qui suffit à détecter une génération lancée depuis un autre écran. refetchInterval: (query) => (query.state.data ?? []).some((p) => p.generationStatus !== "failed") ? 3000 : 30000, }); const { toast } = useToast(); // Un Reel en échec n'a rien à publier : le supprimer retire aussi la notification const dismissMutation = useMutation({ mutationFn: async (postId: string) => (await apiRequest("DELETE", `/api/reels/${postId}`)).json(), onSuccess: (_data, postId) => { queryClient.setQueryData(["/api/reels/ongoing"], (posts) => posts?.filter((p) => p.id !== postId), ); queryClient.invalidateQueries({ queryKey: ["/api/reels/ongoing"] }); queryClient.invalidateQueries({ queryKey: ["/api/scheduled-posts"] }); }, onError: (error: unknown) => { toast({ title: "Erreur", description: getErrorMessage(error, "Impossible de supprimer la notification"), variant: "destructive", }); }, }); // Nothing to show — render nothing (not even a card) if (ongoingPosts.length === 0) { return null; } return ( {ongoingPosts.some((p) => p.generationStatus !== "failed") ? ( ) : ( )} Reels en cours
{ongoingPosts.map((post) => { const progress = post.generationProgress ?? 0; const isFailed = post.generationStatus === "failed"; return (
{/* Icon */}
{/* Content */}

{post.content || "Reel sans texte"}

{isFailed ? (
Échec : {post.generationError || "erreur lors du traitement"}
) : ( <>
{getStageLabel(post)} {progress}%
)} {progress >= 100 && !isFailed && (
Terminé
)}
{isFailed && ( )}
); })}
); }