1 Commits

Author SHA1 Message Date
Cursor Agent
86844de148 feat(adsx): add Historique page and AdsX navigation across screens
- Create historique.html: full send history with filters, pagination, CSV export
- Add AdsX nav bar to Dashboard, Send Page, Historique
- Add Historique to menu, wevads-tools, pages-index
- Add Dashboard/Historique/Send links to DELIVERADS index

Co-authored-by: Yacineutt <Yacineutt@users.noreply.github.com>
2026-03-06 00:57:58 +00:00
7 changed files with 313 additions and 0 deletions

View File

@@ -87,11 +87,21 @@ body { background:var(--bg); color:var(--text); font-family:'DM Sans',sans-serif
.footer { padding:10px 28px; border-top:1px solid var(--border); display:flex; justify-content:space-between; font-size:11px; color:#444; }
.hidden { display:none !important; }
.loading { padding:40px; text-align:center; color:var(--muted); }
.adsx-nav a:not(.active):hover { color:var(--accent) !important; background:rgba(59,130,246,.1) !important; }
</style>
<link rel="stylesheet" href="wevads-global.css?v1770777318">
</head>
<body>
<!-- AdsX Navigation -->
<nav class="adsx-nav" style="display:flex;gap:4px;padding:8px 28px;border-bottom:1px solid var(--border);background:var(--surface)">
<a href="dashboard.html" class="active" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--accent);background:rgba(59,130,246,.15);border:1px solid rgba(59,130,246,.3)">📊 Dashboard</a>
<a href="historique.html" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--muted);transition:all .15s">📜 Historique</a>
<a href="send-process.html" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--muted);transition:all .15s">🚀 Send Page</a>
<a href="/deliverads/" target="_blank" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--muted);transition:all .15s">🏠 DELIVERADS</a>
<a href="/control-hub.php" target="_blank" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--muted);transition:all .15s">🎛️ Control Hub</a>
</nav>
<div class="header">
<div>
<h1><span>WEVADS</span> / Multichannel Hub</h1>

282
public/historique.html Normal file
View File

@@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WEVADS — Historique des envois</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: #08080f; --surface: #111119; --border: rgba(255,255,255,0.06);
--text: #e8e8ec; --muted: #6b6b7b; --accent: #3b82f6;
--green: #10b981; --red: #ef4444; --amber: #f59e0b; --purple: #8b5cf6;
}
* { margin:0; padding:0; box-sizing:border-box; }
body { background:var(--bg); color:var(--text); font-family:'DM Sans',sans-serif; min-height:100vh; }
.mono { font-family:'JetBrains Mono',monospace; }
/* AdsX Nav */
.adsx-nav { display:flex; gap:4px; padding:8px 28px; border-bottom:1px solid var(--border); background:var(--surface); }
.adsx-nav a { padding:8px 16px; border-radius:6px; text-decoration:none; font-size:12px; color:var(--muted); transition:all .15s; }
.adsx-nav a:hover { color:var(--accent); background:rgba(59,130,246,.1); }
.adsx-nav a.active { color:var(--accent); background:rgba(59,130,246,.15); border:1px solid rgba(59,130,246,.3); }
/* Layout */
.header { padding:18px 28px; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:12px; }
.header h1 { font-size:20px; font-weight:700; }
.header h1 span { color:var(--accent); }
.header .sub { font-size:11px; color:var(--muted); margin-top:2px; letter-spacing:0.5px; }
/* Filters */
.filters { display:flex; gap:10px; padding:14px 28px; flex-wrap:wrap; align-items:center; }
.filters input, .filters select { padding:8px 12px; background:var(--surface); border:1px solid var(--border); border-radius:6px; color:var(--text); font-size:12px; }
.filters .btn { padding:8px 16px; border-radius:6px; border:1px solid var(--border); background:var(--surface); color:var(--text); cursor:pointer; font-size:12px; }
.filters .btn:hover { border-color:var(--accent); color:var(--accent); }
.filters .btn-export { border-color:var(--green); color:var(--green); }
.filters .btn-export:hover { background:rgba(16,185,129,.1); }
/* Stats */
.stats { display:flex; gap:10px; padding:14px 28px; overflow-x:auto; }
.stat { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:14px 18px; min-width:120px; flex:1; }
.stat .val { font-size:22px; font-weight:700; font-family:'JetBrains Mono'; }
.stat .lbl { font-size:11px; color:var(--muted); margin-top:2px; }
/* Table */
.table-wrap { padding:0 28px 20px; overflow-x:auto; }
.table-head { display:grid; grid-template-columns:40px 1.5fr 1fr 90px 80px 70px; gap:10px; padding:8px 0; font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:1px; border-bottom:1px solid var(--border); }
.rows { max-height:calc(100vh - 380px); overflow-y:auto; }
.row { display:grid; grid-template-columns:40px 1.5fr 1fr 90px 80px 70px; gap:10px; padding:10px 0; border-bottom:1px solid rgba(255,255,255,.02); cursor:pointer; transition:background .1s; align-items:center; }
.row:hover { background:rgba(255,255,255,.025); }
.ch-icon { width:30px; height:30px; border-radius:7px; display:flex; align-items:center; justify-content:center; font-size:14px; }
.subj { font-size:13px; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.from { font-size:11px; color:var(--muted); margin-top:1px; }
.to { font-size:12px; color:#888; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.badge { font-size:10px; padding:2px 8px; border-radius:4px; font-weight:600; text-transform:uppercase; letter-spacing:.3px; display:inline-block; }
.badge-sent { background:rgba(16,185,129,.15); color:var(--green); }
.badge-failed { background:rgba(239,68,68,.15); color:var(--red); }
.method { font-size:11px; color:var(--muted); }
.time { font-size:11px; color:#555; text-align:right; }
/* Pagination */
.pagination { display:flex; gap:6px; padding:14px 28px; align-items:center; }
.pagination button { padding:6px 12px; border-radius:5px; border:1px solid var(--border); background:var(--surface); color:var(--text); cursor:pointer; font-size:11px; }
.pagination button:hover { border-color:var(--accent); color:var(--accent); }
.pagination button:disabled { opacity:.4; cursor:not-allowed; }
.pagination span { font-size:11px; color:var(--muted); }
/* Modal */
.modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,.75); z-index:999; display:flex; align-items:center; justify-content:center; backdrop-filter:blur(4px); }
.modal { background:var(--surface); border:1px solid var(--border); border-radius:14px; width:92%; max-width:720px; max-height:88vh; display:flex; flex-direction:column; overflow:hidden; }
.modal-head { padding:14px 22px; border-bottom:1px solid var(--border); display:flex; justify-content:space-between; align-items:flex-start; }
.modal-head .label { font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:1px; }
.modal-head .title { font-size:16px; font-weight:600; margin-top:3px; }
.modal-close { background:rgba(255,255,255,.08); border:none; color:#fff; width:30px; height:30px; border-radius:7px; cursor:pointer; font-size:14px; }
.modal-meta { padding:10px 22px; border-bottom:1px solid rgba(255,255,255,.03); display:flex; gap:20px; font-size:12px; flex-wrap:wrap; }
.modal-meta span { color:var(--muted); }
.modal-meta b { color:#ccc; font-weight:500; }
.modal-body { flex:1; overflow:auto; background:#060a14; }
.modal-body iframe { width:100%; height:100%; min-height:450px; border:none; }
.modal-body .empty { padding:50px; text-align:center; color:#999; background:var(--surface); }
.modal-foot { padding:10px 22px; border-top:1px solid var(--border); font-size:12px; color:var(--muted); }
.footer { padding:10px 28px; border-top:1px solid var(--border); display:flex; justify-content:space-between; font-size:11px; color:#444; }
.hidden { display:none !important; }
.loading { padding:40px; text-align:center; color:var(--muted); }
</style>
<link rel="stylesheet" href="wevads-global.css?v1770777318">
</head>
<body>
<!-- AdsX Navigation -->
<nav class="adsx-nav">
<a href="dashboard.html">📊 Dashboard</a>
<a href="historique.html" class="active">📜 Historique</a>
<a href="send-process.html">🚀 Send Page</a>
<a href="/deliverads/" target="_blank">🏠 DELIVERADS</a>
<a href="/control-hub.php" target="_blank">🎛️ Control Hub</a>
</nav>
<div class="header">
<div>
<h1><span>WEVADS</span> / Historique des envois</h1>
<div class="sub">Historique complet · Email · SMS · Tous canaux</div>
</div>
<div style="font-size:11px;color:var(--muted);font-family:'JetBrains Mono',monospace" id="clock"></div>
</div>
<div class="filters">
<input type="text" id="search" placeholder="Rechercher email, sujet, offre, pays...">
<select id="filterMethod">
<option value="all">Tout</option>
<option value="brain">Production</option>
<option value="warmup">Warmup</option>
</select>
<select id="filterStatus">
<option value="all">Tous statuts</option>
<option value="sent">Envoyés</option>
<option value="failed">Échoués</option>
</select>
<button class="btn" onclick="loadData()">🔄 Actualiser</button>
<button class="btn btn-export" onclick="exportCSV()">📥 Export CSV</button>
<span style="font-size:11px;color:var(--muted);margin-left:auto" id="countLabel"></span>
</div>
<div class="stats" id="stats"></div>
<div class="table-wrap">
<div class="table-head">
<div></div><div>Sujet / Expéditeur</div><div>Destinataire</div><div>Statut</div><div>Méthode</div><div style="text-align:right">Date</div>
</div>
<div class="rows" id="rows"></div>
</div>
<div class="pagination" id="pagination">
<button id="prevBtn" onclick="prevPage()">← Précédent</button>
<span id="pageInfo">Page 1</span>
<button id="nextBtn" onclick="nextPage()">Suivant →</button>
</div>
<div class="footer">
<span id="footerInfo">Chargement...</span>
<span>WEVADS ADX · Port 5821</span>
</div>
<div id="modal" class="modal-overlay hidden"></div>
<script>
const API = '/api/send-log.php';
let allSends = [];
let currentFilter = 'all';
let currentStatus = 'all';
let currentPage = 1;
const PAGE_SIZE = 100;
const STATUS_COLORS = { sent:'var(--green)', failed:'var(--red)', bounced:'#666' };
const CH_ICONS = { bcg_local:['✉️','#3b82f6'], bcg_warmup:['🔥','#f59e0b'], pmta:['⚡','#8b5cf6'], bcg_relay:['📨','#10b981'] };
// Clock
setInterval(() => { const el = document.getElementById('clock'); if(el) el.textContent = new Date().toLocaleString('fr-FR'); }, 1000);
// Filters
document.getElementById('search').addEventListener('input', () => renderView());
document.getElementById('filterMethod').addEventListener('change', e => { currentFilter = e.target.value; renderView(); });
document.getElementById('filterStatus').addEventListener('change', e => { currentStatus = e.target.value; renderView(); });
function getFiltered() {
const q = document.getElementById('search').value.toLowerCase();
return allSends.filter(s => {
if (currentFilter === 'warmup' && s.send_method !== 'bcg_warmup') return false;
if (currentFilter === 'brain' && s.send_method === 'bcg_warmup') return false;
if (currentStatus === 'sent' && s.status !== 'sent') return false;
if (currentStatus === 'failed' && s.status === 'sent') return false;
if (q) return [s.subject,s.to_email,s.offer_name,s.from_email,s.country].join(' ').toLowerCase().includes(q);
return true;
});
}
function renderView() {
const filtered = getFiltered();
document.getElementById('countLabel').textContent = `${filtered.length} résultats`;
const totalPages = Math.ceil(filtered.length / PAGE_SIZE) || 1;
const start = (currentPage - 1) * PAGE_SIZE;
const pageData = filtered.slice(start, start + PAGE_SIZE);
renderTable(pageData);
document.getElementById('pageInfo').textContent = `Page ${currentPage} / ${totalPages}`;
document.getElementById('prevBtn').disabled = currentPage <= 1;
document.getElementById('nextBtn').disabled = currentPage >= totalPages;
}
function renderTable(sends) {
const el = document.getElementById('rows');
el.innerHTML = sends.map(s => {
const ch = CH_ICONS[s.send_method] || ['📧','#888'];
const t = new Date(s.created_at);
const time = t.toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'});
const date = t.toLocaleDateString('fr-FR',{month:'short',day:'numeric',year:'numeric'});
return `<div class="row" onclick="openPreview(${s.id})">
<div class="ch-icon" style="background:${ch[1]}18">${ch[0]}</div>
<div><div class="subj">${esc(s.subject || s.offer_name || '—')}</div><div class="from">${esc(s.from_email||'—')}</div></div>
<div class="to">→ ${esc(s.to_email||'—')}</div>
<div><span class="badge badge-${s.status}">${s.status}</span></div>
<div class="method">${s.send_method}</div>
<div class="time">${time}<br><span style="font-size:10px;color:#333">${date}</span></div>
</div>`;
}).join('');
}
function prevPage() { const filtered = getFiltered(); if (currentPage > 1) { currentPage--; renderView(); } }
function nextPage() { const filtered = getFiltered(); const totalPages = Math.ceil(filtered.length / PAGE_SIZE) || 1; if (currentPage < totalPages) { currentPage++; renderView(); } }
async function openPreview(id) {
const modal = document.getElementById('modal');
modal.classList.remove('hidden');
modal.innerHTML = '<div class="modal"><div class="loading" style="padding:60px">Chargement...</div></div>';
try {
const r = await fetch(API + '?action=preview&id=' + id);
const d = await r.json();
window._previewData = d;
const t = new Date(d.created_at);
modal.innerHTML = `<div class="modal">
<div class="modal-head">
<div><div class="label">${d.send_method} · ${d.status}</div><div class="title">${esc(d.subject||d.offer_name||'Sans sujet')}</div></div>
<button class="modal-close" onclick="closeModal()">✕</button>
</div>
<div class="modal-meta">
<div><span>De:</span> <b>${esc(d.from_email||'—')}</b></div>
<div><span>À:</span> <b>${esc(d.to_email||'—')}</b></div>
<div><span>Offre:</span> <b>${esc(d.offer_name||'Offer #'+d.offer_id)}</b></div>
<div><span>Pays:</span> <b>${esc(d.country||'—')}</b></div>
</div>
<div class="modal-body">${d.html_preview
? '<iframe srcdoc="'+esc(d.html_preview).replace(/"/g,'&quot;')+'"></iframe>'
: '<div class="empty"><div style="font-size:40px;margin-bottom:10px">📧</div>Aperçu HTML non disponible</div>'
}</div>
<div class="modal-foot">${t.toLocaleString('fr-FR')} · Tracking: ${esc(d.tracking_id||'—')}</div>
</div>`;
modal.onclick = e => { if (e.target === modal) closeModal(); };
} catch(e) {
modal.innerHTML = '<div class="modal"><div class="loading">Erreur chargement</div></div>';
}
}
function closeModal() { document.getElementById('modal').classList.add('hidden'); }
function esc(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function exportCSV() {
const filtered = getFiltered();
const headers = ['id','subject','from_email','to_email','status','send_method','country','created_at'];
const csv = [headers.join(';'), ...filtered.map(s => headers.map(h => (s[h]||'').toString().replace(/;/g,',')).join(';'))].join('\n');
const blob = new Blob(['\ufeff'+csv], { type: 'text/csv;charset=utf-8' });
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'wevads-historique-'+new Date().toISOString().slice(0,10)+'.csv'; a.click();
}
async function loadData() {
try {
const r = await fetch(API + '?limit=2000');
const data = await r.json();
allSends = data.sends || [];
currentPage = 1;
const statsEl = document.getElementById('stats');
let totalSent=0, totalFail=0;
(data.stats||[]).forEach(s => { totalSent += parseInt(s.ok||0); totalFail += parseInt(s.fail||0); });
statsEl.innerHTML = `
<div class="stat"><div class="val mono" style="color:#fff">${parseInt(data.total||0).toLocaleString()}</div><div class="lbl">Total envois</div></div>
<div class="stat"><div class="val mono" style="color:var(--green)">${totalSent}</div><div class="lbl">Livrés</div></div>
<div class="stat"><div class="val mono" style="color:var(--red)">${totalFail}</div><div class="lbl">Échoués</div></div>
<div class="stat"><div class="val mono" style="color:var(--accent)">${data.creatives_count||0}</div><div class="lbl">Creatives</div></div>
<div class="stat"><div class="val mono" style="color:var(--purple)">${data.offers_used||0}</div><div class="lbl">Offres utilisées</div></div>
`;
document.getElementById('footerInfo').textContent = `${data.total} envois · ${data.creatives_count} creatives`;
renderView();
} catch(e) {
document.getElementById('stats').innerHTML = '<div class="stat"><div class="val">⚠️</div><div class="lbl">Erreur API — vérifier connexion</div></div>';
}
}
loadData();
</script>
</body>
</html>

View File

@@ -18,6 +18,12 @@
<span class="title">Dashboard</span>
</a>
</li>
<li class="nav-item {if(isset($historique))}{echo 'active'}{/if}">
<a {if(IR\App\Helpers\Page::openPageInNewTab())} target="_blank" {/if} href="{echo $app['base_url']}/historique.html">
<i class="fa fa-history"></i>
<span class="title">Historique</span>
</a>
</li>
{/if}
<!-- ========== 🎛️ CONTROL HUB ========== -->

View File

@@ -27,6 +27,7 @@ table{width:100%;border-collapse:collapse;font-size:10px}th{text-align:left;colo
.sc:hover,.card:hover,[class*="stat-card"]:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.25)}
.sc::after,.card::after{content:'';position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--cy,#22d3ee);opacity:0;transition:opacity .25s}
.sc:hover::after,.card:hover::after{opacity:.7}
nav a:hover{color:var(--cy)!important;background:rgba(34,211,238,.1)!important}
.btn,.button,[class*="btn-"]{transition:all .2s ease}
.btn:hover,.button:hover{transform:translateY(-1px)}
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
@@ -41,6 +42,14 @@ table{width:100%;border-collapse:collapse;font-size:10px}th{text-align:left;colo
<link rel="stylesheet" href="wevads-global.css?v1770777318">
</head><body>
<!-- AdsX Navigation -->
<nav style="display:flex;gap:4px;padding:8px 20px;border-bottom:1px solid var(--b);background:var(--s)">
<a href="dashboard.html" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--d)">📊 Dashboard</a>
<a href="historique.html" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--d)">📜 Historique</a>
<a href="send-process.html" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--rd);background:rgba(248,113,113,.15);border:1px solid rgba(248,113,113,.3)">🚀 Send Page</a>
<a href="/deliverads/" target="_blank" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--d)">🏠 DELIVERADS</a>
<a href="/control-hub.php" target="_blank" style="padding:8px 16px;border-radius:6px;text-decoration:none;font-size:12px;color:var(--d)">🎛️ Control Hub</a>
</nav>
<div class="hdr"><div><h1>🚀 WEVADS • <span>Production Send Process</span></h1>
<p style="font-size:12px;color:#64748b;margin:6px 0 16px;max-width:600px;line-height:1.6">Processus d'envoi — pipeline recipient → ISP detect → config → send → log.</p><span style="font-size:10px;color:var(--d)">Sélection serveurs PMTA → VMTAs → Lancement envoi production</span></div><div style="display:flex;gap:8px;align-items:center"><span class="badge badge-gn">● PRODUCTION</span><span style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--d)" id="clock"></span></div></div>

View File

@@ -21,6 +21,7 @@ $nativePages = [
'color' => '#22d3ee',
'pages' => [
['url' => '/dashboard.html', 'name' => 'Dashboard Principal', 'desc' => 'Vue d\'ensemble'],
['url' => '/historique.html', 'name' => 'Historique des envois', 'desc' => 'Historique WEVADS AdsX'],
['url' => '/index.php', 'name' => 'Index', 'desc' => 'Point d\'entrée MVC'],
['url' => '/index.php?controller=Dashboard', 'name' => 'Dashboard Controller', 'desc' => 'Contrôleur dashboard'],
]

View File

@@ -51,6 +51,9 @@ foreach($m as $x):?>
<?php endforeach;?>
</div>
<div class="mt-4 text-center text-xs">
<a href="/dashboard.html" class="text-cyan-400" target="_blank">📊 Dashboard</a> |
<a href="/historique.html" class="text-cyan-400" target="_blank">📜 Historique</a> |
<a href="/send-process.html" class="text-cyan-400" target="_blank">🚀 Send</a> |
<a href="/ceo-deliverads.php" class="text-yellow-400">👔 CEO</a> |
<a href="/hamid-fullscreen.php" class="text-purple-400">🤖 HAMID</a> |
<a href="/office-management.php" class="text-blue-400">📧 O365</a> |

View File

@@ -19,6 +19,8 @@ $wevads_tools = [
['name' => 'MTA Servers', 'url' => '/mta-servers.html', 'icon' => 'fa-server', 'color' => 'red'],
['name' => 'VMTAs', 'url' => '/vmtas.html', 'icon' => 'fa-network-wired', 'color' => 'pink'],
['name' => 'Dashboard', 'url' => '/dashboard.html', 'icon' => 'fa-chart-line', 'color' => 'teal'],
['name' => 'Historique', 'url' => '/historique.html', 'icon' => 'fa-history', 'color' => 'teal'],
['name' => 'Send Page', 'url' => '/send-process.html', 'icon' => 'fa-paper-plane', 'color' => 'teal'],
],
];
?>