10 products pages with Gemini premium CSS applied (marker DOCTRINE-201 verified): - leadforge (52279B) academy (38428) consulting (30061) ai-sdr (29446) - arsenal (47227) auditai (37500) academy-elearning (20999) - ecosysteme-ia-maroc (21032) roi-calculator (24168) linkedin-manager (25793) All HTTP 200 confirmed, Playwright audit tr:0 br:0 ZERO overlap regression Handler v2 improvements (doctrine 203): - wgux-apply.py: sudo chattr -i/+i (fix silent failure batch mode) - Verify post-apply: marker presence + size delta > 0 - Restore from GOLD backup if corruption detected - fallback sudo tee if direct write PermissionError Scripts deployed: - /var/www/html/api/wevia-gemini-ux-apply.sh (orchestrator) - /var/www/html/api/wgux-build-payload.py (Gemini prompt builder, maxTokens 16000) - /var/www/html/api/wgux-parse.py (robust JSON parser) - /var/www/html/api/wgux-apply.py v2 (sudo chattr + verify) - /var/www/html/api/wgux-shot.js (Playwright screenshot) Intents LIVE: - intent-opus4-wevia_gemini_ux_fix (review mode) - intent-opus4-wevia_gemini_ux_apply (apply mode) 10 NL triggers each: gemini ux, refais ux, apply ux gemini, audit ux gemini, etc. Gap batch reliability identified (phase 62-64): - Direct call sudo wgux-apply.py WORKS - Orchestrator via nohup sudo bash -c WORKS in foreground - Background batch parallel: sporadic silent failure despite sudo chattr - Root cause: sudo context loss in nested child process under FPM - Recommendation next phase: appel seq direct sans orchestrator BG Cumul session Opus: - 62 tags (incluant phase 65) - 42 doctrines (146-203) - 428 pages UX doctrine 60 - 10 pages Gemini premium CSS APPLIED E2E - NR 153/153 invariant 65 phases
740 lines
25 KiB
HTML
740 lines
25 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">
|
||
<!-- DOCTRINE-60-UX-ENRICH products-batch-doctrine195 -->
|
||
<style id="wtp-doctrine60-ux-premium">
|
||
:root {
|
||
--wtp-bg-start:#0a0f1c; --wtp-bg-end:#0f172a;
|
||
--wtp-surface:rgba(15,23,42,.85); --wtp-surface-hover:rgba(30,41,59,.9);
|
||
--wtp-border:rgba(99,102,241,.25); --wtp-border-hover:rgba(99,102,241,.5);
|
||
--wtp-text:#e2e8f0; --wtp-text-dim:#94a3b8; --wtp-text-bright:#f1f5f9;
|
||
--wtp-primary:#6366f1; --wtp-primary-hover:#7c7feb;
|
||
--wtp-accent:#8b5cf6; --wtp-success:#10b981; --wtp-warning:#f59e0b; --wtp-danger:#ef4444;
|
||
--wtp-radius:12px; --wtp-shadow:0 4px 24px rgba(99,102,241,.15); --wtp-shadow-lg:0 8px 48px rgba(99,102,241,.25);
|
||
--wtp-transition:all .2s cubic-bezier(.4,0,.2,1);
|
||
--wtp-font:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;
|
||
--wtp-font-mono:'JetBrains Mono',monospace;
|
||
}
|
||
.wtp-card{background:var(--wtp-surface);border:1px solid var(--wtp-border);border-radius:var(--wtp-radius);padding:20px;transition:var(--wtp-transition)}
|
||
.wtp-card:hover{border-color:var(--wtp-border-hover);box-shadow:var(--wtp-shadow)}
|
||
.wtp-btn{background:linear-gradient(135deg,var(--wtp-primary),var(--wtp-accent));color:#fff;padding:10px 20px;border:none;border-radius:8px;cursor:pointer;font-weight:600;transition:var(--wtp-transition)}
|
||
.wtp-btn:hover{transform:translateY(-1px);box-shadow:var(--wtp-shadow)}
|
||
.wtp-badge{display:inline-flex;align-items:center;padding:4px 10px;background:var(--wtp-surface);border:1px solid var(--wtp-border);border-radius:20px;font-size:12px;color:var(--wtp-text-dim)}
|
||
@media (max-width:768px){#weval-bot-widget{bottom:100px !important;right:16px !important;z-index:10001 !important}#weval-bot-btn{width:48px !important;height:48px !important}#weval-bot-btn svg{width:22px !important;height:22px !important}#footer_banner,.footer-banner,[class*="footer-bandeau"]{z-index:9990 !important}}
|
||
</style>
|
||
|
||
|
||
<!-- DOCTRINE-201-GEMINI-APPLY-20260424-181436 -->
|
||
<style>
|
||
:root {
|
||
--wtp-bg: #1A1D24; /* Dark background */
|
||
--wtp-card: #242830; /* Slightly lighter card background */
|
||
--wtp-primary: #FFC107; /* Orange/yellow for primary actions */
|
||
--wtp-accent: #66BB6A; /* Vibrant green for accents */
|
||
--wtp-text-light: #E0E0E0;
|
||
--wtp-text-muted: #A0A0A0;
|
||
--wtp-border: #3A3F47;
|
||
--wtp-shadow: rgba(0, 0, 0, 0.3);
|
||
--wtp-shadow-light: rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
background-color: var(--wtp-bg);
|
||
color: var(--wtp-text-light);
|
||
margin: 0;
|
||
padding: 20px;
|
||
line-height: 1.6;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
/* General container styling */
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 0 15px;
|
||
}
|
||
|
||
/* Header styling */
|
||
h1, h2, h3, h4, h5, h6 {
|
||
color: var(--wtp-text-light);
|
||
margin-top: 0;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
/* Buttons */
|
||
button, .button-like {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
}
|
||
|
||
/* Specific elements from the image */
|
||
.header-section {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30px;
|
||
padding-bottom: 20px;
|
||
border-bottom: 1px solid var(--wtp-border);
|
||
}
|
||
|
||
.header-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.header-title img {
|
||
height: 30px; /* Adjust as needed */
|
||
}
|
||
|
||
.header-title h1 {
|
||
font-size: 1.8rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.header-subtitle {
|
||
color: var(--wtp-text-muted);
|
||
font-size: 0.9rem;
|
||
margin-top: -10px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.header-actions button, .header-actions .button-like {
|
||
background-color: var(--wtp-card);
|
||
color: var(--wtp-text-light);
|
||
border: 1px solid var(--wtp-border);
|
||
box-shadow: 0 2px 5px var(--wtp-shadow-light);
|
||
}
|
||
|
||
.header-actions button:hover, .header-actions .button-like:hover {
|
||
background-color: var(--wtp-border);
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 8px var(--wtp-shadow);
|
||
}
|
||
|
||
.header-actions button.primary, .header-actions .button-like.primary {
|
||
background-color: var(--wtp-primary);
|
||
color: var(--wtp-bg); /* Dark text on primary button */
|
||
box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3);
|
||
}
|
||
|
||
.header-actions button.primary:hover, .header-actions .button-like.primary:hover {
|
||
background-color: #FFD54F; /* Lighter primary on hover */
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 6px 15px rgba(255, 193, 7, 0.5);
|
||
}
|
||
|
||
.last-updated {
|
||
font-size: 0.85rem;
|
||
color: var(--wtp-text-muted);
|
||
text-align: right;
|
||
margin-top: -10px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* KPI Section */
|
||
.kpi-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
/* Table Styling */
|
||
.data-table-wrapper {
|
||
overflow-x: auto;
|
||
border-radius: 12px;
|
||
box-shadow: 0 8px 20px var(--wtp-shadow);
|
||
}
|
||
|
||
.data-table {
|
||
background-color: var(--wtp-card);
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
min-width: 800px; /* Ensure table content doesn't shrink too much on medium screens */
|
||
}
|
||
|
||
.data-table th, .data-table td {
|
||
padding: 15px 20px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--wtp-border);
|
||
}
|
||
|
||
.data-table th {
|
||
background-color: var(--wtp-border);
|
||
color: var(--wtp-text-muted);
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.data-table tbody tr {
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.data-table tbody tr:hover {
|
||
background-color: #2E333D; /* Slightly lighter on hover */
|
||
}
|
||
|
||
.data-table tbody tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.data-table td.image-cell img {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 6px;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.data-table .title-cell strong {
|
||
color: var(--wtp-text-light);
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.data-table .title-cell span {
|
||
color: var(--wtp-text-muted);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.data-table .tag {
|
||
display: inline-block;
|
||
padding: 5px 10px;
|
||
border-radius: 6px;
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.data-table .tag.life-sci {
|
||
background-color: rgba(102, 187, 106, 0.2); /* var(--wtp-accent) with transparency */
|
||
color: var(--wtp-accent);
|
||
}
|
||
|
||
.data-table .tag.weval {
|
||
background-color: rgba(66, 165, 245, 0.2); /* A blue tone */
|
||
color: #42A5F5;
|
||
}
|
||
|
||
.data-table .action-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.data-table .action-buttons button {
|
||
background-color: var(--wtp-border);
|
||
color: var(--wtp-text-muted);
|
||
width: 36px;
|
||
height: 36px;
|
||
padding: 0;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
box-shadow: 0 2px 5px var(--wtp-shadow-light);
|
||
}
|
||
|
||
.data-table .action-buttons button:hover {
|
||
background-color: #4A505B;
|
||
color: var(--wtp-text-light);
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 8px var(--wtp-shadow);
|
||
}
|
||
|
||
/* Specific requirements */
|
||
|
||
/* .wtp-hero-premium */
|
||
.wtp-hero-premium {
|
||
background: linear-gradient(135deg, var(--wtp-bg) 0%, #0F1217 100%);
|
||
padding: 60px 0;
|
||
margin-bottom: 40px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
border-radius: 15px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||
text-align: center;
|
||
}
|
||
|
||
.wtp-hero-premium::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: url('data:image/svg+xml;utf8,<svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="%233A3F47" stroke-width="0.5"/></pattern></defs><rect width="100%" height="100%" fill="url(%23grid)"/></svg>') repeat;
|
||
opacity: 0.05; /* Subtle backdrop */
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
|
||
.wtp-hero-premium > * {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* .wtp-kpi-card */
|
||
.wtp-kpi-card {
|
||
background-color: var(--wtp-card);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 15px var(--wtp-shadow);
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
min-height: 100px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
border: 1px solid var(--wtp-border);
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.wtp-kpi-card:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 8px 20px var(--wtp-shadow);
|
||
}
|
||
|
||
.wtp-kpi-card .kpi-value {
|
||
font-size: 2.2rem;
|
||
font-weight: 700;
|
||
color: var(--wtp-primary);
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.wtp-kpi-card .kpi-label {
|
||
font-size: 0.9rem;
|
||
color: var(--wtp-text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.wtp-kpi-card .sparkline-container {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 40px; /* Height for the sparkline */
|
||
opacity: 0.3;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.wtp-kpi-card .sparkline-container svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
stroke: var(--wtp-accent); /* Sparkline color */
|
||
stroke-width: 2;
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
|
||
/* .wtp-status-led */
|
||
@keyframes pulse {
|
||
0% { box-shadow: 0 0 0 0 rgba(102, 187, 106, 0.7); }
|
||
70% { box-shadow: 0 0 0 10px rgba(102, 187, 106, 0); }
|
||
100% { box-shadow: 0 0 0 0 rgba(102, 187, 106, 0); }
|
||
}
|
||
|
||
.wtp-status-led {
|
||
display: inline-block;
|
||
width: 10px;
|
||
height: 10px;
|
||
background-color: var(--wtp-accent);
|
||
border-radius: 50%;
|
||
margin-right: 8px;
|
||
animation: pulse 2s infinite;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* .wtp-action-btn */
|
||
.wtp-action-btn {
|
||
background: linear-gradient(45deg, var(--wtp-primary) 0%, #FFD54F 100%);
|
||
color: var(--wtp-bg);
|
||
border: none;
|
||
padding: 12px 25px;
|
||
border-radius: 8px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-decoration: none; /* In case it's an anchor */
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.wtp-action-btn:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 8px 20px rgba(255, 193, 7, 0.5);
|
||
background: linear-gradient(45deg, #FFD54F 0%, var(--wtp-primary) 100%);
|
||
}
|
||
|
||
/* Media query mobile 768px (bot-widget bottom 100px anti-overlap) */
|
||
@media (max-width: 768px) {
|
||
body {
|
||
padding: 15px;
|
||
}
|
||
|
||
.header-section {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 20px;
|
||
}
|
||
|
||
.header-actions {
|
||
width: 100%;
|
||
justify-content: stretch;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.header-actions button, .header-actions .button-like {
|
||
flex-grow: 1;
|
||
padding: 12px 15px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.kpi-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.data-table-wrapper {
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.data-table th, .data-table td {
|
||
padding: 12px 15px;
|
||
}
|
||
|
||
.data-table .title-cell strong {
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.data-table .title-cell span {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.data-table .tag {
|
||
font-size: 0.75rem;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
.data-table .action-buttons button {
|
||
width: 32px;
|
||
height: 32px;
|
||
}
|
||
|
||
/* Assuming a '.bot-widget' class for the bottom widget */
|
||
.bot-widget {
|
||
position: fixed;
|
||
bottom: 100px; /* Anti-overlap with potential mobile navigation/footer */
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 1000;
|
||
background-color: var(--wtp-card);
|
||
padding: 15px;
|
||
border-top-left-radius: 12px;
|
||
border-top-right-radius: 12px;
|
||
box-shadow: 0 -4px 15px rgba(0,0,0,0.3);
|
||
}
|
||
}
|
||
|
||
/* General improvements for premium feel */
|
||
a {
|
||
color: var(--wtp-primary);
|
||
text-decoration: none;
|
||
transition: color 0.2s ease;
|
||
}
|
||
|
||
a:hover {
|
||
color: #FFD54F;
|
||
}
|
||
|
||
/* Scrollbar styling for a dark theme */
|
||
::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: var(--wtp-bg);
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--wtp-border);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: var(--wtp-text-muted);
|
||
}
|
||
|
||
</style>
|
||
<!-- END-DOCTRINE-201 -->
|
||
</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>
|