| <!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> |
|
|
| |
| <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'); |
| |
| 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(''); |
| |
| |
| 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>'; } |
| } |
| |
| |
| const urlParam = new URLSearchParams(location.search).get('url'); |
| if(urlParam){ |
| |
| setTimeout(()=>{ |
| document.getElementById('projUrl').value = urlParam; |
| openAddModal(); |
| }, 500); |
| } |
| |
| loadProjects(); |
| </script> |
| </body> |
| </html> |
|
|