last_edit / frontend /projects.html
Moharek
Deploy Moharek GEO Platform
a74b879
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>مشاريعي — محرك GEO</title>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<link rel="stylesheet" href="/theme.css">
<style>
:root {
/* Shared from theme.css */
--brand-blue: var(--accent2);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--text);font-family:var(--font);overflow-x:hidden}
.bg-mesh{position:fixed;inset:0;z-index:0;background:radial-gradient(circle at 15% 15%,rgba(59,130,246,.1) 0%,transparent 45%),radial-gradient(circle at 85% 85%,rgba(251,191,36,.07) 0%,transparent 45%);filter:blur(80px);pointer-events:none}
nav{position:sticky;top:0;z-index:100;background:rgba(3,7,18,.88);backdrop-filter:blur(24px);border-bottom:1px solid var(--border);height:68px;display:flex;align-items:center;padding:0 40px;justify-content:space-between}
.nav-logo{display:flex;align-items:center;gap:10px;text-decoration:none}
.nav-logo-icon{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,#3b82f6,#06b6d4);display:grid;place-items:center;font-weight:900;font-size:15px;color:#fff}
.nav-logo-text{font-size:15px;font-weight:900;color:#fff}
.nav-logo-sub{font-size:10px;color:var(--muted)}
.nav-links{display:flex;gap:20px;align-items:center}
.nav-link{color:var(--muted);text-decoration:none;font-size:13.5px;font-weight:600;transition:.3s}
.nav-link:hover,.nav-link.active{color:#fff}
.nav-back{display:flex;align-items:center;gap:7px;padding:8px 16px;background:rgba(59,130,246,.1);border:1px solid rgba(59,130,246,.25);border-radius:9px;color:var(--blue);font-size:13px;font-weight:700;text-decoration:none;transition:all .25s}
.nav-back:hover{background:rgba(59,130,246,.18)}
.wrap{max-width:1300px;margin:0 auto;padding:0 28px;position:relative;z-index:1}
.page-header{padding:48px 0 32px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px}
.page-title{font-size:28px;font-weight:900}
.page-sub{font-size:13px;color:var(--muted);margin-top:4px}
.btn{display:inline-flex;align-items:center;gap:8px;padding:11px 22px;border-radius:10px;font-family:var(--font);font-size:13.5px;font-weight:700;cursor:pointer;border:none;transition:all .25s}
.btn-primary{background:linear-gradient(135deg,var(--blue),var(--cyan));color:#fff}
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(59,130,246,.4)}
.btn-ghost{background:rgba(255,255,255,.04);border:1px solid var(--border);color:var(--muted)}
.btn-ghost:hover{border-color:var(--blue);color:var(--blue)}
.btn-danger{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.25);color:var(--red)}
/* Projects grid */
.projects-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:20px;margin-bottom:40px}
.project-card{background:rgba(255,255,255,.025);border:1px solid var(--border);border-radius:18px;padding:24px;transition:all .3s;position:relative;overflow:hidden}
.project-card:hover{border-color:rgba(59,130,246,.3);transform:translateY(-4px);box-shadow:0 16px 40px rgba(0,0,0,.2)}
.project-card-top{display:flex;align-items:center;gap:12px;margin-bottom:18px}
.project-icon{width:44px;height:44px;border-radius:12px;display:grid;place-items:center;font-size:18px;font-weight:900;color:#fff;flex-shrink:0}
.project-name{font-size:15px;font-weight:800}
.project-url{font-size:11.5px;color:var(--muted);margin-top:2px}
.project-score{font-size:28px;font-weight:900;line-height:1}
.project-score-lbl{font-size:10px;color:var(--muted)}
.project-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:16px 0}
.pstat{text-align:center;padding:10px;background:rgba(255,255,255,.03);border-radius:10px}
.pstat-num{font-size:16px;font-weight:800;line-height:1;margin-bottom:3px}
.pstat-lbl{font-size:10px;color:var(--muted)}
.project-chart{height:60px;margin:12px 0}
.project-actions{display:flex;gap:8px;margin-top:14px;padding-top:14px;border-top:1px solid var(--border)}
.project-actions .btn{flex:1;padding:8px 12px;font-size:12px;justify-content:center}
/* Add project modal */
.modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.8);backdrop-filter:blur(8px);z-index:999;display:none;align-items:center;justify-content:center}
.modal-bg.open{display:flex}
.modal{background:#0b1120;border:1px solid rgba(59,130,246,.3);border-radius:20px;padding:36px;width:90%;max-width:480px}
.modal h3{font-size:20px;font-weight:900;margin-bottom:24px}
.form-group{margin-bottom:18px}
.form-group label{display:block;font-size:13px;font-weight:700;margin-bottom:8px;color:var(--muted)}
.form-input{width:100%;background:rgba(255,255,255,.04);border:1px solid var(--border);border-radius:10px;padding:12px 16px;color:#fff;font-family:var(--font);font-size:14px;transition:all .3s}
.form-input:focus{outline:none;border-color:rgba(59,130,246,.5);background:rgba(255,255,255,.07)}
.color-options{display:flex;gap:8px;flex-wrap:wrap}
.color-opt{width:28px;height:28px;border-radius:7px;cursor:pointer;border:2px solid transparent;transition:all .2s}
.color-opt.selected{border-color:#fff;transform:scale(1.15)}
/* Empty state */
.empty-state{text-align:center;padding:80px 20px;color:var(--muted)}
.empty-state i{font-size:48px;display:block;margin-bottom:16px;color:var(--dim)}
.empty-state h3{font-size:18px;font-weight:800;color:#fff;margin-bottom:8px}
.empty-state p{font-size:13.5px;margin-bottom:24px}
/* Toast */
.toasts{position:fixed;top:20px;left:20px;z-index:9999;display:flex;flex-direction:column;gap:8px}
.toast{padding:12px 18px;border-radius:11px;background:rgba(11,17,32,.95);border:1px solid var(--border);font-size:13px;font-weight:600;display:flex;align-items:center;gap:9px;min-width:260px;animation:tslide .35s ease}
.toast.ok{border-color:rgba(16,185,129,.3)}.toast.ok i{color:#10b981}
.toast.err{border-color:rgba(239,68,68,.3)}.toast.err i{color:var(--red)}
@keyframes tslide{from{opacity:0;transform:translateX(-16px)}to{opacity:1;transform:translateX(0)}}
@media(max-width:640px){.projects-grid{grid-template-columns:1fr}.page-header{flex-direction:column;align-items:flex-start}}
</style>
</head>
<body>
<div class="bg-mesh"></div>
<div class="toasts" id="toasts"></div>
<nav>
<a href="/portal.html" class="nav-logo">
<div class="nav-logo-icon">م</div>
<div><div class="nav-logo-text">محرك</div><div class="nav-logo-sub">SEO · GEO · AI</div></div>
</a>
<div class="nav-links">
<a class="nav-link active" href="/projects.html"><i class="fas fa-folder"></i> مشاريعي</a>
<a class="nav-link" href="/jobs.html">التحليلات</a>
<a class="nav-link" href="/pricing.html">الخطط</a>
</div>
<a href="/portal.html" class="nav-back"><i class="fas fa-th-large"></i> لوحة التحكم</a>
</nav>
<div class="wrap">
<div class="page-header">
<div>
<h1 class="page-title"><i class="fas fa-folder-open" style="color:var(--blue);margin-left:10px"></i> مشاريعي</h1>
<p class="page-sub">تتبع أداء مواقع متعددة وقارن نتائجها عبر الزمن</p>
</div>
<button class="btn btn-primary" onclick="openAddModal()">
<i class="fas fa-plus"></i> مشروع جديد
</button>
</div>
<div class="projects-grid" id="projectsGrid">
<div class="empty-state" style="grid-column:1/-1">
<i class="fas fa-circle-notch fa-spin"></i>
<h3>جارٍ التحميل...</h3>
</div>
</div>
</div>
<!-- Add Project Modal -->
<div class="modal-bg" id="addModal">
<div class="modal">
<h3><i class="fas fa-plus-circle" style="color:var(--blue);margin-left:8px"></i> مشروع جديد</h3>
<div class="form-group">
<label>اسم المشروع</label>
<input class="form-input" id="projName" placeholder="مثال: موقع الشركة الرئيسي">
</div>
<div class="form-group">
<label>رابط الموقع</label>
<input class="form-input" id="projUrl" placeholder="https://example.com">
</div>
<div class="form-group">
<label>الصناعة (اختياري)</label>
<input class="form-input" id="projIndustry" placeholder="مثال: التجارة الإلكترونية">
</div>
<div class="form-group">
<label>لون المشروع</label>
<div class="color-options" id="colorOptions">
<div class="color-opt selected" style="background:#3b82f6" data-color="#3b82f6" onclick="selectColor(this)"></div>
<div class="color-opt" style="background:#10b981" data-color="#10b981" onclick="selectColor(this)"></div>
<div class="color-opt" style="background:#fbbf24" data-color="#fbbf24" onclick="selectColor(this)"></div>
<div class="color-opt" style="background:#a855f7" data-color="#a855f7" onclick="selectColor(this)"></div>
<div class="color-opt" style="background:#ef4444" data-color="#ef4444" onclick="selectColor(this)"></div>
<div class="color-opt" style="background:#06b6d4" data-color="#06b6d4" onclick="selectColor(this)"></div>
<div class="color-opt" style="background:#f97316" data-color="#f97316" onclick="selectColor(this)"></div>
<div class="color-opt" style="background:#ec4899" data-color="#ec4899" onclick="selectColor(this)"></div>
</div>
</div>
<div style="display:flex;gap:10px;margin-top:8px">
<button class="btn btn-primary" style="flex:1" onclick="createProject()"><i class="fas fa-check"></i> إنشاء</button>
<button class="btn btn-ghost" onclick="closeAddModal()">إلغاء</button>
</div>
</div>
</div>
<script>
const API = 'http://localhost:8001/api';
const token = localStorage.getItem('token');
// if(!token) window.location.href = '/login.html';
let selectedColor = '#3b82f6';
let charts = {};
function toast(msg, type='ok'){
const c=document.getElementById('toasts');
const t=document.createElement('div');
t.className='toast '+type;
t.innerHTML=`<i class="fas fa-${type==='ok'?'check-circle':'exclamation-circle'}"></i><span>${msg}</span>`;
c.appendChild(t);
setTimeout(()=>{t.style.opacity='0';t.style.transition='.3s';setTimeout(()=>t.remove(),300)},4000);
}
function selectColor(el){
document.querySelectorAll('.color-opt').forEach(e=>e.classList.remove('selected'));
el.classList.add('selected');
selectedColor = el.dataset.color;
}
function openAddModal(){ document.getElementById('addModal').classList.add('open'); }
function closeAddModal(){ document.getElementById('addModal').classList.remove('open'); }
async function createProject(){
const name = document.getElementById('projName').value.trim();
const url = document.getElementById('projUrl').value.trim();
const industry = document.getElementById('projIndustry').value.trim();
if(!name||!url){ toast('أدخل الاسم والرابط','err'); return; }
try{
const r = await fetch(`${API}/projects`,{
method:'POST',
headers:{'Content-Type':'application/json','Authorization':`Bearer ${token}`},
body: JSON.stringify({name,url,industry,color:selectedColor})
});
const d = await r.json();
if(d.ok){ toast('تم إنشاء المشروع'); closeAddModal(); loadProjects(); }
else toast(d.error||'خطأ','err');
}catch(e){ toast('خطأ في الاتصال','err'); }
}
async function deleteProject(id){
if(!confirm('هل تريد حذف هذا المشروع؟')) return;
await fetch(`${API}/projects/${id}`,{method:'DELETE',headers:{'Authorization':`Bearer ${token}`}});
toast('تم الحذف');
loadProjects();
}
function analyzeProject(url){
window.location.href = `/portal.html?url=${encodeURIComponent(url)}`;
}
function viewReport(jobId){
window.location.href = `/jobs.html?job=${jobId}`;
}
function buildTrendChart(canvasId, trend, color){
const ctx = document.getElementById(canvasId);
if(!ctx || !trend.length) return;
if(charts[canvasId]) charts[canvasId].destroy();
const grad = ctx.getContext('2d').createLinearGradient(0,0,0,60);
grad.addColorStop(0, color+'44');
grad.addColorStop(1, color+'00');
charts[canvasId] = new Chart(ctx, {
type:'line',
data:{
labels: trend.map(t=>new Date(t.snapshot_date).toLocaleDateString('ar-SA',{day:'numeric',month:'short'})),
datasets:[{data:trend.map(t=>t.geo_score),borderColor:color,backgroundColor:grad,borderWidth:2,fill:true,tension:0.4,pointRadius:2}]
},
options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{enabled:false}},scales:{x:{display:false},y:{display:false,min:0,max:100}}}
});
}
async function loadProjects(){
const grid = document.getElementById('projectsGrid');
try{
const r = await fetch(`${API}/projects`,{headers:{'Authorization':`Bearer ${token}`}});
const d = await r.json();
if(!d.ok||!d.projects.length){
grid.innerHTML = `<div class="empty-state" style="grid-column:1/-1">
<i class="fas fa-folder-open"></i>
<h3>لا توجد مشاريع بعد</h3>
<p>أضف موقعك الأول لبدء تتبع أدائه عبر الزمن</p>
<button class="btn btn-primary" onclick="openAddModal()"><i class="fas fa-plus"></i> أضف مشروعاً</button>
</div>`;
return;
}
grid.innerHTML = d.projects.map(p=>{
const latest = p.latest || {};
const score = latest.geo_score || 0;
const sColor = score>=80?'#10b981':score>=60?'#fbbf24':score>=40?'#f97316':'#ef4444';
const hasTrend = p.trend && p.trend.length > 1;
const trendDir = hasTrend ? (p.trend[p.trend.length-1].geo_score > p.trend[0].geo_score ? '↑' : '↓') : '';
const trendColor = trendDir==='↑'?'#10b981':'#ef4444';
return `<div class="project-card">
<div class="project-card-top">
<div class="project-icon" style="background:${p.color||'#3b82f6'}22;color:${p.color||'#3b82f6'}">${p.name[0].toUpperCase()}</div>
<div style="flex:1">
<div class="project-name">${p.name}</div>
<div class="project-url">${p.url}</div>
${p.industry?`<div style="font-size:10px;color:var(--muted);margin-top:2px">${p.industry}</div>`:''}
</div>
<div style="text-align:left">
<div class="project-score" style="color:${sColor}">${score||'--'}</div>
<div class="project-score-lbl">GEO /100</div>
${trendDir?`<div style="font-size:12px;color:${trendColor};font-weight:700">${trendDir}</div>`:''}
</div>
</div>
<div class="project-stats">
<div class="pstat">
<div class="pstat-num" style="color:#06b6d4">${latest.ai_visibility||0}%</div>
<div class="pstat-lbl">رؤية AI</div>
</div>
<div class="pstat">
<div class="pstat-num" style="color:#a855f7">${latest.pages_crawled||0}</div>
<div class="pstat-lbl">صفحة</div>
</div>
<div class="pstat">
<div class="pstat-num" style="color:#fbbf24">${latest.keywords_count||0}</div>
<div class="pstat-lbl">كلمة</div>
</div>
</div>
${hasTrend?`<div class="project-chart"><canvas id="chart_${p.id}" height="60"></canvas></div>`:'<div style="height:60px;display:grid;place-items:center;color:var(--dim);font-size:12px">لا توجد بيانات تاريخية بعد</div>'}
<div class="project-actions">
<button class="btn btn-primary" onclick="analyzeProject('${p.url}')"><i class="fas fa-play"></i> تحليل</button>
${latest.job_id?`<button class="btn btn-ghost" onclick="viewReport(${latest.job_id})"><i class="fas fa-file-alt"></i> تقرير</button>`:''}
<button class="btn btn-danger" onclick="deleteProject(${p.id})"><i class="fas fa-trash"></i></button>
</div>
</div>`;
}).join('');
// Build trend charts
setTimeout(()=>{
d.projects.forEach(p=>{
if(p.trend && p.trend.length > 1){
buildTrendChart(`chart_${p.id}`, p.trend, p.color||'#3b82f6');
}
});
}, 100);
}catch(e){ grid.innerHTML='<div class="empty-state" style="grid-column:1/-1"><i class="fas fa-exclamation-circle"></i><h3>خطأ في التحميل</h3></div>'; }
}
// Auto-analyze if URL param passed from portal
const urlParam = new URLSearchParams(location.search).get('url');
if(urlParam){
// Pre-fill the add modal
setTimeout(()=>{
document.getElementById('projUrl').value = urlParam;
openAddModal();
}, 500);
}
loadProjects();
</script>
</body>
</html>