249 lines
13 KiB
HTML
249 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>WEVAL — LinkedIn Posts Manager</title>
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:-apple-system,system-ui,sans-serif;background:#0d0f13;color:#e8e9ed;min-height:100vh;padding:20px}
|
||
h1{font-size:1.4rem;margin-bottom:4px}
|
||
.sub{color:#6b7e;font-size:.82rem;margin-bottom:20px}
|
||
.top{display:flex;gap:12px;align-items:center;margin-bottom:20px;flex-wrap:wrap}
|
||
.btn{padding:8px 18px;border:none;border-radius:8px;font-weight:600;font-size:.82rem;cursor:pointer}
|
||
.btn-amber{background:#f5a623;color:#0d0f13}
|
||
.btn-blue{background:#3b82f6;color:#fff}
|
||
.btn-green{background:#22c55e;color:#fff}
|
||
.btn-red{background:#ef4444;color:#fff}
|
||
.btn-gray{background:#2a2d38;color:#a0a3ae}
|
||
.stats{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap}
|
||
.stat{background:#14161c;border:1px solid #2a2d38;border-radius:10px;padding:12px 18px}
|
||
.stat-n{font-size:1.5rem;font-weight:700;color:#f5a623}.stat-l{font-size:.7rem;color:#6b7e;margin-top:2px}
|
||
table{width:100%;border-collapse:collapse;background:#14161c;border-radius:12px;overflow:hidden}
|
||
th{background:#1a1d25;padding:10px 12px;font-size:.72rem;text-transform:uppercase;color:#6b7e;text-align:left;font-weight:600}
|
||
td{padding:10px 12px;border-top:1px solid #1a1d25;font-size:.82rem}
|
||
tr:hover td{background:#1a1d25}
|
||
.img-thumb{width:50px;height:35px;object-fit:cover;border-radius:4px}
|
||
input,textarea,select{background:#1a1d25;border:1px solid #2a2d38;border-radius:6px;padding:8px 10px;color:#e8e9ed;font-size:.82rem;font-family:inherit}
|
||
input:focus,textarea:focus{border-color:#f5a623;outline:none}
|
||
.form-row{display:flex;gap:10px;margin-bottom:10px;align-items:center;flex-wrap:wrap}
|
||
.form-row label{width:100px;font-size:.75rem;color:#6b7e;flex-shrink:0}
|
||
.form-row input,.form-row textarea,.form-row select{flex:1;min-width:200px}
|
||
.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:100;align-items:center;justify-content:center}
|
||
.modal.open{display:flex}
|
||
.modal-box{background:#14161c;border:1px solid #2a2d38;border-radius:12px;padding:24px;width:600px;max-width:95vw;max-height:90vh;overflow-y:auto}
|
||
.modal h2{font-size:1.1rem;margin-bottom:16px}
|
||
.tag{display:inline-block;font-size:.65rem;padding:2px 8px;border-radius:4px;font-weight:600}
|
||
.tag-w{background:rgba(59,130,246,.15);color:#3b82f6}
|
||
.tag-l{background:rgba(16,185,129,.15);color:#10b981}
|
||
.toast{position:fixed;bottom:20px;right:20px;background:#22c55e;color:#fff;padding:12px 20px;border-radius:8px;font-weight:600;display:none;z-index:200}
|
||
input,select,textarea{background:#0b0d14!important;color:#e2e8f0!important;border:1px solid #1e293b!important;border-radius:8px!important}input::placeholder{color:#475569!important}</style>
|
||
<link rel="stylesheet" href="/assets/dark-iframe.css"></head>
|
||
<body>
|
||
<h1>📰 LinkedIn Posts Manager</h1>
|
||
<div class="sub">Gérez vos publications LinkedIn — les stats se mettent à jour en temps réel sur la page Actualités</div>
|
||
|
||
<div class="top">
|
||
<button class="btn btn-amber" onclick="openAdd()">➕ Ajouter un post</button>
|
||
<button class="btn btn-blue" onclick="openImport()">🔗 Importer depuis URL</button>
|
||
<button class="btn btn-gray" onclick="loadPosts()">🔄 Rafraîchir</button>
|
||
<span style="margin-left:auto;font-size:.75rem;color:#6b7e" id="last-update"></span>
|
||
</div>
|
||
|
||
<div class="stats" id="stats"></div>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Image</th>
|
||
<th>Date</th>
|
||
<th>Titre</th>
|
||
<th>Source</th>
|
||
<th>👍</th>
|
||
<th>💬</th>
|
||
<th>🔁</th>
|
||
<th>👁</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="posts-table"></tbody>
|
||
</table>
|
||
|
||
<!-- Add/Edit Modal -->
|
||
<div class="modal" id="modal-add">
|
||
<div class="modal-box">
|
||
<h2 id="modal-title">➕ Ajouter un post LinkedIn</h2>
|
||
<input type="hidden" id="edit-id">
|
||
<div class="form-row"><label>URL LinkedIn</label><input type="text" id="f-url" placeholder="https://www.linkedin.com/feed/update/urn:li:activity:..." oninput="parseUrl(this.value)"></div>
|
||
<div class="form-row"><label>Date</label><input type="date" id="f-date"></div>
|
||
<div class="form-row"><label>Titre</label><input type="text" id="f-title" placeholder="Titre du post"></div>
|
||
<div class="form-row"><label>Description</label><textarea id="f-excerpt" rows="2" placeholder="Texte court du post"></textarea></div>
|
||
<div class="form-row"><label>Image</label><input type="text" id="f-image" placeholder="/uploads/actualites/mon-image.jpg"></div>
|
||
<div class="form-row"><label>Source</label><select id="f-source"><option value="W">WEVAL</option><option value="L">Life Sciences</option></select></div>
|
||
<div class="form-row"><label>Likes</label><input type="number" id="f-likes" value="0" min="0"></div>
|
||
<div class="form-row"><label>Comments</label><input type="number" id="f-comments" value="0" min="0"></div>
|
||
<div class="form-row"><label>Reposts</label><input type="number" id="f-reposts" value="0" min="0"></div>
|
||
<div class="form-row"><label>Vues</label><input type="number" id="f-views" value="0" min="0"></div>
|
||
<div style="display:flex;gap:8px;margin-top:16px">
|
||
<button class="btn btn-green" onclick="savePost()">💾 Enregistrer</button>
|
||
<button class="btn btn-gray" onclick="closeModal('modal-add')">Annuler</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Import Modal -->
|
||
<div class="modal" id="modal-import">
|
||
<div class="modal-box">
|
||
<h2>🔗 Importer depuis un lien LinkedIn</h2>
|
||
<p style="font-size:.82rem;color:#6b7e;margin-bottom:12px">Collez le lien de votre post LinkedIn. Les infos seront pré-remplies.</p>
|
||
<div class="form-row"><label>URL</label><input type="text" id="import-url" placeholder="https://www.linkedin.com/feed/update/..." style="flex:2"></div>
|
||
<div class="form-row"><label>Titre</label><input type="text" id="import-title" placeholder="Titre du post (copié depuis LinkedIn)"></div>
|
||
<div class="form-row"><label>Description</label><textarea id="import-excerpt" rows="2" placeholder="Première phrase du post"></textarea></div>
|
||
<div style="display:flex;gap:8px;margin-top:16px">
|
||
<button class="btn btn-blue" onclick="importPost()">📥 Importer</button>
|
||
<button class="btn btn-gray" onclick="closeModal('modal-import')">Annuler</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<script>
|
||
const API='/api/linkedin-posts.php';
|
||
let allPosts=[];
|
||
|
||
function toast(msg){const t=document.getElementById('toast');t.textContent=msg;t.style.display='block';setTimeout(()=>t.style.display='none',3000)}
|
||
|
||
async function loadPosts(){
|
||
const d=await fetch(API+'?action=list').then(r=>r.json());
|
||
allPosts=d.posts||[];
|
||
document.getElementById('last-update').textContent='Mis à jour: '+new Date().toLocaleTimeString();
|
||
|
||
// Stats
|
||
const total=allPosts.length;
|
||
const totalLikes=allPosts.reduce((s,p)=>s+parseInt(p.likes||0),0);
|
||
const totalViews=allPosts.reduce((s,p)=>s+parseInt(p.views||0),0);
|
||
const weval=allPosts.filter(p=>p.source==='W').length;
|
||
const ls=allPosts.filter(p=>p.source==='L').length;
|
||
document.getElementById('stats').innerHTML=`
|
||
<div class="stat"><div class="stat-n">${total}</div><div class="stat-l">Posts</div></div>
|
||
<div class="stat"><div class="stat-n">${totalLikes}</div><div class="stat-l">Total Likes</div></div>
|
||
<div class="stat"><div class="stat-n">${totalViews>999?(totalViews/1000).toFixed(1)+'K':totalViews}</div><div class="stat-l">Total Vues</div></div>
|
||
<div class="stat"><div class="stat-n">${weval}</div><div class="stat-l">WEVAL</div></div>
|
||
<div class="stat"><div class="stat-n">${ls}</div><div class="stat-l">Life Sciences</div></div>`;
|
||
|
||
// Table
|
||
const tb=document.getElementById('posts-table');
|
||
tb.innerHTML=allPosts.map(p=>`<tr>
|
||
<td>${p.image?'<img src="'+p.image+'" class="img-thumb" onerror="this.style.display=\'none\'">':'-'}</td>
|
||
<td style="white-space:nowrap">${p.post_date||''}</td>
|
||
<td style="max-width:300px"><b>${esc(p.title)}</b><br><span style="font-size:.72rem;color:#6b7e">${esc(p.excerpt||'').substring(0,60)}</span></td>
|
||
<td><span class="tag tag-${(p.source||'w').toLowerCase()}">${p.source==='L'?'Life Sci':'WEVAL'}</span></td>
|
||
<td><input type="number" value="${p.likes||0}" min="0" style="width:55px" onchange="updateStat(${p.id},'likes',this.value)"></td>
|
||
<td><input type="number" value="${p.comments||0}" min="0" style="width:55px" onchange="updateStat(${p.id},'comments',this.value)"></td>
|
||
<td><input type="number" value="${p.reposts||0}" min="0" style="width:55px" onchange="updateStat(${p.id},'reposts',this.value)"></td>
|
||
<td><input type="number" value="${p.views||0}" min="0" style="width:65px" onchange="updateStat(${p.id},'views',this.value)"></td>
|
||
<td style="white-space:nowrap">
|
||
<button class="btn btn-gray" style="padding:4px 8px;font-size:.7rem" onclick="editPost(${p.id})">✏️</button>
|
||
${p.linkedin_url?'<a href="'+p.linkedin_url+'" target="_blank" class="btn btn-gray" style="padding:4px 8px;font-size:.7rem;text-decoration:none;display:inline-block">🔗</a>':''}
|
||
</td>
|
||
</tr>`).join('');
|
||
}
|
||
|
||
function esc(s){const d=document.createElement('div');d.textContent=s||'';return d.innerHTML}
|
||
|
||
async function updateStat(id,field,val){
|
||
const body={id:id};body[field]=parseInt(val)||0;
|
||
await fetch(API+'?action=update',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
||
toast('✅ '+field+' mis à jour');
|
||
}
|
||
|
||
function openAdd(){
|
||
document.getElementById('modal-title').textContent='➕ Ajouter un post';
|
||
document.getElementById('edit-id').value='';
|
||
['f-url','f-title','f-excerpt','f-image'].forEach(id=>document.getElementById(id).value='');
|
||
document.getElementById('f-date').value=new Date().toISOString().split('T')[0];
|
||
document.getElementById('f-source').value='W';
|
||
['f-likes','f-comments','f-reposts','f-views'].forEach(id=>document.getElementById(id).value='0');
|
||
document.getElementById('modal-add').classList.add('open');
|
||
}
|
||
|
||
function editPost(id){
|
||
const p=allPosts.find(x=>x.id==id);if(!p)return;
|
||
document.getElementById('modal-title').textContent='✏️ Modifier le post';
|
||
document.getElementById('edit-id').value=id;
|
||
document.getElementById('f-url').value=p.linkedin_url||'';
|
||
document.getElementById('f-date').value=p.post_date||'';
|
||
document.getElementById('f-title').value=p.title||'';
|
||
document.getElementById('f-excerpt').value=p.excerpt||'';
|
||
document.getElementById('f-image').value=p.image||'';
|
||
document.getElementById('f-source').value=p.source||'W';
|
||
document.getElementById('f-likes').value=p.likes||0;
|
||
document.getElementById('f-comments').value=p.comments||0;
|
||
document.getElementById('f-reposts').value=p.reposts||0;
|
||
document.getElementById('f-views').value=p.views||0;
|
||
document.getElementById('modal-add').classList.add('open');
|
||
}
|
||
|
||
async function savePost(){
|
||
const id=document.getElementById('edit-id').value;
|
||
const data={
|
||
date:document.getElementById('f-date').value,
|
||
title:document.getElementById('f-title').value,
|
||
excerpt:document.getElementById('f-excerpt').value,
|
||
image:document.getElementById('f-image').value,
|
||
source:document.getElementById('f-source').value,
|
||
likes:parseInt(document.getElementById('f-likes').value)||0,
|
||
comments:parseInt(document.getElementById('f-comments').value)||0,
|
||
reposts:parseInt(document.getElementById('f-reposts').value)||0,
|
||
views:parseInt(document.getElementById('f-views').value)||0,
|
||
linkedin_url:document.getElementById('f-url').value
|
||
};
|
||
if(!data.title){toast('❌ Titre requis');return}
|
||
|
||
if(id){
|
||
data.id=parseInt(id);
|
||
await fetch(API+'?action=update',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
||
}else{
|
||
await fetch(API+'?action=add',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)});
|
||
}
|
||
closeModal('modal-add');
|
||
toast('✅ Post '+(id?'modifié':'ajouté'));
|
||
loadPosts();
|
||
}
|
||
|
||
function openImport(){document.getElementById('modal-import').classList.add('open')}
|
||
|
||
async function importPost(){
|
||
const url=document.getElementById('import-url').value;
|
||
const title=document.getElementById('import-title').value;
|
||
const excerpt=document.getElementById('import-excerpt').value;
|
||
if(!title){toast('❌ Titre requis');return}
|
||
await fetch(API+'?action=add',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({
|
||
date:new Date().toISOString().split('T')[0],
|
||
title:title,
|
||
excerpt:excerpt,
|
||
linkedin_url:url,
|
||
source:'W',
|
||
likes:0,comments:0,reposts:0,views:0,
|
||
image:'/uploads/actualites/img-wevia-ia.jpg'
|
||
})});
|
||
closeModal('modal-import');
|
||
toast('✅ Post importé');
|
||
loadPosts();
|
||
}
|
||
|
||
function parseUrl(url){
|
||
// Extract post ID from LinkedIn URL for future API use
|
||
const m=url.match(/activity:(\d+)/);
|
||
if(m)console.log('LinkedIn activity ID:',m[1]);
|
||
}
|
||
|
||
function closeModal(id){document.getElementById(id).classList.remove('open')}
|
||
document.querySelectorAll('.modal').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.remove('open')}));
|
||
|
||
loadPosts();
|
||
</script>
|
||
</body>
|
||
</html>
|