From 1e511f35a938a4f6d73fff6e7dcbf00979b972be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Tue, 30 Jun 2026 10:36:22 -0300 Subject: [PATCH] Constrain campaign preview list --- src/pages/Campaigns.tsx | 67 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/src/pages/Campaigns.tsx b/src/pages/Campaigns.tsx index 93baff6..fbca03f 100644 --- a/src/pages/Campaigns.tsx +++ b/src/pages/Campaigns.tsx @@ -19,6 +19,8 @@ type StatusVisual = { text: string; }; +type PreviewFilter = 'all' | 'ready' | 'near' | 'below'; + const statusStyles: Record = { pending: { accent: '#FFC247', bg: 'rgba(255, 194, 71, 0.10)', border: 'rgba(255, 194, 71, 0.28)', text: '#FFC247' }, processing: { accent: '#25C2FF', bg: 'rgba(37, 194, 255, 0.10)', border: 'rgba(37, 194, 255, 0.28)', text: '#25C2FF' }, @@ -42,6 +44,8 @@ const formatDate = (value?: string | null) => { const formatDelta = (value: number) => `${value} un.`; +const NEAR_THRESHOLD_PERCENT = 80; + const statusPillStyle = (status: CampaignStatus) => { const style = statusStyles[status]; return { @@ -108,6 +112,7 @@ const Campaigns = () => { const [processResult, setProcessResult] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isProcessing, setIsProcessing] = useState(false); + const [previewFilter, setPreviewFilter] = useState('all'); const loadCampaigns = async () => { setIsLoading(true); @@ -147,6 +152,37 @@ const Campaigns = () => { return [...ready, ...below].sort((a, b) => Number(b.isReady) - Number(a.isReady) || b.total_delta - a.total_delta); }, [preview]); + const previewProductRows = useMemo(() => { + return previewProducts.map(product => { + const progress = threshold ? Math.min(100, (product.total_delta / threshold) * 100) : 0; + return { + ...product, + progress, + isNear: !product.isReady && progress >= NEAR_THRESHOLD_PERCENT + }; + }); + }, [previewProducts, threshold]); + + const filteredPreviewProducts = useMemo(() => { + switch (previewFilter) { + case 'ready': + return previewProductRows.filter(product => product.isReady); + case 'near': + return previewProductRows.filter(product => product.isNear); + case 'below': + return previewProductRows.filter(product => !product.isReady); + default: + return previewProductRows; + } + }, [previewFilter, previewProductRows]); + + const previewFilterOptions = useMemo(() => [ + { key: 'all' as const, label: 'Todos', count: previewProductRows.length }, + { key: 'ready' as const, label: 'Prontos', count: previewProductRows.filter(product => product.isReady).length }, + { key: 'near' as const, label: 'Quase prontos', count: previewProductRows.filter(product => product.isNear).length }, + { key: 'below' as const, label: 'Abaixo', count: previewProductRows.filter(product => !product.isReady).length } + ], [previewProductRows]); + const resultStats = processResult ? [ { label: 'Processados', value: processResult.claimed, color: '#25C2FF' }, { label: 'Enviados', value: processResult.sentGroups, color: '#52DFA0' }, @@ -264,9 +300,26 @@ const Campaigns = () => {

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

-
- {previewProducts.map(product => { - const progress = threshold ? Math.min(100, (product.total_delta / threshold) * 100) : 0; +
+ {previewFilterOptions.map(option => ( + + ))} +
+ +
+ {filteredPreviewProducts.map(product => { const accent = product.isReady ? statusStyles.sent.accent : statusStyles.pending.accent; return ( @@ -287,18 +340,18 @@ const Campaigns = () => { className="rounded-full border px-2.5 py-1 text-[11px] font-bold" style={product.isReady ? statusPillStyle('sent') : statusPillStyle('pending')} > - {product.isReady ? 'Pronto' : `${Math.round(progress)}%`} + {product.isReady ? 'Pronto' : `${Math.round(product.progress)}%`}
-
+
); })} - {!previewProducts.length && ( + {!filteredPreviewProducts.length && (
- Nenhum produto para campanha no momento. + Nenhum produto nesta visualização.
)}