neya / static /admin.html
my0919175's picture
Upload 20 files
50c11e2 verified
Raw
History Blame Contribute Delete
34.1 kB
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<title>أدمن — نيّة</title>
<meta name="robots" content="noindex, nofollow">
<style>
@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;800&family=IBM+Plex+Sans+Arabic:wght@300;400;500;600&display=swap');
:root{
--bg:#FFFFFF; --ink:#0A0A0A;
--line: rgba(10,10,10,0.12); --line-strong: rgba(10,10,10,0.22);
--muted: rgba(10,10,10,0.55); --muted-soft: rgba(10,10,10,0.4);
--surface: rgba(10,10,10,0.03); --surface-hover: rgba(10,10,10,0.05);
--radius:14px; --radius-sm:10px; --ease: cubic-bezier(.22,1,.36,1);
--shadow-sm: 0 1px 3px rgba(10,10,10,0.05), 0 1px 2px rgba(10,10,10,0.04);
--shadow-md: 0 6px 20px rgba(10,10,10,0.07), 0 2px 6px rgba(10,10,10,0.04);
--good:#1E8E3E; --good-bg:#EAF7EE; --danger:#C00000; --danger-bg:#FDECEC;
}
*{ box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
html{ overflow-x:hidden; scroll-behavior:smooth; height:100%; }
body{
font-family:'IBM Plex Sans Arabic', sans-serif; margin:0; padding:0; min-height:100vh;
background: var(--bg, #FFFFFF); color: var(--ink, #0A0A0A); overflow-x:hidden; -webkit-font-smoothing:antialiased;
}
.page-wrap{ max-width:1180px; margin:0 auto; padding:22px; animation: fadeIn .35s var(--ease, cubic-bezier(.22,1,.36,1)) both; }
h1,h2,h3,button{ font-family:'Cairo', sans-serif; }
@keyframes fadeIn{ from{opacity:0; transform:translateY(8px);} to{opacity:1; transform:none;} }
@keyframes rowIn{ from{opacity:0; transform:translateY(4px);} to{opacity:1; transform:none;} }
tr{ animation: rowIn .3s var(--ease, cubic-bezier(.22,1,.36,1)) both; }
img,video{ max-width:100%; height:auto; }
*{ word-wrap:break-word; overflow-wrap:break-word; }
.topbar{
display:flex; justify-content:space-between; align-items:center; gap:14px; flex-wrap:wrap;
margin-bottom:22px; padding-bottom:18px; border-bottom:1px solid var(--line, rgba(10,10,10,0.12));
}
.topbar h1{ font-size:22px; font-weight:800; margin:0; }
.stats{ display:flex; gap:12px; flex-wrap:wrap; margin-bottom:22px; }
.stat{
background:var(--bg, #FFFFFF); border:1.5px solid var(--line, rgba(10,10,10,0.12)); border-radius: var(--radius-sm, 10px);
padding:14px 18px; font-size:13px; color:var(--muted, rgba(10,10,10,0.55)); transition: transform .2s var(--ease, cubic-bezier(.22,1,.36,1)), box-shadow .2s var(--ease, cubic-bezier(.22,1,.36,1)), border-color .2s var(--ease, cubic-bezier(.22,1,.36,1));
}
.stat:hover{ transform: translateY(-2px); box-shadow: var(--shadow-sm, 0 1px 3px rgba(10,10,10,0.05), 0 1px 2px rgba(10,10,10,0.04)); border-color: var(--line-strong, rgba(10,10,10,0.22)); }
.stat b{ font-size:19px; display:block; font-family:'Cairo',sans-serif; font-weight:800; color:var(--ink, #0A0A0A); margin-top:2px; }
.filters{ display:flex; gap:8px; margin-bottom:18px; flex-wrap:wrap; }
.filters button{
padding:8px 18px; border-radius:999px; border:1.5px solid var(--line-strong, rgba(10,10,10,0.22)); background:var(--bg, #FFFFFF);
color:var(--ink, #0A0A0A); cursor:pointer; font-size:13px; font-weight:600; transition: all .18s var(--ease, cubic-bezier(.22,1,.36,1));
}
.filters button:hover{ background: var(--surface, rgba(10,10,10,0.03)); }
.filters button:active{ transform: scale(.95); }
.filters button.active{ background:var(--ink, #0A0A0A); color:var(--bg, #FFFFFF); border-color:var(--ink, #0A0A0A); }
input#search{
padding:9px 14px; border-radius: var(--radius-sm, 10px); border:1.5px solid var(--line, rgba(10,10,10,0.12)); font-size:13.5px;
min-width:220px; font-family:inherit; transition: border-color .18s var(--ease, cubic-bezier(.22,1,.36,1)), box-shadow .18s var(--ease, cubic-bezier(.22,1,.36,1));
}
input#search:focus{ outline:none; border-color:var(--ink, #0A0A0A); box-shadow:0 0 0 3.5px rgba(10,10,10,0.07); }
select, input{
font-family:inherit; border-radius: var(--radius-sm, 10px); border:1.5px solid var(--line, rgba(10,10,10,0.12));
padding:6px 10px; font-size:13px; transition: border-color .18s var(--ease, cubic-bezier(.22,1,.36,1)), box-shadow .18s var(--ease, cubic-bezier(.22,1,.36,1));
background:var(--bg, #FFFFFF); color:var(--ink, #0A0A0A);
}
select:focus, input:focus{ outline:none; border-color:var(--ink, #0A0A0A); box-shadow:0 0 0 3.5px rgba(10,10,10,0.07); }
table{ border-collapse:separate; border-spacing:0; width:100%; background:var(--bg, #FFFFFF); border:1px solid var(--line, rgba(10,10,10,0.12)); border-radius: var(--radius, 14px); overflow:hidden; }
td, th{ border-bottom:1px solid var(--line, rgba(10,10,10,0.12)); padding:12px 14px; text-align:right; font-size:13.5px; }
th{ background: var(--surface, rgba(10,10,10,0.03)); font-weight:700; font-size:12.5px; color:var(--muted, rgba(10,10,10,0.55)); }
tr:last-child td{ border-bottom:none; }
tr:hover td{ background: var(--surface, rgba(10,10,10,0.03)); }
img{ max-width:180px; max-height:130px; border-radius: var(--radius-sm, 10px); cursor:pointer; transition: transform .2s var(--ease, cubic-bezier(.22,1,.36,1)); }
img:hover{ transform: scale(1.03); }
button.act{
margin:2px; padding:7px 14px; cursor:pointer; border-radius:8px; border:1.5px solid var(--line-strong, rgba(10,10,10,0.22));
background:var(--bg, #FFFFFF); color:var(--ink, #0A0A0A); font-size:12.5px; font-weight:600; font-family:inherit;
transition: all .18s var(--ease, cubic-bezier(.22,1,.36,1));
display:inline-flex; align-items:center; gap:4px; white-space:nowrap; line-height:1.3;
}
button.act:hover{ background: var(--surface, rgba(10,10,10,0.03)); }
button.act:active{ transform: scale(.94); }
button.act.approve{ background:var(--good, #1E8E3E); color:#ffffff; border-color:var(--good, #1E8E3E); }
button.act.reject{ background:var(--danger, #C00000); color:#ffffff; border-color:var(--danger, #C00000); }
.status-pending, .status-open{ color:#B15C00; font-weight:700; }
.status-approved, .status-resolved, .status-active{ color:var(--good, #1E8E3E); font-weight:700; }
.status-rejected, .status-dismissed, .status-closed{ color:var(--danger, #C00000); font-weight:700; }
.empty{ text-align:center; padding:50px 20px; color:var(--muted-soft, rgba(10,10,10,0.4)); font-size:14px; }
.badge{ display:inline-block; font-size:11px; font-weight:600; padding:3px 10px; border-radius:999px; background:var(--surface, rgba(10,10,10,0.03)); color:var(--muted, rgba(10,10,10,0.55)); margin-inline-start:4px; }
.badge.danger{ background:var(--danger-bg, #FDECEC); color:var(--danger, #C00000); }
.badge.good{ background:var(--good-bg, #EAF7EE); color:var(--good, #1E8E3E); }
::selection{ background:var(--ink, #0A0A0A); color:#fff; }
@media (max-width:720px){
body{ padding:14px; }
.topbar{ margin-bottom:16px; padding-bottom:14px; }
table{ display:block; overflow-x:auto; -webkit-overflow-scrolling:touch; border-radius: var(--radius-sm, 10px); }
button.act{ min-height:38px; }
}
/* ---------- user detail modal ---------- */
.u-overlay{
display:none; position:fixed; inset:0; width:100vw; height:100vh; background:rgba(10,10,10,.5);
align-items:center; justify-content:center; z-index:99; padding:20px;
backdrop-filter: blur(2px); animation: fadeIn .18s var(--ease, cubic-bezier(.22,1,.36,1)) both;
}
.u-overlay.show{ display:flex; }
.u-modal{
background:var(--bg, #FFFFFF); border-radius: var(--radius, 14px); width:100%; max-width:540px;
max-height:85vh; display:flex; flex-direction:column; box-shadow: var(--shadow-md, 0 6px 20px rgba(10,10,10,0.07), 0 2px 6px rgba(10,10,10,0.04));
animation: popIn .22s var(--ease, cubic-bezier(.22,1,.36,1)) both; overflow:hidden;
}
@keyframes popIn{ from{ opacity:0; transform:scale(.96) translateY(8px);} to{ opacity:1; transform:none; } }
.u-modal-head{
display:flex; align-items:center; justify-content:space-between; padding:16px 20px;
border-bottom:1px solid var(--line, rgba(10,10,10,0.12)); font-weight:700; font-size:14.5px; flex-shrink:0;
}
.u-close{
border:none; background:var(--surface, rgba(10,10,10,0.03)); width:30px; height:30px; border-radius:999px;
display:flex; align-items:center; justify-content:center; cursor:pointer; transition: background .18s var(--ease, cubic-bezier(.22,1,.36,1));
}
.u-close svg{ width:15px; height:15px; stroke:var(--ink, #0A0A0A); fill:none; stroke-width:2; }
.u-close:hover{ background:var(--line, rgba(10,10,10,0.12)); }
.u-modal-body{ padding:18px 20px 22px; overflow-y:auto; scrollbar-width:thin; scrollbar-color: var(--line-strong, rgba(10,10,10,0.22)) transparent; }
.u-modal-body::-webkit-scrollbar{ width:7px; }
.u-modal-body::-webkit-scrollbar-track{ background:transparent; }
.u-modal-body::-webkit-scrollbar-thumb{ background: var(--line-strong, rgba(10,10,10,0.22)); border-radius:999px; }
html{ scrollbar-width:thin; scrollbar-color: var(--line-strong, rgba(10,10,10,0.22)) transparent; }
html::-webkit-scrollbar{ width:8px; }
html::-webkit-scrollbar-track{ background:transparent; }
html::-webkit-scrollbar-thumb{ background: var(--line-strong, rgba(10,10,10,0.22)); border-radius:999px; }
.u-name{ margin:0 0 14px; font-size:17px; font-weight:800; }
.u-row{
display:flex; align-items:center; flex-wrap:wrap; gap:6px 10px; padding:8px 0;
border-bottom:1px solid var(--line, rgba(10,10,10,0.12)); font-size:13.5px;
}
.u-row:last-of-type{ border-bottom:none; }
.u-lbl{ font-weight:700; color:var(--muted, rgba(10,10,10,0.55)); min-width:110px; flex-shrink:0; }
.u-inline{ display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
.u-inline select{ padding:5px 8px; }
.u-idcard{ display:block; max-width:100%; max-height:220px; border-radius: var(--radius-sm, 10px); margin:10px 0; cursor:pointer; }
.u-actions{ display:flex; flex-wrap:wrap; gap:8px; margin:12px 0; }
.u-hr{ border:none; border-top:1px solid var(--line, rgba(10,10,10,0.12)); margin:16px 0; }
.u-block-lbl{ font-weight:700; color:var(--muted, rgba(10,10,10,0.55)); font-size:13px; margin-bottom:6px; }
.u-list{ margin:0 0 4px; padding-inline-start:20px; font-size:13px; }
.u-list li{ margin-bottom:5px; }
@media (max-width:480px){
.u-lbl{ min-width:100%; }
}
</style>
</head>
<body>
<div class="page-wrap">
<div class="topbar">
<h2 style="margin:0;">لوحة الأدمن</h2>
</div>
<div class="filters" id="main-tabs">
<button data-t="overview" class="active" onclick="setTab('overview')">نظرة عامة</button>
<button data-t="users" onclick="setTab('users')">كل المستخدمين</button>
<button data-t="projects" onclick="setTab('projects')">كل المشاريع</button>
<button data-t="verif" onclick="setTab('verif')">توثيق الهوية</button>
<button data-t="reports" onclick="setTab('reports')">البلاغات <span id="reports-badge"></span></button>
<button data-t="backups" onclick="setTab('backups')">النسخ الاحتياطية</button>
</div>
<div id="tab-overview"><div class="stats" id="overview-stats"></div></div>
<div id="tab-users" style="display:none">
<div class="filters">
<input type="text" id="search" placeholder="دوّر بالاسم أو الإيميل..." oninput="renderUsers()">
<button data-rf="all" class="active" onclick="setRoleFilter('all')">الكل</button>
<button data-rf="owner" onclick="setRoleFilter('owner')">أصحاب مشاريع</button>
<button data-rf="investor" onclick="setRoleFilter('investor')">مستثمرين</button>
<button data-rf="suspended" onclick="setRoleFilter('suspended')">موقوفين</button>
</div>
<table><thead><tr><th>الاسم</th><th>الإيميل / الهاتف</th><th>الدور</th><th>التوثيق</th><th>مشاريعه</th><th>بلاغات ضده</th><th>الحالة</th><th>تاريخ التسجيل</th></tr></thead>
<tbody id="users-rows"></tbody></table>
</div>
<div id="tab-projects" style="display:none">
<div class="filters">
<button data-pf="all" class="active" onclick="setProjFilter('all')">الكل</button>
<button data-pf="active" onclick="setProjFilter('active')">شغالة</button>
<button data-pf="closed" onclick="setProjFilter('closed')">مقفولة</button>
<button data-pf="featured" onclick="setProjFilter('featured')">مميّزة</button>
</div>
<table><thead><tr><th>المشروع</th><th>صاحبه</th><th>القطاع</th><th>الهدف</th><th>النوايا</th><th>الحالة</th><th>إجراءات</th></tr></thead>
<tbody id="projects-rows"></tbody></table>
</div>
<div id="tab-verif" style="display:none">
<div class="filters" id="filters">
<button data-f="pending" class="active" onclick="setFilter('pending')">قيد الانتظار</button>
<button data-f="approved" onclick="setFilter('approved')">موثّقين</button>
<button data-f="rejected" onclick="setFilter('rejected')">مرفوضين</button>
<button data-f="all" onclick="setFilter('all')">الكل</button>
</div>
<table><thead><tr><th>الاسم</th><th>الإيميل</th><th>الدور</th><th>البطاقة</th><th>الحالة</th><th>إجراء</th></tr></thead>
<tbody id="rows"></tbody></table>
</div>
<div id="tab-reports" style="display:none">
<div class="filters" id="report-filters">
<button data-f="open" class="active" onclick="setReportFilter('open')">مفتوحة</button>
<button data-f="resolved" onclick="setReportFilter('resolved')">اتحلّت</button>
<button data-f="dismissed" onclick="setReportFilter('dismissed')">اتجاهلت</button>
<button data-f="all" onclick="setReportFilter('all')">الكل</button>
</div>
<table><thead><tr><th>المُبلَّغ عنه</th><th>السبب</th><th>التفاصيل</th><th>المُبلِّغ</th><th>الحالة</th><th>إجراء</th></tr></thead>
<tbody id="report-rows"></tbody></table>
</div>
<div id="tab-backups" style="display:none">
<button onclick="runBackup()" style="margin-bottom:12px; padding:8px 16px; cursor:pointer; border-radius:6px; border:1px solid #999;"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 7v10l9 4 9-4V7"/><path d="M12 11v10"/></svg> اعمل نسخة دلوقتي</button>
<table><thead><tr><th>الملف</th><th>الحجم</th><th>التاريخ</th><th>تحميل</th></tr></thead>
<tbody id="backup-rows"></tbody></table>
</div>
</div>
<div id="user-overlay" class="u-overlay" onclick="if(event.target===this) hideUserOverlay();">
<div class="u-modal">
<div class="u-modal-head">
<span>تفاصيل المستخدم</span>
<button class="u-close" onclick="hideUserOverlay()" aria-label="إغلاق">
<svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></svg>
</button>
</div>
<div class="u-modal-body" id="user-detail"></div>
</div>
</div>
<script>
let allRows = [], allReports = [], allUsers = [], allProjects = [];
let filter = 'pending', reportFilter = 'open', roleFilter = 'all', projFilter = 'all';
async function api(path, opts={}){
let res;
try{
res = await fetch(path, opts);
}catch(e){
throw new Error('فشل الاتصال بالسيرفر');
}
if(!res.ok) throw new Error('فشل: ' + path);
return res.json();
}
function showUserOverlay(){
document.getElementById('user-overlay').classList.add('show');
document.body.style.overflow = 'hidden';
}
function hideUserOverlay(){
document.getElementById('user-overlay').classList.remove('show');
document.body.style.overflow = '';
}
function setTab(t){
document.querySelectorAll('#main-tabs button[data-t]').forEach(b=>b.classList.toggle('active', b.dataset.t===t));
['overview','users','projects','verif','reports','backups'].forEach(id=>{
document.getElementById('tab-'+id).style.display = (id===t) ? 'block' : 'none';
});
}
function setFilter(f){ filter=f; document.querySelectorAll('#filters button').forEach(b=>b.classList.toggle('active', b.dataset.f===f)); renderRows(); }
function setReportFilter(f){ reportFilter=f; document.querySelectorAll('#report-filters button').forEach(b=>b.classList.toggle('active', b.dataset.f===f)); renderReports(); }
function setRoleFilter(f){ roleFilter=f; document.querySelectorAll('#tab-users button[data-rf]').forEach(b=>b.classList.toggle('active', b.dataset.rf===f)); renderUsers(); }
function setProjFilter(f){ projFilter=f; document.querySelectorAll('#tab-projects button[data-pf]').forEach(b=>b.classList.toggle('active', b.dataset.pf===f)); renderProjects(); }
async function load(){
let ov;
try{
ov = await api('/api/admin/overview');
}catch(e){ alert(e.message || 'خطأ في التحميل'); return; }
renderOverview(ov);
try{ allUsers = await api('/api/admin/users'); renderUsers(); }catch(e){}
try{ allProjects = await api('/api/admin/projects'); renderProjects(); }catch(e){}
try{ allRows = await api('/api/admin/id-verifications'); renderRows(); }catch(e){}
try{
allReports = await api('/api/admin/reports');
const openCount = allReports.filter(r=>r.status==='open').length;
document.getElementById('reports-badge').textContent = openCount ? `(${openCount})` : '';
renderReports();
}catch(e){}
try{ renderBackups(await api('/api/admin/backups')); }catch(e){}
}
function renderOverview(ov){
document.getElementById('overview-stats').innerHTML = `
<div class="stat"><b>${ov.usersTotal}</b>إجمالي المستخدمين</div>
<div class="stat"><b>${ov.usersVerified}</b>موثّقين</div>
<div class="stat"><b>${ov.usersSuspended}</b>موقوفين</div>
<div class="stat"><b>${ov.usersByRole.owner||0}</b>أصحاب مشاريع</div>
<div class="stat"><b>${ov.usersByRole.investor||0}</b>مستثمرين</div>
<div class="stat"><b>${ov.signups7d}</b>تسجيل جديد (٧ أيام)</div>
<div class="stat"><b>${ov.projectsTotal}</b>إجمالي المشاريع</div>
<div class="stat"><b>${ov.projectsActive}</b>مشاريع شغالة</div>
<div class="stat"><b>${ov.projectsClosed}</b>مشاريع مقفولة</div>
<div class="stat"><b>${ov.interestsTotal}</b>تسجيل نية</div>
<div class="stat"><b><bdi dir="ltr">${Math.round(ov.interestsAmount).toLocaleString('ar-EG')}</bdi></b>إجمالي النوايا (جنيه)</div>
<div class="stat"><b>${ov.appointmentsTotal}</b>مواعيد محجوزة</div>
<div class="stat"><b>${ov.pendingIdReviews}</b>توثيق قيد الانتظار</div>
<div class="stat"><b>${ov.reportsOpen}</b>بلاغات مفتوحة</div>
`;
}
function renderUsers(){
const q = (document.getElementById('search').value||'').trim().toLowerCase();
let rows = allUsers;
if(roleFilter==='suspended') rows = rows.filter(u=>!u.active);
else if(roleFilter!=='all') rows = rows.filter(u=>u.role===roleFilter || u.role==='both');
if(q) rows = rows.filter(u => (u.name||'').toLowerCase().includes(q) || (u.email||'').toLowerCase().includes(q));
const roleLabel = { owner:'صاحب مشروع', investor:'مستثمر', both:'الاتنين', broker:'وسيط' };
if(!rows.length){ document.getElementById('users-rows').innerHTML = `<tr><td colspan="8" class="empty">مفيش نتائج</td></tr>`; return; }
document.getElementById('users-rows').innerHTML = rows.map(u => `
<tr>
<td><a href="#" onclick="openUser('${u.id}');return false;">${u.name}</a></td>
<td>${u.email}<br><small>${u.phone||''}</small></td>
<td>${roleLabel[u.role]||u.role}</td>
<td>${u.id_verified ? '<span class="badge good">موثّق</span>' : `<span class="badge">${idStatusLabel[u.id_status]||u.id_status}</span>`}</td>
<td>${u.projects_count}</td>
<td>${u.reports_count ? `<span class="badge danger">${u.reports_count}</span>` : '0'}</td>
<td>${u.active ? 'نشط' : '<span class="badge danger">موقوف</span>'}</td>
<td>${new Date(u.created_at*1000).toLocaleDateString('ar-EG')}</td>
</tr>
`).join('');
}
function renderProjects(){
let rows = allProjects;
if(projFilter==='active') rows = rows.filter(p=>p.status==='active');
else if(projFilter==='closed') rows = rows.filter(p=>p.status==='closed');
else if(projFilter==='featured') rows = rows.filter(p=>p.featuredActive);
if(!rows.length){ document.getElementById('projects-rows').innerHTML = `<tr><td colspan="7" class="empty">مفيش مشاريع</td></tr>`; return; }
document.getElementById('projects-rows').innerHTML = rows.map(p => `
<tr>
<td><a href="project.html?id=${p.id}" target="_blank">${p.name}</a> ${p.featuredActive?`<span class="badge good">مميّز${p.featured_until ? ' — باقيله ' + Math.max(0, Math.ceil((p.featured_until - Date.now()/1000)/86400)) + ' يوم' : ''}</span>`:''}</td>
<td><a href="#" onclick="openUser('${p.owner_id}');return false;">${p.owner_name}</a></td>
<td>${p.sector}</td>
<td><bdi dir="ltr">${Math.round(p.target).toLocaleString('ar-EG')} ج</bdi></td>
<td>${p.interests_count} (<bdi dir="ltr">${Math.round(p.interests_amount).toLocaleString('ar-EG')} ج</bdi>)</td>
<td class="status-${p.status}">${p.status==='closed'?'مقفول':'شغال'}</td>
<td>
${p.featuredActive
? `<button class="act" onclick="projAct('${p.id}','unfeature')">إلغاء التمييز</button>`
: `<button class="act approve" onclick="projAct('${p.id}','feature')">تمييز <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M12 3l2.6 5.7 6.2.6-4.7 4.2 1.4 6.2L12 16.9 6.5 19.7l1.4-6.2L3.2 9.3l6.2-.6z"/></svg></button>`}
${p.status==='closed'
? `<button class="act" onclick="projAct('${p.id}','reopen')">فتح تاني</button>`
: `<button class="act" onclick="projAct('${p.id}','close')">قفل</button>`}
<button class="act reject" onclick="projAct('${p.id}','delete')">حذف نهائي <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 7h14M9 7V4h6v3"/><path d="M7 7l1 13h8l1-13"/></svg>️</button>
</td>
</tr>
`).join('');
}
async function projAct(id, action){
if(action==='delete' && !confirm('حذف نهائي للمشروع وكل بياناته (النوايا والمواعيد والرسائل)، الإجراء ده مايترجعش. متأكد؟')) return;
let query = '';
if(action === 'feature'){
const input = prompt('تمييز المشروع لمدة كام يوم؟ (1-90)', '14');
if(input === null) return; // المستخدم عمل إلغاء
const days = Number(input);
if(!days || days < 1 || days > 90){ alert('لازم رقم بين 1 و90'); return; }
query = `?days=${days}`;
}
try{ await api(`/api/admin/projects/${id}/${action}${query}`, { method:'POST' }); allProjects = await api('/api/admin/projects'); renderProjects(); }
catch(e){ alert('فشل الإجراء'); }
}
function renderBackups(files){
if(!files.length){ document.getElementById('backup-rows').innerHTML = `<tr><td colspan="4" class="empty">لسه مفيش نسخ احتياطية</td></tr>`; return; }
document.getElementById('backup-rows').innerHTML = files.map(f => `
<tr>
<td>${f.name}</td>
<td>${(f.size/1024).toFixed(1)} KB</td>
<td>${new Date(f.created_at*1000).toLocaleString('ar-EG')}</td>
<td><a href="/api/admin/backups/${f.name}">تحميل</a></td>
</tr>
`).join('');
}
async function runBackup(){
try{ await api('/api/admin/backups/run', { method:'POST' }); renderBackups(await api('/api/admin/backups')); }
catch(e){ alert('فشل عمل النسخة'); }
}
const idStatusLabel = { pending:'قيد الانتظار', approved:'موثّق', rejected:'مرفوض', none:'لسه ملقدمش' };
const reportStatusLabel = { open:'مفتوحة', resolved:'اتحلّت', dismissed:'اتجاهلت' };
function renderRows(){
const roleLabel = { owner:'صاحب مشروع', investor:'مستثمر', both:'الاتنين' };
const rows = filter === 'all' ? allRows : allRows.filter(r => r.id_status === filter);
if(!rows.length){ document.getElementById('rows').innerHTML = `<tr><td colspan="6" class="empty">مفيش طلبات في القسم ده</td></tr>`; return; }
document.getElementById('rows').innerHTML = rows.map(r => `
<tr>
<td><a href="#" onclick="openUser('${r.id}');return false;">${r.name}</a></td>
<td>${r.email}<br><small>${r.phone||''}</small></td>
<td>${roleLabel[r.role]||r.role}</td>
<td>${(r.id_card && r.id_card !== 'none') ? `<img src="/api/admin/id-card/${r.id_card}" onclick="window.open(this.src)" onerror="this.style.display='none'; this.insertAdjacentHTML('afterend','—');">` : '—'}</td>
<td class="status-${r.id_status}">${idStatusLabel[r.id_status]||r.id_status}</td>
<td>
<button class="act approve" ${r.id_status==='approved'?'disabled':''} onclick="act('${r.id}','approve')">توثيق <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg></button>
<button class="act reject" ${r.id_status==='rejected'?'disabled':''} onclick="act('${r.id}','reject')">رفض <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</td>
</tr>
`).join('');
}
async function act(userId, action){
if(action==='reject' && !confirm('متأكد إنك عايز ترفض التوثيق ده؟')) return;
try{ await api(`/api/admin/id-verifications/${userId}/${action}`, { method:'POST' }); load(); }
catch(e){ alert('فشل الإجراء'); }
}
function renderReports(){
const rows = reportFilter === 'all' ? allReports : allReports.filter(r => r.status === reportFilter);
if(!rows.length){ document.getElementById('report-rows').innerHTML = `<tr><td colspan="6" class="empty">مفيش بلاغات في القسم ده</td></tr>`; return; }
document.getElementById('report-rows').innerHTML = rows.map(r => `
<tr>
<td>${r.target_user_id ? `<a href="#" onclick="openUser('${r.target_user_id}');return false;">${r.target_name||r.target_user_id}</a>${r.target_active===0?' <small style="color:#c00;">(موقوف)</small>':''}` : (r.project_name || '—')}</td>
<td>${r.reason}</td>
<td>${r.details||'—'}</td>
<td>${r.reporter_name||'زائر'}${r.reporter_email?`<br><small>${r.reporter_email}</small>`:''}</td>
<td class="status-${r.status}">${reportStatusLabel[r.status]||r.status}</td>
<td>
${r.status!=='resolved'?`<button class="act approve" onclick="reportAct('${r.id}','resolve')">اتحل <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg></button>`:''}
${r.status!=='dismissed'?`<button class="act reject" onclick="reportAct('${r.id}','dismiss')">تجاهل <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M6 6l12 12M18 6L6 18"/></svg></button>`:''}
${r.status!=='open'?`<button class="act" onclick="reportAct('${r.id}','reopen')">فتح تاني</button>`:''}
</td>
</tr>
`).join('');
}
async function reportAct(reportId, action){
try{ await api(`/api/admin/reports/${reportId}/${action}`, { method:'POST' }); load(); }
catch(e){ alert('فشل الإجراء'); }
}
async function openUser(userId){
let u;
try{ u = await api(`/api/admin/users/${userId}`); }
catch(e){ alert('تعذر تحميل بيانات المستخدم'); return; }
const roleLabel = { owner:'صاحب مشروع', investor:'مستثمر', both:'الاتنين', broker:'وسيط' };
document.getElementById('user-detail').innerHTML = `
<h3 class="u-name">${u.name} ${u.active?'':'<span class="badge danger">موقوف</span>'}</h3>
<div class="u-row"><span class="u-lbl">الإيميل</span><span>${u.email}</span></div>
<div class="u-row"><span class="u-lbl">الهاتف</span><span>${u.phone||'—'}</span></div>
<div class="u-row">
<span class="u-lbl">الدور</span>
<span class="u-inline">
${roleLabel[u.role]||u.role}
<select id="role-select">
${['owner','investor','both','broker'].map(r=>`<option value="${r}" ${r===u.role?'selected':''}>${roleLabel[r]}</option>`).join('')}
</select>
<button class="act" onclick="changeRole('${u.id}')">تغيير</button>
</span>
</div>
<div class="u-row">
<span class="u-lbl">توثيق الهوية</span>
<span class="u-inline">${idStatusLabel[u.id_status]||u.id_status} ${u.id_verified?'<span class="badge good">موثّق <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg></span>':''}</span>
</div>
${(u.id_card && u.id_card !== 'none')?`<img class="u-idcard" src="/api/admin/id-card/${u.id_card}" onclick="window.open(this.src)" onerror="this.remove();">`:''}
<div class="u-actions">
${u.id_verified
? `<button class="act reject" onclick="manualVerify('${u.id}', false)">إلغاء التوثيق</button>`
: `<button class="act approve" onclick="manualVerify('${u.id}', true)">توثيق يدوي (من غير بطاقة) <svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg></button>`}
</div>
<div class="u-row"><span class="u-lbl">البايو</span><span>${u.bio||'—'}</span></div>
<div class="u-row"><span class="u-lbl">تاريخ التسجيل</span><span>${new Date(u.created_at*1000).toLocaleDateString('ar-EG')}</span></div>
<hr class="u-hr">
<div class="u-row"><span class="u-lbl">مشاريعه (${u.projects.length})</span><span>${u.projects.map(p=>p.name).join('، ')||'—'}</span></div>
<div class="u-block-lbl">بلاغات ضده (${u.reportsAgainst.length})</div>
<ul class="u-list">${u.reportsAgainst.map(r=>`<li>${r.reason}${reportStatusLabel[r.status]||r.status}${r.details?` (${r.details})`:''}</li>`).join('')||'<li>لا يوجد</li>'}</ul>
<hr class="u-hr">
<div class="u-actions">
${u.active
? `<button class="act reject" onclick="toggleUser('${u.id}','suspend')"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><circle cx="12" cy="12" r="9"/><path d="M6 6l12 12"/></svg> إيقاف الحساب</button>`
: `<button class="act approve" onclick="toggleUser('${u.id}','unsuspend')"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><path d="M5 13l4 4L19 7"/></svg> إعادة تفعيل الحساب</button>`}
<button class="act" onclick="resetUserPassword('${u.id}')"><svg viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;margin-inline-end:3px"><circle cx="8" cy="15" r="4"/><path d="M11 12l9-9M17 6l3 3M14 9l2 2"/></svg> عمل كلمة مرور جديدة</button>
</div>
`;
showUserOverlay();
}
async function changeRole(userId){
const role = document.getElementById('role-select').value;
try{ await api(`/api/admin/users/${userId}/set-role`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({role}) }); openUser(userId); allUsers = await api('/api/admin/users'); renderUsers(); }
catch(e){ alert('فشل تغيير الدور'); }
}
async function manualVerify(userId, verified){
if(!confirm(verified ? 'توثيق يدوي من غير مراجعة بطاقة — استخدمه بس لو اتأكدت من هوية الشخص بطريقة تانية. متأكد؟' : 'إلغاء التوثيق؟')) return;
try{ await api(`/api/admin/users/${userId}/verify-manual`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({verified}) }); openUser(userId); load(); }
catch(e){ alert('فشل الإجراء'); }
}
async function resetUserPassword(userId){
if(!confirm('ده إجراء استثنائي — استخدمه بس بعد ما تتأكد من هوية صاحب الحساب يدويًا (واتساب/إيميل). متأكد؟')) return;
let d;
try{ d = await api(`/api/admin/users/${userId}/reset-password`, { method:'POST' }); }
catch(e){ alert('فشل الإجراء'); return; }
alert(`كلمة المرور المؤقتة: ${d.tempPassword}\nكود الاسترجاع الجديد: ${d.recoveryCode}\n\nابعتهم لصاحب الحساب على طول (واتساب/إيميل) — مش هيتعرضوا تاني.`);
}
async function toggleUser(userId, action){
const msg = action==='suspend' ? 'متأكد إنك عايز توقف الحساب ده؟ هيتقفل خروجه فورًا.' : 'تفعيل الحساب تاني؟';
if(!confirm(msg)) return;
try{ await api(`/api/admin/users/${userId}/${action}`, { method:'POST' }); hideUserOverlay(); load(); }
catch(e){ alert('فشل الإجراء'); }
}
load();
</script>
</body>
</html>