Files
musadaq-saas/public/shell.php

1073 lines
68 KiB
PHP

<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مُصادَق | لوحة التحكم v2.0</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@400;700;900&family=Noto+Kufi+Arabic:wght@400;700;900&display=swap" rel="stylesheet">
<style>
:root {
--bb-bg: #000000;
--bb-panel: #111111;
--bb-border: #222222;
--bb-text: #ffffff;
--bb-dim: #888888;
--bb-green: #00ff00;
--bb-red: #ff3333;
--bb-yellow: #ffff00;
--bb-blue: #0088ff;
}
body {
background-color: var(--bb-bg);
color: var(--bb-text);
font-family: 'Inter', 'Noto Kufi Arabic', sans-serif;
font-size: 13px;
overflow: hidden;
}
.bb-mono { font-family: 'JetBrains Mono', monospace; }
.bb-panel { background: var(--bb-panel); border: 1px solid var(--bb-border); }
.bb-table th { border-bottom: 2px solid var(--bb-border); color: var(--bb-dim); font-weight: 900; text-transform: uppercase; padding: 8px; font-size: 11px; }
.bb-table td { border-bottom: 1px solid var(--bb-border); padding: 8px; vertical-align: middle; }
.bb-btn { border: 1px solid var(--bb-dim); padding: 4px 12px; transition: all 0.2s; cursor: pointer; }
.bb-btn:hover:not(:disabled) { background: var(--bb-text); color: var(--bb-bg); }
.bb-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.bb-btn-primary { border-color: var(--bb-green); color: var(--bb-green); }
.bb-btn-primary:hover:not(:disabled) { background: var(--bb-green); color: var(--bb-bg); }
.bb-stat { border-right: 2px solid var(--bb-green); padding-right: 12px; }
.bb-scroll::-webkit-scrollbar { width: 4px; height: 4px; }
.bb-scroll::-webkit-scrollbar-track { background: transparent; }
.bb-scroll::-webkit-scrollbar-thumb { background: var(--bb-border); }
.bb-nav-item { border-right: 3px solid transparent; transition: all 0.2s; }
.bb-nav-item.active { border-right-color: var(--bb-green); background: rgba(0, 255, 0, 0.05); }
@keyframes ticker { 0% { transform: translateX(100%); } 100% { transform: translateX(-100%); } }
.ticker-wrap { overflow: hidden; background: var(--bb-panel); border-bottom: 1px solid var(--bb-border); white-space: nowrap; direction: ltr; }
.ticker { display: inline-block; animation: ticker 30s linear infinite; }
.status-pill { font-size: 10px; font-weight: 900; padding: 2px 6px; border: 1px solid currentColor; }
</style>
</head>
<body class="h-screen flex flex-col">
<!-- Top Bar / Ticker -->
<div class="ticker-wrap h-8 flex items-center bb-mono text-[11px]">
<div class="px-4 border-r border-bb-border text-bb-green font-bold">النظام نشط</div>
<div class="ticker flex gap-8 pr-8">
<span class="text-bb-dim">MARKET: <span class="text-white">AMM/JOD</span></span>
<span class="text-bb-dim">LATENCY: <span class="text-bb-green">14ms</span></span>
<span class="text-bb-dim">CPU: <span class="text-bb-green">12%</span></span>
<span class="text-bb-dim">REDIS_JTI: <span class="text-bb-blue">VERIFIED</span></span>
<span class="text-bb-dim">NONCE_CHECK: <span class="text-bb-green">ACTIVE</span></span>
<span class="text-bb-dim">ENCRYPTION: <span class="text-bb-green">AES-256-GCM</span></span>
</div>
</div>
<!-- Main Shell -->
<div class="flex-1 flex overflow-hidden">
<!-- Sidebar Navigation -->
<nav class="w-64 bb-panel border-r-0 flex flex-col">
<div class="p-6 border-b border-bb-border">
<div class="flex items-center gap-3 mb-2">
<div class="w-2 h-2 bg-bb-green animate-pulse rounded-full"></div>
<span class="text-xl font-black tracking-tighter">مُصادَق.v2</span>
</div>
<div class="text-[10px] text-bb-dim bb-mono" id="user-display">جاري التحميل...</div>
</div>
<div class="flex-1 py-4 bb-mono text-[12px]">
<a href="#" onclick="navigateTo('dashboard')" id="nav-dashboard" class="bb-nav-item active flex items-center gap-3 px-6 py-3 hover:bg-white/5">
<span class="text-bb-dim">01</span> لوحة القيادة
</a>
<a href="#" onclick="navigateTo('invoices')" id="nav-invoices" class="bb-nav-item flex items-center gap-3 px-6 py-3 hover:bg-white/5">
<span class="text-bb-dim">02</span> الفواتير والعمليات
</a>
<a href="#" onclick="navigateTo('companies')" id="nav-companies" class="bb-nav-item flex items-center gap-3 px-6 py-3 hover:bg-white/5">
<span class="text-bb-dim">03</span> الشركات (الكيانات)
</a>
<a href="#" onclick="navigateTo('users')" id="nav-users" class="bb-nav-item flex items-center gap-3 px-6 py-3 hover:bg-white/5">
<span class="text-bb-dim">04</span> المستخدمين والصلاحيات
</a>
<a href="#" onclick="navigateTo('risk')" id="nav-risk" class="bb-nav-item flex items-center gap-3 px-6 py-3 hover:bg-white/5">
<span class="text-bb-dim">05</span> مؤشرات المخاطر
</a>
<div class="mt-8 px-6 text-[10px] text-bb-dim font-bold uppercase tracking-widest">إدارة النظام</div>
<a href="#" onclick="navigateTo('settings')" id="nav-settings" class="bb-nav-item flex items-center gap-3 px-6 py-3 hover:bg-white/5">
<span class="text-bb-dim">09</span> الإعدادات ومفاتيح الربط
</a>
<a href="#" onclick="navigateTo('admin')" id="nav-admin" class="bb-nav-item hidden flex items-center gap-3 px-6 py-3 hover:bg-white/5 text-bb-blue">
<span class="text-bb-dim">10</span> الإدارة العليا (SUPER ADMIN)
</a>
</div>
<div class="p-6 border-t border-bb-border">
<button onclick="logout()" class="w-full bb-btn text-bb-red text-[11px] font-bold">تسجيل الخروج (DISCONNECT)</button>
</div>
</nav>
<!-- Content Area -->
<main class="flex-1 flex flex-col overflow-hidden bg-black">
<!-- Header -->
<header class="h-14 bb-panel border-r-0 border-l-0 flex items-center justify-between px-8 bg-black/50 backdrop-blur-md">
<div class="flex items-center gap-6">
<h2 id="page-title" class="text-lg font-black tracking-tighter">لوحة القيادة</h2>
<div class="h-4 w-[1px] bg-bb-border"></div>
<div class="flex items-center gap-4 text-[11px] bb-mono">
<span class="text-bb-dim">الحالة:</span>
<span class="text-bb-green">تداول حي (LIVE)</span>
<span class="text-bb-dim">المنطقة:</span>
<span class="text-white">الأردن / عمّان</span>
</div>
</div>
<div class="flex items-center gap-4">
<button onclick="showUploadModal()" class="bb-btn bb-btn-primary text-[11px] font-black">+ فاتورة جديدة</button>
</div>
</header>
<!-- Main Content Grid -->
<div id="content" class="flex-1 overflow-y-auto bb-scroll p-6">
<!-- Content will be injected here -->
</div>
</main>
</div>
<!-- Modals Overlay -->
<div id="modal-overlay" class="fixed inset-0 bg-black/90 backdrop-blur-sm z-[100] hidden items-center justify-center p-6">
<div id="modal-content" class="w-full max-w-2xl bb-panel p-8 animate-in"></div>
</div>
<!-- Auth Overlay -->
<div id="auth-overlay" class="fixed inset-0 bg-black z-[200] hidden items-center justify-center p-6">
<div id="auth-content" class="w-full max-w-sm bb-panel p-10"></div>
</div>
<!-- Toast Notifications -->
<div id="toast-container" class="fixed bottom-8 left-8 z-[300] space-y-2"></div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const API = {
baseUrl: 'index.php?route=/api/v1',
get token() { return localStorage.getItem('access_token'); },
async req(method, path, body = null, files = false) {
const headers = { 'Accept': 'application/json' };
if (this.token) headers['Authorization'] = `Bearer ${this.token}`;
if (!files && body) { headers['Content-Type'] = 'application/json'; body = JSON.stringify(body); }
try {
const res = await fetch(`${this.baseUrl}${path}`, { method, headers, body });
let data;
const isJson = res.headers.get('content-type')?.includes('application/json');
if (isJson) {
data = await res.json();
} else {
const text = await res.text();
console.error('Non-JSON response:', text);
throw { error: { message_ar: 'استجابة غير متوقعة من الخادم (تأكد من الرابط)' } };
}
if (!res.ok) {
if (res.status === 401 && !path.includes('/auth/login')) logout();
throw data;
}
return data;
} catch (err) {
showToast(err.error?.message_ar || err.error?.message || err.message || 'خطأ غير متوقع في النظام', 'error');
throw err;
}
},
get(p) { return this.req('GET', p); },
post(p, b) { return this.req('POST', p, b); },
delete(p) { return this.req('DELETE', p); },
upload(p, fd) { return this.req('POST', p, fd, true); }
};
function showToast(msg, type = 'success') {
const container = document.getElementById('toast-container');
const t = document.createElement('div');
const color = type === 'success' ? 'var(--bb-green)' : 'var(--bb-red)';
t.className = `bb-panel p-4 bb-mono text-[11px] border-r-4`;
t.style.borderRightColor = color;
t.innerHTML = `<span style="color: ${color}">[${type.toUpperCase()}]</span> ${msg}`;
container.appendChild(t);
setTimeout(() => t.remove(), 4000);
}
function logout() { localStorage.clear(); window.location.reload(); }
async function navigateTo(page) {
document.querySelectorAll('.bb-nav-item').forEach(l => l.classList.remove('active'));
document.getElementById(`nav-${page}`)?.classList.add('active');
const content = document.getElementById('content');
content.innerHTML = '<div class="h-full flex items-center justify-center bb-mono animate-pulse text-bb-dim">جاري تحميل البيانات... (TERMINAL BUSY)</div>';
try {
if (page === 'dashboard') await renderDashboard();
else if (page === 'invoices') await renderInvoices();
else if (page === 'companies') await renderCompanies();
else if (page === 'users') await renderUsers();
else if (page === 'risk') await renderRisk();
else if (page === 'settings') await renderSettings();
else if (page === 'admin') await renderAdmin();
else if (page === 'admin_tenants') await renderAdminTenants();
else if (page === 'admin_queue') await renderAdminQueue();
} catch (e) {
console.error(e);
content.innerHTML = '<div class="h-full flex items-center justify-center bb-mono text-bb-red">فشل تحميل الصفحة. يرجى التحقق من الشبكة أو الخادم.</div>';
}
}
// ── View Renderers ───────────────────────────────────────
async function renderDashboard() {
document.getElementById('page-title').textContent = 'لوحة القيادة';
const res = await API.get('/dashboard');
const { data: s } = res;
document.getElementById('content').innerHTML = `
<div class="grid grid-cols-4 gap-6 mb-8">
<div class="bb-panel p-6 bb-stat">
<div class="text-bb-dim text-[10px] bb-mono uppercase mb-2">إجمالي فواتير الشهر</div>
<div class="text-4xl font-black">${s.total_this_month || 0}</div>
<div class="text-[10px] mt-2 text-bb-green">+12.4% عن السابق</div>
</div>
<div class="bb-panel p-6 bb-stat" style="border-right-color: var(--bb-blue)">
<div class="text-bb-dim text-[10px] bb-mono uppercase mb-2">استهلاك الباقة</div>
<div class="text-4xl font-black">${s.subscription_usage || 0}%</div>
<div class="w-full h-1 bg-bb-border mt-4"><div class="h-full bg-bb-blue" style="width: ${s.subscription_usage || 0}%"></div></div>
</div>
<div class="bb-panel p-6 bb-stat" style="border-right-color: var(--bb-yellow)">
<div class="text-bb-dim text-[10px] bb-mono uppercase mb-2">قيد المعالجة (AI)</div>
<div class="text-4xl font-black">${s.pending_extraction || 0}</div>
<div class="text-[10px] mt-2 text-bb-yellow">حالة الطابور: ممتاز</div>
</div>
<div class="bb-panel p-6 bb-stat" style="border-right-color: var(--bb-green)">
<div class="text-bb-dim text-[10px] bb-mono uppercase mb-2">نسبة القبول (جوفوترة)</div>
<div class="text-4xl font-black">98.2%</div>
<div class="text-[10px] mt-2 text-bb-green">التوافق الضريبي: عالي</div>
</div>
</div>
<div class="grid grid-cols-3 gap-6">
<div class="col-span-2 bb-panel p-8">
<h3 class="text-sm font-black mb-6 uppercase tracking-widest border-b border-bb-border pb-4">موجز العمليات الحديثة</h3>
<table class="w-full bb-table text-right">
<thead>
<tr>
<th class="text-right">الشركة</th>
<th class="text-right">رقم الفاتورة</th>
<th class="text-right">المجموع</th>
<th class="text-center">الحالة</th>
<th class="text-left">دقة الذكاء الاصطناعي</th>
</tr>
</thead>
<tbody>
${(s.recent_invoices || []).map(i => `
<tr onclick="renderInvoiceDetail('${i.id}')" class="hover:bg-white/5 cursor-pointer">
<td class="font-bold">${i.company_name}</td>
<td class="bb-mono text-xs">${i.invoice_number || '---'}</td>
<td class="font-bold text-bb-green">${i.grand_total} دينار</td>
<td class="text-center">
<span class="status-pill ${getStatusColor(i.status)}">${translateStatus(i.status)}</span>
</td>
<td class="text-left bb-mono text-xs">${i.ai_confidence_score ? (i.ai_confidence_score * 100).toFixed(1) + '%' : '---'}</td>
</tr>
`).join('')}
${(!s.recent_invoices || s.recent_invoices.length === 0) ? `<tr><td colspan="5" class="text-center text-bb-dim py-4">لا توجد عمليات مسجلة</td></tr>` : ''}
</tbody>
</table>
</div>
<div class="bb-panel p-8 flex flex-col">
<h3 class="text-sm font-black mb-6 uppercase tracking-widest border-b border-bb-border pb-4">توزيع حالات الفواتير</h3>
<div class="flex-1 flex items-center justify-center">
<canvas id="statusChart"></canvas>
</div>
</div>
</div>
`;
if (s.status_distribution && s.status_distribution.length > 0) {
new Chart(document.getElementById('statusChart'), {
type: 'doughnut',
data: {
labels: s.status_distribution.map(x => translateStatus(x.status)),
datasets: [{
data: s.status_distribution.map(x => x.count),
backgroundColor: ['#00ff00', '#ffff00', '#ff3333', '#0088ff'],
borderWidth: 0
}]
},
options: {
cutout: '80%',
plugins: { legend: { position: 'bottom', labels: { color: '#888888', font: { size: 10, family: 'Noto Kufi Arabic' } } } }
}
});
}
}
async function renderInvoices() {
document.getElementById('page-title').textContent = 'الفواتير والعمليات';
const res = await API.get('/invoices');
document.getElementById('content').innerHTML = `
<div class="bb-panel p-8">
<div class="flex justify-between items-center mb-8">
<div class="flex gap-4">
<input type="text" placeholder="بحث برقم الفاتورة..." class="bb-panel bg-black px-4 py-2 text-xs bb-mono w-64 text-right">
<select class="bb-panel bg-black px-4 py-2 text-xs bb-mono">
<option>جميع الحالات</option>
<option>مقبولة</option>
<option>قيد الانتظار</option>
</select>
</div>
<div class="text-[10px] text-bb-dim bb-mono">الإجمالي: ${res.data.length} | الصفحة: 1/1</div>
</div>
<table class="w-full bb-table text-right">
<thead>
<tr>
<th class="text-right">التاريخ</th>
<th class="text-right">الشركة</th>
<th class="text-right">الرقم المتسلسل</th>
<th class="text-right">الضريبة</th>
<th class="text-right">الإجمالي</th>
<th class="text-center">الحالة</th>
<th class="text-left">إجراءات</th>
</tr>
</thead>
<tbody>
${res.data.map(i => `
<tr class="hover:bg-white/5 transition">
<td class="bb-mono text-xs text-left" dir="ltr">${i.invoice_date || '---'}</td>
<td class="font-bold">${i.company_name}</td>
<td class="bb-mono text-xs text-left" dir="ltr">${i.invoice_number || '---'}</td>
<td class="text-bb-dim">${i.tax_amount}</td>
<td class="font-bold text-bb-green">${i.grand_total}</td>
<td class="text-center"><span class="status-pill ${getStatusColor(i.status)}">${translateStatus(i.status)}</span></td>
<td class="text-left">
<button onclick="renderInvoiceDetail('${i.id}')" class="bb-btn text-[10px] font-black">فتح وعرض</button>
</td>
</tr>
`).join('')}
${res.data.length === 0 ? `<tr><td colspan="7" class="text-center py-4 text-bb-dim">لا توجد فواتير</td></tr>` : ''}
</tbody>
</table>
</div>
`;
}
async function renderInvoiceDetail(id) {
const res = await API.get(`/invoices/${id}`);
const i = res.data;
document.getElementById('content').innerHTML = `
<div class="grid grid-cols-2 gap-8 h-full">
<div class="bb-panel flex flex-col overflow-hidden">
<div class="p-4 border-b border-bb-border flex justify-between items-center bg-white/5">
<span class="bb-mono text-[10px] uppercase">المستند الأصلي (المصدر)</span>
<a href="/api/v1/invoices/${i.id}/file" target="_blank" class="text-bb-blue text-[10px] hover:underline font-bold">عرض خارجي ↗</a>
</div>
<div class="flex-1 bg-zinc-900 flex items-center justify-center p-4">
${i.original_file_path && i.original_file_path.endsWith('.pdf') ?
`<iframe src="/api/v1/invoices/${i.id}/file" class="w-full h-full border-0"></iframe>` :
`<img src="/api/v1/invoices/${i.id}/file" class="max-w-full max-h-full shadow-2xl">`}
</div>
</div>
<div class="flex flex-col gap-8">
<div class="bb-panel p-8">
<div class="flex justify-between items-start mb-8">
<div>
<h2 class="text-2xl font-black tracking-tighter mb-2">${i.supplier_name || 'كيان غير معروف'}</h2>
<div class="flex gap-4 text-[10px] bb-mono">
<span class="text-bb-dim">رقم الفاتورة:</span> <span class="text-white">${i.invoice_number || 'قيد الانتظار'}</span>
<span class="text-bb-dim">الرقم الضريبي:</span> <span class="text-bb-green">${i.supplier_tin || '---'}</span>
</div>
</div>
<div class="flex flex-col gap-2 items-end">
<span class="status-pill ${getStatusColor(i.status)} text-sm px-4 py-2">${translateStatus(i.status)}</span>
<div class="text-[10px] bb-mono text-bb-dim">دقة الذكاء الاصطناعي: ${i.ai_confidence_score ? (i.ai_confidence_score * 100).toFixed(2) + '%' : '---'}</div>
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-8 bb-mono">
<div class="bb-panel p-4">
<div class="text-bb-dim text-[9px] mb-1 uppercase">بيانات المشتري</div>
<div class="font-bold">${i.buyer_name || 'مشتري عام (نقدي)'}</div>
<div class="text-[9px] text-bb-dim mt-1">الرقم الضريبي: ${i.buyer_tin || '---'}</div>
</div>
<div class="bb-panel p-4">
<div class="text-bb-dim text-[9px] mb-1 uppercase">بيانات الوقت والتاريخ</div>
<div class="font-bold">${i.invoice_date || '---'}</div>
<div class="text-[9px] text-bb-dim mt-1 text-left" dir="ltr">ISSUED_AT: ${i.created_at}</div>
</div>
</div>
<div class="bb-scroll overflow-y-auto max-h-64 mb-8">
<table class="w-full bb-table text-right text-xs">
<thead>
<tr>
<th class="text-right">الوصف (السلعة/الخدمة)</th>
<th class="text-center">الكمية</th>
<th class="text-right">السعر الإفرادي</th>
<th class="text-right">الإجمالي</th>
</tr>
</thead>
<tbody>
${i.lines && i.lines.length > 0 ? i.lines.map(l => `
<tr>
<td class="text-bb-dim">${l.description}</td>
<td class="text-center bb-mono">${l.quantity}</td>
<td class="bb-mono">${l.unit_price}</td>
<td class="font-bold text-bb-green">${l.line_total}</td>
</tr>
`).join('') : `<tr><td colspan="4" class="text-center text-bb-dim">لا توجد أصناف مستخرجة أو قيد المعالجة</td></tr>`}
</tbody>
</table>
</div>
<div class="border-t border-bb-border pt-6 bb-mono">
<div class="flex justify-between text-xs mb-2">
<span class="text-bb-dim">المجموع الفرعي</span>
<span>${i.subtotal || 0} دينار</span>
</div>
<div class="flex justify-between text-xs mb-2">
<span class="text-bb-dim">قيمة الضريبة المضافة (16%)</span>
<span class="text-bb-yellow">${i.tax_amount || 0} دينار</span>
</div>
<div class="flex justify-between text-xl font-black mt-4 border-t border-bb-border pt-4">
<span class="text-bb-dim">المجموع الكلي</span>
<span class="text-bb-green">${i.grand_total || 0} دينار</span>
</div>
</div>
</div>
<div class="flex gap-4">
<button onclick="submitToJoFotara('${i.id}')" class="flex-1 bb-btn bb-btn-primary py-4 font-black" ${i.status === 'approved' ? 'disabled' : ''}>إرسال لنظام جوفوترة</button>
<button onclick="navigateTo('invoices')" class="bb-btn px-8 font-black">العودة للقائمة</button>
</div>
</div>
</div>
`;
}
async function submitToJoFotara(id) {
if(!confirm('هل أنت متأكد من إرسال واعتماد هذه الفاتورة رسمياً عبر بوابة جوفوترة؟ لا يمكن التراجع.')) return;
try {
await API.post(`/invoices/${id}/submit`);
showToast('جاري إرسال الفاتورة...');
setTimeout(() => renderInvoiceDetail(id), 2000);
} catch(e) {}
}
async function renderCompanies() {
document.getElementById('page-title').textContent = 'الشركات (الكيانات التابعة)';
const res = await API.get('/companies');
document.getElementById('content').innerHTML = `
<div class="flex justify-end mb-6">
<button onclick="showAddCompanyModal()" class="bb-btn bb-btn-primary font-black">+ تسجيل شركة جديدة</button>
</div>
<div class="grid grid-cols-3 gap-6">
${res.data.map(c => `
<div class="bb-panel p-8 border-t-4 border-t-bb-green">
<div class="flex justify-between items-start mb-6">
<h3 class="text-xl font-black tracking-tighter uppercase">${c.name}</h3>
<span class="status-pill ${c.is_jofotara_linked ? 'text-bb-green border-bb-green' : 'text-bb-red border-bb-red'}">${c.is_jofotara_linked ? 'مربوط' : 'غير مربوط'}</span>
</div>
<div class="space-y-3 bb-mono text-xs">
<div class="flex justify-between"><span class="text-bb-dim">الرقم الضريبي:</span> <span>${c.tax_identification_number}</span></div>
<div class="flex justify-between"><span class="text-bb-dim">المدينة:</span> <span>${c.city || '---'}</span></div>
</div>
<div class="mt-8 flex gap-2">
<button onclick="showJoFotaraModal('${c.id}')" class="flex-1 bb-btn text-[10px] font-black">إعدادات الربط الحكومي</button>
</div>
</div>
`).join('')}
${res.data.length === 0 ? `<div class="col-span-3 text-center py-10 text-bb-dim">لم يتم إضافة أي شركات بعد</div>` : ''}
</div>
`;
}
async function renderUsers() {
document.getElementById('page-title').textContent = 'المستخدمين والصلاحيات';
const res = await API.get('/users');
document.getElementById('content').innerHTML = `
<div class="bb-panel p-8">
<div class="flex justify-between items-center mb-8">
<h3 class="text-sm font-black uppercase tracking-widest">صلاحيات الوصول</h3>
<button onclick="showAddUserModal()" class="bb-btn bb-btn-primary font-black">+ إضافة مستخدم</button>
</div>
<table class="w-full bb-table text-right">
<thead>
<tr>
<th class="text-right">الاسم الكامل</th>
<th class="text-right text-left" dir="ltr">البريد الإلكتروني</th>
<th class="text-right">نوع الصلاحية</th>
<th class="text-center">2FA (التحقق الثنائي)</th>
<th class="text-left">إجراءات</th>
</tr>
</thead>
<tbody>
${res.data.map(u => `
<tr>
<td class="font-bold">${u.name}</td>
<td class="bb-mono text-xs text-bb-dim text-left" dir="ltr">${u.email}</td>
<td><span class="status-pill text-bb-blue">${translateRole(u.role)}</span></td>
<td class="text-center">${u.totp_enabled ? '<span class="text-bb-green">مفعل</span>' : '<span class="text-bb-red">معطل</span>'}</td>
<td class="text-left">
<button onclick="deleteUser('${u.id}')" class="text-bb-red hover:underline text-[10px] bb-mono">إلغاء الصلاحية (إزالة)</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
}
async function renderRisk() {
document.getElementById('page-title').textContent = 'مؤشرات المخاطر';
try {
const res = await API.get('/risk');
document.getElementById('content').innerHTML = `
<div class="bb-panel p-8">
<div class="flex justify-between items-center mb-8">
<h3 class="text-sm font-black uppercase tracking-widest">تنبيهات التدقيق والمخاطر الضريبية</h3>
</div>
<table class="w-full bb-table text-right">
<thead>
<tr>
<th class="text-right">التاريخ</th>
<th class="text-right">الشركة</th>
<th class="text-right">الفاتورة</th>
<th class="text-right">مستوى الخطر</th>
<th class="text-right">السبب</th>
<th class="text-center">الحالة</th>
</tr>
</thead>
<tbody>
${res.data.map(r => `
<tr class="hover:bg-white/5 transition">
<td class="bb-mono text-xs text-left" dir="ltr">${r.created_at}</td>
<td class="font-bold">${r.company_name}</td>
<td class="bb-mono text-xs">${r.invoice_number || '---'}</td>
<td class="${getRiskColor(r.risk_level)} font-bold">${translateRisk(r.risk_level)}</td>
<td class="text-bb-dim text-xs">${r.reason}</td>
<td class="text-center">${r.is_resolved ? '<span class="text-bb-green">محلول</span>' : '<span class="text-bb-red font-bold animate-pulse">قيد الانتظار</span>'}</td>
</tr>
`).join('')}
${res.data.length === 0 ? `<tr><td colspan="6" class="text-center py-10 text-bb-dim">لا توجد مخاطر مسجلة حتى الآن</td></tr>` : ''}
</tbody>
</table>
</div>
`;
} catch (e) {
document.getElementById('content').innerHTML = `<div class="bb-panel p-8 text-bb-dim text-center">خدمة المخاطر غير متاحة حالياً أو قيد الإنشاء</div>`;
}
}
async function renderSettings() {
document.getElementById('page-title').textContent = 'الإعدادات ومفاتيح الربط';
const u = (await API.get('/auth/me')).data;
document.getElementById('content').innerHTML = `
<div class="max-w-2xl space-y-8">
<div class="bb-panel p-8 border-r-4 border-r-bb-blue">
<h3 class="text-lg font-black mb-2 tracking-tighter">الحماية المتقدمة (التحقق الثنائي 2FA)</h3>
<p class="text-bb-dim text-[11px] mb-6">يضيف التحقق بخطوتين طبقة حماية إضافية لحسابك.</p>
<div id="2fa-area">
${u.totp_enabled ?
`<button onclick="disable2FA()" class="bb-btn text-bb-red font-black border-bb-red">إيقاف التحقق الثنائي</button>` :
`<button onclick="start2FA()" class="bb-btn bb-btn-primary font-black">تفعيل التحقق الثنائي الآن</button>`}
</div>
</div>
<div class="bb-panel p-8 border-r-4 border-r-bb-yellow">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-black tracking-tighter">مفاتيح الربط (API Keys)</h3>
<button onclick="createApiKey()" class="bb-btn bb-btn-primary text-[10px] font-black">+ إنشاء مفتاح جديد</button>
</div>
<div id="api-keys-list" class="space-y-4">
<div class="bb-mono text-[10px] animate-pulse text-bb-dim">جاري جلب المفاتيح...</div>
</div>
</div>
</div>
`;
loadApiKeys();
}
async function renderAdmin() {
document.getElementById('page-title').textContent = 'الإدارة العليا للأنظمة (SUPER ADMIN)';
const s = (await API.get('/admin/stats')).data;
const h = (await API.get('/admin/health')).data;
document.getElementById('content').innerHTML = `
<div class="grid grid-cols-2 gap-8">
<div class="bb-panel p-8">
<h3 class="text-sm font-black mb-6 border-b border-bb-border pb-4 uppercase">صحة البنية التحتية</h3>
<div class="grid grid-cols-2 gap-4 bb-mono text-xs">
<div class="bb-panel p-4">قواعد البيانات: <span class="${h.db==='ok'?'text-bb-green':'text-bb-red'}">${h.db.toUpperCase()}</span></div>
<div class="bb-panel p-4">كاش Redis: <span class="${h.redis==='ok'?'text-bb-green':'text-bb-red'}">${h.redis.toUpperCase()}</span></div>
<div class="bb-panel p-4">المهام المنتظرة: <span class="text-bb-yellow">${h.queue_pending}</span></div>
<div class="bb-panel p-4">المهام الفاشلة: <span class="text-bb-red">${h.queue_dead}</span></div>
</div>
<div class="mt-8 flex gap-2">
<button onclick="navigateTo('admin_tenants')" class="flex-1 bb-btn text-[10px] font-black">إدارة الحسابات (Tenants)</button>
<button onclick="navigateTo('admin_queue')" class="flex-1 bb-btn text-[10px] font-black">مراقب الطابور (Queue)</button>
</div>
</div>
<div class="bb-panel p-8">
<h3 class="text-sm font-black mb-6 border-b border-bb-border pb-4 uppercase">إحصائيات المنصة الكلية</h3>
<div class="space-y-4">
<div class="flex justify-between items-end">
<span class="bb-mono text-bb-dim text-[10px]">المستأجرين (TENANTS)</span>
<span class="text-3xl font-black text-white">${s.tenants}</span>
</div>
<div class="flex justify-between items-end">
<span class="bb-mono text-bb-dim text-[10px]">الفواتير المتراكمة</span>
<span class="text-3xl font-black text-bb-green">${s.invoices}</span>
</div>
<div class="flex justify-between items-end">
<span class="bb-mono text-bb-dim text-[10px]">إجمالي المستخدمين</span>
<span class="text-3xl font-black text-bb-blue">${s.users}</span>
</div>
</div>
</div>
</div>
`;
}
async function renderAdminTenants() {
document.getElementById('page-title').textContent = 'إدارة حسابات المستأجرين (Tenants)';
const res = await API.get('/admin/tenants');
document.getElementById('content').innerHTML = `
<div class="bb-panel p-8">
<button onclick="navigateTo('admin')" class="bb-btn mb-6 font-black">< العودة</button>
<table class="w-full bb-table text-right">
<thead><tr><th>الاسم</th><th>البريد</th><th>الحالة</th><th>تاريخ التسجيل</th></tr></thead>
<tbody>
${res.data.map(t => `
<tr>
<td class="font-bold">${t.name}</td>
<td class="bb-mono text-left" dir="ltr">${t.email}</td>
<td><span class="status-pill text-bb-green">${t.status}</span></td>
<td class="bb-mono text-xs text-left" dir="ltr">${t.created_at}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
}
async function renderAdminQueue() {
document.getElementById('page-title').textContent = 'مراقب طابور المهام (Queue)';
const res = await API.get('/admin/queue');
document.getElementById('content').innerHTML = `
<div class="bb-panel p-8">
<button onclick="navigateTo('admin')" class="bb-btn mb-6 font-black">< العودة</button>
<table class="w-full bb-table text-right text-xs">
<thead><tr><th>المعرف</th><th>النوع</th><th>المحاولات</th><th>الحالة</th><th>تاريخ الجدولة</th></tr></thead>
<tbody>
${(res.data.failed_jobs || []).map(q => `
<tr>
<td class="bb-mono text-left" dir="ltr">${q.id.substring(0,8)}...</td>
<td class="font-bold text-bb-blue">${q.type}</td>
<td class="bb-mono text-center">${q.attempts}</td>
<td><span class="status-pill ${q.status==='pending'?'text-bb-yellow':'text-bb-red'}">${q.status}</span></td>
<td class="bb-mono text-left" dir="ltr">${q.scheduled_at}</td>
</tr>
`).join('')}
${(!res.data.failed_jobs || res.data.failed_jobs.length === 0) ? `<tr><td colspan="5" class="text-center py-4">لا توجد مهام معلقة أو فاشلة</td></tr>` : ''}
</tbody>
</table>
</div>
`;
}
// ── Helper Functions ─────────────────────────────────────
function getStatusColor(s) {
const m = { 'approved': 'text-bb-green border-bb-green', 'rejected': 'text-bb-red border-bb-red', 'extracted': 'text-bb-blue border-bb-blue', 'uploaded': 'text-bb-dim border-bb-dim', 'validation_failed': 'text-bb-red border-bb-red' };
return m[s] || 'text-bb-yellow border-bb-yellow';
}
function translateStatus(s) {
const m = { 'uploaded': 'مرفوعة (تنتظر الذكاء)', 'extracting': 'قيد الاستخراج', 'extracted': 'تم الاستخراج', 'validated': 'تم التدقيق', 'validation_failed': 'فشل التدقيق', 'submitting': 'جاري الإرسال', 'approved': 'تم الاعتماد', 'rejected': 'مرفوضة' };
return m[s] || s;
}
function translateRole(r) {
const m = { 'super_admin': 'مسؤول النظام', 'admin': 'مسؤول الكيان', 'accountant': 'محاسب', 'employee': 'موظف', 'viewer': 'مراقب' };
return m[r] || r;
}
function translateRisk(r) {
const m = { 'low': 'منخفض', 'medium': 'متوسط', 'high': 'عالي', 'critical': 'حرج' };
return m[r] || r;
}
function getRiskColor(r) {
const m = { 'low': 'text-bb-green', 'medium': 'text-bb-yellow', 'high': 'text-orange-500', 'critical': 'text-bb-red' };
return m[r] || 'text-white';
}
async function loadApiKeys() {
try {
const res = await API.get('/api-keys');
document.getElementById('api-keys-list').innerHTML = res.data.map(k => `
<div class="bb-panel p-4 flex justify-between items-center bg-black/40">
<div>
<div class="font-bold bb-mono text-xs">${k.name}</div>
<div class="text-[9px] text-bb-dim bb-mono mt-1 text-left" dir="ltr">PUB: ${k.public_key}</div>
</div>
<button onclick="revokeApiKey('${k.id}')" class="text-bb-red text-[9px] bb-mono hover:underline font-bold">إلغاء وإيقاف</button>
</div>
`).join('') || '<div class="text-bb-dim text-xs bb-mono">لا توجد مفاتيح مسجلة</div>';
} catch(e) {}
}
// ── Modals Functions ─────────────────────────────────────
async function showAddCompanyModal() {
showModal(`
<h2 class="text-xl font-black mb-8 border-b border-bb-border pb-4 uppercase">تسجيل شركة / كيان جديد</h2>
<form id="add-company-form" class="space-y-4">
<input type="text" name="name" placeholder="اسم الشركة" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
<input type="text" name="tax_identification_number" placeholder="الرقم الضريبي (TIN)" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<input type="text" name="city" placeholder="المدينة (مثال: عمان)" class="w-full bb-panel bg-black p-3 bb-mono text-sm">
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bb-btn bb-btn-primary font-black py-4">حفظ وإضافة</button>
<button type="button" onclick="closeModal()" class="bb-btn px-10 font-black">إلغاء</button>
</div>
</form>
`);
document.getElementById('add-company-form').onsubmit = async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
try {
await API.post('/companies', Object.fromEntries(fd));
showToast('تمت إضافة الشركة بنجاح');
closeModal();
navigateTo('companies');
} catch(err) {}
};
}
async function showJoFotaraModal(companyId) {
showModal(`
<h2 class="text-xl font-black mb-8 border-b border-bb-border pb-4 uppercase">إعدادات الربط الحكومي (جوفوترة)</h2>
<form id="jofotara-form" class="space-y-4">
<input type="text" name="client_id" placeholder="Client ID (رقم العميل)" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<input type="password" name="secret_key" placeholder="Secret Key (المفتاح السري)" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bb-btn bb-btn-primary font-black py-4">حفظ بيانات الربط</button>
<button type="button" onclick="closeModal()" class="bb-btn px-10 font-black">إلغاء</button>
</div>
</form>
`);
document.getElementById('jofotara-form').onsubmit = async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
try {
await API.post(`/companies/${companyId}/jofotara`, Object.fromEntries(fd));
showToast('تم حفظ بيانات الربط بنجاح');
closeModal();
navigateTo('companies');
} catch(err) {}
};
}
async function showAddUserModal() {
try {
const res = await API.get('/companies');
const comps = res.data;
showModal(`
<h2 class="text-xl font-black mb-8 border-b border-bb-border pb-4 uppercase">إضافة مستخدم للوحة</h2>
<form id="add-user-form" class="space-y-4">
<input type="text" name="name" placeholder="الاسم الكامل" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
<input type="email" name="email" placeholder="البريد الإلكتروني" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<input type="password" name="password" placeholder="كلمة المرور الافتراضية" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
<select name="role" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
<option value="accountant">محاسب (Accountant) - يقدر يرفع فواتير</option>
<option value="viewer">مراقب (Viewer) - فقط مشاهدة</option>
<option value="admin">مسؤول نظام (Admin) - كافة الصلاحيات</option>
</select>
<select name="assigned_company_id" class="w-full bb-panel bg-black p-3 bb-mono text-sm">
<option value="">كافة الشركات التابعة</option>
${comps.map(c => `<option value="${c.id}">${c.name}</option>`).join('')}
</select>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bb-btn bb-btn-primary font-black py-4">إضافة المستخدم</button>
<button type="button" onclick="closeModal()" class="bb-btn px-10 font-black">إلغاء</button>
</div>
</form>
`);
document.getElementById('add-user-form').onsubmit = async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
try {
await API.post('/users', Object.fromEntries(fd));
showToast('تمت إضافة المستخدم بنجاح');
closeModal();
navigateTo('users');
} catch(err) {}
};
} catch(e) {}
}
async function deleteUser(id) {
if(!confirm('تحذير: هل أنت متأكد من إلغاء صلاحية هذا المستخدم وإزالته نهائياً؟')) return;
try {
await API.delete(`/users/${id}`);
showToast('تمت الإزالة بنجاح');
navigateTo('users');
} catch(err) {}
}
async function createApiKey() {
showModal(`
<h2 class="text-xl font-black mb-8 border-b border-bb-border pb-4 uppercase">توليد مفتاح ربط جديد</h2>
<form id="api-key-form" class="space-y-4">
<input type="text" name="name" placeholder="اسم النظام / التطبيق المستفيد" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bb-btn bb-btn-primary font-black py-4">إنشاء</button>
<button type="button" onclick="closeModal()" class="bb-btn px-10 font-black">إلغاء</button>
</div>
</form>
`);
document.getElementById('api-key-form').onsubmit = async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
try {
const res = await API.post('/api-keys', Object.fromEntries(fd));
showModal(`
<h2 class="text-xl font-black mb-8 border-b border-bb-border pb-4 uppercase text-bb-green">تم التوليد بنجاح</h2>
<div class="space-y-4 bb-mono text-sm">
<div class="p-4 bb-panel bg-white/5 border-bb-dim text-left" dir="ltr">
<div class="text-bb-dim text-[10px] mb-1">Public Key</div>
<div class="break-all font-bold text-white">${res.data.public_key}</div>
</div>
<div class="p-4 bb-panel bg-red-900/20 border-bb-red text-left" dir="ltr">
<div class="text-bb-red text-[10px] mb-1 text-right" dir="rtl">Secret Key (تنبيه: انسخه الآن، لن يعرض مجدداً)</div>
<div class="break-all font-bold text-bb-red select-all py-2">${res.data.secret}</div>
</div>
<button onclick="closeModal(); loadApiKeys()" class="w-full bb-btn bb-btn-primary py-4 mt-4 font-black">لقد قمت بنسخ المفتاح، إغلاق</button>
</div>
`);
} catch(err) {}
};
}
async function revokeApiKey(id) {
if(!confirm('سيتم تعطيل الوصول عبر هذا المفتاح فوراً. هل أنت متأكد؟')) return;
try {
await API.delete(`/api-keys/${id}`);
showToast('تم إلغاء المفتاح');
loadApiKeys();
} catch(err) {}
}
async function start2FA() {
try {
const res = await API.post('/auth/2fa/enable', {});
showModal(`
<h2 class="text-xl font-black mb-8 border-b border-bb-border pb-4 uppercase">تفعيل التحقق الثنائي (2FA)</h2>
<div class="flex flex-col items-center space-y-6">
<img src="${res.data.qr_url}" alt="QR Code" class="w-48 h-48 bg-white p-2">
<div class="text-[10px] text-bb-dim bb-mono text-center">امسح الكود عبر تطبيق Google Authenticator<br>أو أدخل الرمز السري يدوياً:<br><span class="text-white select-all">${res.data.secret}</span></div>
<form id="verify-2fa-form" class="w-full space-y-4">
<input type="hidden" name="secret" value="${res.data.secret}">
<input type="text" name="code" placeholder="أدخل الرمز المكون من 6 أرقام" class="w-full bb-panel bg-black p-3 bb-mono text-center text-xl tracking-[1em]" dir="ltr" required>
<button type="submit" class="w-full bb-btn bb-btn-primary font-black py-4">تأكيد التفعيل</button>
</form>
</div>
`);
document.getElementById('verify-2fa-form').onsubmit = async (e) => {
e.preventDefault();
try {
await API.post('/auth/2fa/verify', Object.fromEntries(new FormData(e.target)));
showToast('تم تفعيل التحقق الثنائي بنجاح');
closeModal();
navigateTo('settings');
} catch(err) {}
};
} catch(err) {}
}
async function disable2FA() {
if(!confirm('تحذير: هل أنت متأكد من رغبتك بإزالة طبقة الحماية الثنائية عن حسابك؟')) return;
try {
await API.post('/auth/2fa/disable', {});
showToast('تم تعطيل حماية 2FA');
navigateTo('settings');
} catch(err) {}
}
async function showUploadModal() {
try {
const res = await API.get('/companies');
const comps = res.data;
showModal(`
<h2 class="text-xl font-black mb-8 border-b border-bb-border pb-4 uppercase tracking-tighter">رفع فاتورة جديدة للتدقيق</h2>
<form id="upload-form" class="space-y-6">
<div class="space-y-2">
<label class="text-[10px] bb-mono text-bb-dim">الكيان التابع (الشركة المرفوع عنها)</label>
<select name="company_id" class="w-full bb-panel bg-black p-3 bb-mono text-sm border-bb-dim" required>
<option value="">-- اختر الشركة --</option>
${comps.map(c => `<option value="${c.id}">${c.name}</option>`).join('')}
</select>
</div>
<div class="space-y-2">
<label class="text-[10px] bb-mono text-bb-dim">مستند الفاتورة (PDF / صور)</label>
<div class="bb-panel p-10 border-dashed text-center bg-white/5 border-bb-dim">
<input type="file" name="file" class="bb-mono text-xs w-full cursor-pointer" required>
</div>
</div>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bb-btn bb-btn-primary font-black py-4">بدء المعالجة بالذكاء الاصطناعي</button>
<button type="button" onclick="closeModal()" class="bb-btn px-10 font-black">إلغاء</button>
</div>
</form>
`);
document.getElementById('upload-form').onsubmit = async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
try {
const btn = e.target.querySelector('button[type="submit"]');
btn.disabled = true; btn.textContent = 'جاري الرفع والجدولة...';
await API.upload('/invoices/upload', fd);
showToast('تم رفع الفاتورة، سيقوم النظام بالاستخراج فوراً');
closeModal();
navigateTo('invoices');
} catch(err) {
const btn = e.target.querySelector('button[type="submit"]');
btn.disabled = false; btn.textContent = 'بدء المعالجة بالذكاء الاصطناعي';
}
};
} catch (e) {
showToast('فشل جلب الشركات لرفع الفاتورة', 'error');
}
}
// ── Auth Logic ───────────────────────────────────────────
async function initApp() {
if (!API.token) {
renderLogin();
return;
}
try {
const u = (await API.get('/auth/me')).data;
document.getElementById('user-display').textContent = `المنفذ: ${u.name} | الصلاحية: ${translateRole(u.role)}`;
if (u.role === 'super_admin') document.getElementById('nav-admin').classList.remove('hidden');
navigateTo('dashboard');
} catch(e) { renderLogin(); }
}
function renderLogin() {
const overlay = document.getElementById('auth-overlay');
overlay.classList.replace('hidden', 'flex');
document.getElementById('auth-content').innerHTML = `
<div class="text-center mb-10">
<h1 class="text-3xl font-black tracking-tighter mb-2">مُصادَق للمؤسسات</h1>
<p class="text-bb-dim text-[10px] bb-mono uppercase">بوابة الدخول المشفرة (TERMINAL)</p>
</div>
<form id="login-form" class="space-y-6">
<input type="email" name="email" placeholder="البريد الإلكتروني (IDENTITY)" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<input type="password" name="password" placeholder="كلمة المرور (PASSWORD)" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<button type="submit" class="w-full bb-btn bb-btn-primary py-4 font-black text-lg tracking-widest">تأسيس الاتصال (LOGIN)</button>
</form>
<div class="mt-8 text-center text-bb-dim text-[10px] bb-mono">
لا تملك صلاحية وصول؟ <a href="#" onclick="renderRegister()" class="text-white hover:underline font-bold">تسجيل كيان جديد</a>
</div>
`;
document.getElementById('login-form').onsubmit = async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
try {
const btn = e.target.querySelector('button[type="submit"]');
btn.disabled = true; btn.textContent = 'جاري التحقق...';
const res = await API.post('/auth/login', Object.fromEntries(fd));
if (res.requires_2fa) render2FAChallenge(res.temp_token);
else saveAuth(res.data);
} catch(err) {
const btn = e.target.querySelector('button[type="submit"]');
btn.disabled = false; btn.textContent = 'تأسيس الاتصال (LOGIN)';
}
};
}
function render2FAChallenge(tempToken) {
document.getElementById('auth-content').innerHTML = `
<div class="text-center mb-10">
<h1 class="text-3xl font-black tracking-tighter mb-2 text-bb-yellow">مطلوب خطوة إضافية</h1>
<p class="text-bb-dim text-[10px] bb-mono uppercase">المصادقة الثنائية (2FA Challenge)</p>
</div>
<form id="2fa-form" class="space-y-6">
<input type="text" name="code" placeholder="000000" class="w-full bb-panel bg-black p-3 bb-mono text-center text-3xl tracking-[0.5em] text-bb-yellow font-bold" dir="ltr" required autofocus>
<button type="submit" class="w-full bb-btn bb-btn-primary py-4 font-black">تحقق ودخول</button>
</form>
`;
document.getElementById('2fa-form').onsubmit = async (e) => {
e.preventDefault();
const code = new FormData(e.target).get('code');
try {
const res = await fetch('index.php?route=/api/v1/auth/2fa/verify_login', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${tempToken}` },
body: JSON.stringify({ code })
});
const data = await res.json();
if (res.ok) saveAuth(data.data);
else showToast(data.error?.message_ar || 'رمز غير صحيح', 'error');
} catch(err) { showToast('حدث خطأ في الاتصال', 'error'); }
};
}
function renderRegister() {
document.getElementById('auth-content').innerHTML = `
<div class="text-center mb-10">
<h1 class="text-3xl font-black tracking-tighter mb-2">تأسيس مساحة عمل</h1>
<p class="text-bb-dim text-[10px] bb-mono uppercase">مستأجر جديد (New Tenant)</p>
</div>
<form id="register-form" class="space-y-4">
<input type="text" name="name" placeholder="اسمك الكامل" class="w-full bb-panel bg-black p-3 bb-mono text-sm" required>
<input type="email" name="email" placeholder="البريد الإلكتروني" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<input type="password" name="password" placeholder="كلمة المرور القوية" class="w-full bb-panel bg-black p-3 bb-mono text-sm text-left" dir="ltr" required>
<button type="submit" class="w-full bb-btn bb-btn-primary py-4 font-black tracking-widest mt-4">إنشاء وبدء الاستخدام</button>
</form>
<div class="mt-8 text-center text-bb-dim text-[10px] bb-mono">
لديك مساحة عمل مسبقاً؟ <a href="#" onclick="renderLogin()" class="text-white hover:underline font-bold">العودة لتسجيل الدخول</a>
</div>
`;
document.getElementById('register-form').onsubmit = async (e) => {
e.preventDefault();
const fd = new FormData(e.target);
try {
const btn = e.target.querySelector('button[type="submit"]');
btn.disabled = true; btn.textContent = 'جاري التأسيس...';
const res = await API.post('/auth/register', Object.fromEntries(fd));
saveAuth(res.data);
} catch(err) {
const btn = e.target.querySelector('button[type="submit"]');
btn.disabled = false; btn.textContent = 'إنشاء وبدء الاستخدام';
}
};
}
function saveAuth(data) {
localStorage.setItem('access_token', data.access_token);
window.location.reload();
}
function showModal(html) {
document.getElementById('modal-content').innerHTML = html;
document.getElementById('modal-overlay').classList.replace('hidden', 'flex');
}
function closeModal() { document.getElementById('modal-overlay').classList.replace('flex', 'hidden'); }
initApp();
</script>
</body>
</html>