Constrain campaign preview list
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m38s

This commit is contained in:
Cauê Faleiros
2026-06-30 10:36:22 -03:00
parent d66aa77f4d
commit 1e511f35a9

View File

@@ -19,6 +19,8 @@ type StatusVisual = {
text: string;
};
type PreviewFilter = 'all' | 'ready' | 'near' | 'below';
const statusStyles: Record<CampaignStatus, StatusVisual> = {
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<CampaignProcessSummary | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isProcessing, setIsProcessing] = useState(false);
const [previewFilter, setPreviewFilter] = useState<PreviewFilter>('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 = () => {
<p className="text-xl font-bold text-dark-text">{summary?.threshold ?? preview?.threshold ?? 100}</p>
</div>
</div>
<div className="space-y-2">
{previewProducts.map(product => {
const progress = threshold ? Math.min(100, (product.total_delta / threshold) * 100) : 0;
<div className="flex flex-wrap gap-2">
{previewFilterOptions.map(option => (
<button
key={option.key}
type="button"
onClick={() => setPreviewFilter(option.key)}
className={`inline-flex h-8 cursor-pointer items-center gap-2 rounded-lg border px-3 text-xs font-bold transition-colors ${
previewFilter === option.key
? 'border-brand-primary bg-brand-primary/10 text-dark-text'
: 'border-dark-border bg-dark-input/45 text-dark-muted hover:text-dark-text'
}`}
>
{option.label}
<span className="rounded-full bg-dark-card px-1.5 py-0.5 text-[10px] text-dark-muted">{option.count}</span>
</button>
))}
</div>
<div className="max-h-[34rem] space-y-2 overflow-y-auto pr-1">
{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)}%`}
</span>
</div>
<div className="mt-3 h-1.5 overflow-hidden rounded-full bg-dark-border/70">
<div className="h-full rounded-full" style={{ width: `${progress}%`, backgroundColor: accent }} />
<div className="h-full rounded-full" style={{ width: `${product.progress}%`, backgroundColor: accent }} />
</div>
</div>
);
})}
{!previewProducts.length && (
{!filteredPreviewProducts.length && (
<div className="rounded-xl border border-dark-border bg-dark-input/45 p-4 text-sm font-semibold text-dark-muted">
Nenhum produto para campanha no momento.
Nenhum produto nesta visualização.
</div>
)}
</div>