Files
html/products/linkedin-manager.html
2026-04-12 22:57:03 +02:00

249 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>