metaads-pro/src/app/dashboard/activity/page.tsx

152 lines
7 KiB
TypeScript
Raw Normal View History

'use client';
import React, { useState } from 'react';
import { Activity, CheckCircle, Clock, XCircle, Zap, Filter } from 'lucide-react';
interface LogEntry {
id: string;
timestamp: string;
campaign_name: string;
rule_name: string;
action: string;
details: string;
status: 'executed' | 'pending' | 'failed' | 'skipped';
}
// Demo data (will be replaced with Supabase query)
const DEMO_LOGS: LogEntry[] = [
{
id: '1', timestamp: new Date(Date.now() - 3600000).toISOString(),
campaign_name: 'Campanha Conversão Q1', rule_name: 'CPL Alto — Pausar',
action: 'pause', details: 'CPL = R$67.30 (threshold: R$50.00)', status: 'executed',
},
{
id: '2', timestamp: new Date(Date.now() - 7200000).toISOString(),
campaign_name: 'Remarketing Visitantes', rule_name: 'ROAS Baixo — Pausar',
action: 'pause', details: 'ROAS = 0.32x (threshold: 0.50x)', status: 'executed',
},
{
id: '3', timestamp: new Date(Date.now() - 14400000).toISOString(),
campaign_name: 'Tráfego Blog', rule_name: 'CTR Muito Baixo — Pausar',
action: 'pause', details: 'CTR = 0.21% (threshold: 0.50%)', status: 'executed',
},
{
id: '4', timestamp: new Date(Date.now() - 28800000).toISOString(),
campaign_name: 'Leads Premium', rule_name: 'ROAS Excelente — Aumentar Budget',
action: 'increase_budget', details: 'Budget +20%: R$50.00 → R$60.00. ROAS = 4.2x', status: 'executed',
},
{
id: '5', timestamp: new Date(Date.now() - 43200000).toISOString(),
campaign_name: 'Campanha Vídeo', rule_name: 'CPC Alto — Reduzir Budget',
action: 'reduce_budget', details: 'Em cooldown (48h)', status: 'skipped',
},
];
const statusConfig = {
executed: { icon: <CheckCircle size={14} />, color: '#4ade80', bg: 'rgba(34,197,94,0.1)', label: 'Executado' },
pending: { icon: <Clock size={14} />, color: '#fbbf24', bg: 'rgba(245,158,11,0.1)', label: 'Pendente' },
failed: { icon: <XCircle size={14} />, color: '#f87171', bg: 'rgba(239,68,68,0.1)', label: 'Falhou' },
skipped: { icon: <Clock size={14} />, color: '#8888a0', bg: 'var(--overlay-05)', label: 'Pulado' },
};
function timeAgo(dateStr: string): string {
const diff = Date.now() - new Date(dateStr).getTime();
const mins = Math.floor(diff / 60000);
if (mins < 60) return `${mins}min atrás`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h atrás`;
const days = Math.floor(hours / 24);
return `${days}d atrás`;
}
export default function ActivityPage() {
const [filter, setFilter] = useState<string>('all');
const logs = filter === 'all' ? DEMO_LOGS : DEMO_LOGS.filter((l) => l.status === filter);
return (
<div className="flex flex-col gap-6">
{/* Header */}
<div className="flex items-center justify-between animate-fade-in-up">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'rgba(34,197,94,0.15)', border: '1px solid rgba(34,197,94,0.25)' }}>
<Activity size={20} style={{ color: '#4ade80' }} />
</div>
<div>
<h1 className="text-2xl font-bold text-white">Atividade</h1>
<p className="text-sm" style={{ color: 'var(--muted-fg)' }}>Ações executadas pelo agente de automação</p>
</div>
</div>
<div className="flex items-center gap-1 p-1 rounded-xl" style={{ background: 'var(--overlay-03)', border: '1px solid var(--overlay-06)' }}>
<Filter size={14} className="ml-2" style={{ color: 'var(--muted-fg)' }} />
{['all', 'executed', 'pending', 'skipped', 'failed'].map((f) => (
<button key={f} onClick={() => setFilter(f)} className="px-3 py-1.5 rounded-lg text-xs font-medium transition-all"
style={{ background: filter === f ? 'var(--accent)' : 'transparent', color: filter === f ? 'white' : 'var(--muted-fg)' }}>
{f === 'all' ? 'Todos' : statusConfig[f as keyof typeof statusConfig].label}
</button>
))}
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-4 gap-4 animate-fade-in-up delay-1">
{[
{ label: 'Total', value: DEMO_LOGS.length, color: 'var(--accent-soft-fg)' },
{ label: 'Executados', value: DEMO_LOGS.filter((l) => l.status === 'executed').length, color: '#4ade80' },
{ label: 'Pendentes', value: DEMO_LOGS.filter((l) => l.status === 'pending').length, color: '#fbbf24' },
{ label: 'Pulados', value: DEMO_LOGS.filter((l) => l.status === 'skipped').length, color: '#8888a0' },
].map((s) => (
<div key={s.label} className="glass-card-static p-4 text-center">
<p className="text-2xl font-bold" style={{ color: s.color }}>{s.value}</p>
<p className="text-xs mt-1" style={{ color: 'var(--muted-fg)' }}>{s.label}</p>
</div>
))}
</div>
{/* Timeline */}
<div className="glass-card-static overflow-hidden animate-fade-in-up delay-2">
<div className="p-5 border-b" style={{ borderColor: 'var(--overlay-06)' }}>
<h2 className="text-base font-semibold text-white">Timeline</h2>
</div>
<div className="divide-y" style={{ borderColor: 'var(--overlay-04)' }}>
{logs.length === 0 ? (
<div className="p-12 text-center">
<p className="text-[var(--muted-fg)]">Nenhuma atividade encontrada</p>
</div>
) : (
logs.map((log) => {
const sc = statusConfig[log.status];
return (
<div key={log.id} className="flex items-start gap-4 p-5 hover:bg-white/[0.02] transition-colors">
{/* Timeline dot */}
<div className="mt-1 flex flex-col items-center">
<div className="w-8 h-8 rounded-full flex items-center justify-center" style={{ background: sc.bg }}>
<Zap size={14} style={{ color: sc.color }} />
</div>
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<p className="font-medium text-white text-sm">{log.campaign_name}</p>
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium" style={{ background: sc.bg, color: sc.color }}>
{sc.icon} {sc.label}
</span>
</div>
<p className="text-xs" style={{ color: 'var(--muted-fg)' }}>
Regra: <span className="text-white">{log.rule_name}</span>
</p>
<p className="text-xs mt-1" style={{ color: 'var(--muted-fg)' }}>{log.details}</p>
</div>
{/* Time */}
<span className="text-xs whitespace-nowrap" style={{ color: 'var(--muted-fg)' }}>
{timeAgo(log.timestamp)}
</span>
</div>
);
})
)}
</div>
</div>
</div>
);
}