import { useEffect, useMemo, useState } from 'react'; import { AlertTriangle, CheckCircle2, Clock, Megaphone, RefreshCw, RotateCcw, Send, XCircle } from 'lucide-react'; import type { CampaignGroup, CampaignPreview, CampaignProcessSummary, CampaignQueueSummary, CampaignStatus } from '../types'; import { fetchCampaignPreview, fetchCampaigns, processCampaignsNow, retryCampaignGroup } from '../dataService'; const statusLabels: Record = { pending: 'Pendente', processing: 'Processando', sent: 'Enviada', failed: 'Falhou', skipped: 'Ignorada' }; const statusStyles: Record = { pending: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20', processing: 'bg-blue-500/10 text-blue-400 border-blue-500/20', sent: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20', failed: 'bg-red-500/10 text-red-400 border-red-500/20', skipped: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20' }; const statusIcons: Record = { pending: Clock, processing: RefreshCw, sent: CheckCircle2, failed: XCircle, skipped: AlertTriangle }; const formatDate = (value?: string | null) => { if (!value) return '-'; return new Date(value).toLocaleString('pt-BR'); }; const formatDelta = (value: number) => `${value} un.`; const Campaigns = () => { const [summary, setSummary] = useState(null); const [preview, setPreview] = useState(null); const [processResult, setProcessResult] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isProcessing, setIsProcessing] = useState(false); const loadCampaigns = async () => { setIsLoading(true); const [campaignsData, previewData] = await Promise.all([ fetchCampaigns(), fetchCampaignPreview() ]); setSummary(campaignsData); setPreview(previewData); setIsLoading(false); }; useEffect(() => { // Campaign state is loaded from the backend after the protected route mounts. // eslint-disable-next-line react-hooks/set-state-in-effect void loadCampaigns(); }, []); const groupedCounts = useMemo(() => { const counts: Record = { pending: 0, processing: 0, sent: 0, failed: 0, skipped: 0 }; summary?.groups.forEach(group => { counts[group.status] += 1; }); return counts; }, [summary]); const handleProcessNow = async () => { setIsProcessing(true); const result = await processCampaignsNow(); setProcessResult(result); await loadCampaigns(); setIsProcessing(false); }; const handleRetry = async (group: CampaignGroup) => { setIsProcessing(true); await retryCampaignGroup(group.baseProductName); await loadCampaigns(); setIsProcessing(false); }; return (

Campanhas

Fila de reposição, prévia de envio e histórico das campanhas do WhatsApp.

{processResult && (
Resultado: {processResult.claimed} itens processados, {processResult.sentGroups} grupos enviados, {processResult.failedGroups} falhas, {processResult.pendingBelowThresholdGroups} abaixo do limite.
)}
{Object.entries(groupedCounts).map(([status, count]) => { const typedStatus = status as CampaignStatus; const Icon = statusIcons[typedStatus]; return (
{statusLabels[typedStatus]}

{count}

); })}

Prévia do próximo envio

Produtos prontos

{preview?.productsText || 'Nenhum produto atingiu o limite ainda.'}

Clientes alvo

{preview?.customerCount ?? 0}

Limite por produto

{summary?.threshold ?? preview?.threshold ?? 100}

{(preview?.readyProducts || []).map(product => (
{product.baseProduct} {formatDelta(product.total_delta)}
))} {(preview?.belowThresholdProducts || []).map(product => (
{product.baseProduct} {formatDelta(product.total_delta)}
))}

Top clientes da campanha

{(preview?.customersPreview || []).map(customer => (

{customer.nome}

{customer.fone}

{customer.total_comprado || 0} un.
))} {!preview?.customersPreview.length &&

Nenhum cliente com telefone válido encontrado.

}
{(summary?.groups || []).map(group => { const Icon = statusIcons[group.status]; return ( ); })}
Produto Status Delta Itens Tentativas Atualizado Ações

{group.baseProductName}

{group.lastError &&

{group.lastError}

}
{statusLabels[group.status]} {formatDelta(group.totalDelta)} {group.rowCount} {group.attempts} {formatDate(group.updatedAt)} {(group.status === 'failed' || group.status === 'skipped') && ( )}
{!summary?.groups.length && !isLoading && (
Nenhuma campanha registrada ainda.
)}
); }; export default Campaigns;