+
+
+
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.
}
+
+
+
+
+
+
+
+
+
+ | Produto |
+ Status |
+ Delta |
+ Itens |
+ Tentativas |
+ Atualizado |
+ Ações |
+
+
+
+ {(summary?.groups || []).map(group => {
+ const Icon = statusIcons[group.status];
+ return (
+
+ |
+ {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;
diff --git a/src/types.ts b/src/types.ts
index ea2cc00..51e0d8c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -23,3 +23,74 @@ export interface DateRange {
start: Date;
end: Date;
}
+
+export type CampaignStatus = 'pending' | 'processing' | 'sent' | 'failed' | 'skipped';
+
+export interface CampaignQueueItem {
+ id: number;
+ base_product_name: string;
+ produto_id: string;
+ nome: string;
+ saldo: number;
+ delta_estoque: number;
+ status: CampaignStatus;
+ attempts: number;
+ last_error?: string | null;
+ created_at: string;
+ updated_at: string;
+ sent_at?: string | null;
+}
+
+export interface CampaignGroup {
+ key: string;
+ baseProductName: string;
+ status: CampaignStatus;
+ totalDelta: number;
+ rowCount: number;
+ attempts: number;
+ lastError?: string | null;
+ createdAt: string;
+ updatedAt: string;
+ sentAt?: string | null;
+ items: CampaignQueueItem[];
+}
+
+export interface CampaignQueueSummary {
+ threshold: number;
+ maxAttempts: number;
+ groups: CampaignGroup[];
+ rows: CampaignQueueItem[];
+}
+
+export interface CampaignProductPreview {
+ baseProduct: string;
+ total_delta: number;
+ sizes: Array<{
+ id: string;
+ nome: string;
+ delta: number;
+ saldo: number;
+ }>;
+}
+
+export interface CampaignPreview {
+ threshold: number;
+ readyProducts: CampaignProductPreview[];
+ belowThresholdProducts: CampaignProductPreview[];
+ productsText: string;
+ customerCount: number;
+ customersPreview: Array<{
+ nome: string;
+ fone: string;
+ total_gasto?: string;
+ total_comprado?: string;
+ }>;
+}
+
+export interface CampaignProcessSummary {
+ claimed: number;
+ sentGroups: number;
+ skippedGroups: number;
+ failedGroups: number;
+ pendingBelowThresholdGroups: number;
+}