🚀 مُصادَق: تحديث برمجي جديد 2026-05-03 01:27
This commit is contained in:
196
public/shell.php
196
public/shell.php
@@ -71,99 +71,59 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Load API Client -->
|
|
||||||
<script src="assets/js/api.js"></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ══════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════
|
||||||
// مُصادَق — SPA Router (No frameworks, no race conditions)
|
// مُصادَق — API Client (Inlined for reliability)
|
||||||
// ══════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════
|
||||||
|
const API = {
|
||||||
|
baseUrl: 'api.php', // Use relative path to api.php
|
||||||
|
accessToken: localStorage.getItem('access_token'),
|
||||||
|
async post(path, body) {
|
||||||
|
const headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' };
|
||||||
|
if (this.accessToken) headers['Authorization'] = `Bearer ${this.accessToken}`;
|
||||||
|
const res = await fetch(`${this.baseUrl}${path}`, { method: 'POST', headers, body: JSON.stringify(body) });
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Router ──
|
||||||
const isLoggedIn = () => !!localStorage.getItem('access_token');
|
const isLoggedIn = () => !!localStorage.getItem('access_token');
|
||||||
const pageContent = document.getElementById('page-content');
|
const pageContent = document.getElementById('page-content');
|
||||||
|
|
||||||
// ── Login Page Template ──────────────────────────────────
|
|
||||||
function loginPage() {
|
function loginPage() {
|
||||||
return `
|
return `
|
||||||
<div class="flex flex-col items-center justify-center min-h-[60vh]">
|
<div class="flex flex-col items-center justify-center min-h-[60vh]">
|
||||||
<div class="w-full max-w-md p-8 glass rounded-3xl glow border border-white/10">
|
<div class="w-full max-w-md p-8 glass rounded-3xl glow border border-white/10">
|
||||||
<h2 class="text-3xl font-bold mb-2 text-center">مرحباً بك مجدداً</h2>
|
<h2 class="text-3xl font-bold mb-2 text-center">مرحباً بك مجدداً</h2>
|
||||||
<p class="text-slate-400 text-center mb-8">قم بتسجيل الدخول للوصول إلى لوحة التحكم</p>
|
|
||||||
|
|
||||||
<form id="login-form" class="space-y-6">
|
<form id="login-form" class="space-y-6">
|
||||||
<div>
|
<input type="email" id="login-email" class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-primary" placeholder="البريد الإلكتروني" required>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2">البريد الإلكتروني</label>
|
<input type="password" id="login-password" class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-primary" placeholder="كلمة المرور" required>
|
||||||
<input type="email" id="login-email" class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-primary transition-colors" placeholder="name@company.com" required>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2">كلمة المرور</label>
|
|
||||||
<input type="password" id="login-password" class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 focus:outline-none focus:border-primary transition-colors" placeholder="••••••••" required>
|
|
||||||
</div>
|
|
||||||
<div id="login-error" class="text-red-400 text-sm text-center hidden"></div>
|
<div id="login-error" class="text-red-400 text-sm text-center hidden"></div>
|
||||||
<button type="submit" class="w-full bg-primary hover:bg-primary-dark text-white font-bold py-3 rounded-xl transition-all shadow-lg shadow-primary/20">
|
<button type="submit" class="w-full bg-primary hover:bg-primary-dark text-white font-bold py-3 rounded-xl shadow-lg">دخول</button>
|
||||||
دخول
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Dashboard Page Template ──────────────────────────────
|
|
||||||
function dashboardPage() {
|
function dashboardPage() {
|
||||||
return `
|
return `<div class="space-y-8">
|
||||||
<div class="space-y-8">
|
<h2 class="text-3xl font-bold">لوحة التحكم</h2>
|
||||||
<div class="flex items-center justify-between">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
<h2 class="text-3xl font-bold">لوحة التحكم</h2>
|
<div class="glass p-6 rounded-3xl border border-white/10 glow"><p class="text-slate-400 text-sm">فواتير الشهر</p><h3 class="text-3xl font-bold">1,284</h3></div>
|
||||||
<button class="bg-primary hover:bg-primary-dark text-white px-6 py-2 rounded-xl transition-all shadow-lg shadow-primary/20 flex items-center gap-2">
|
<div class="glass p-6 rounded-3xl border border-white/10"><p class="text-slate-400 text-sm">تمت المصادقة</p><h3 class="text-3xl font-bold text-primary">1,150</h3></div>
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
<div class="glass p-6 rounded-3xl border border-white/10"><p class="text-slate-400 text-sm">قيد المعالجة</p><h3 class="text-3xl font-bold text-yellow-400">94</h3></div>
|
||||||
رفع فاتورة جديدة
|
<div class="glass p-6 rounded-3xl border border-red-500/20"><p class="text-slate-400 text-sm">تنبيهات المخاطر</p><h3 class="text-3xl font-bold text-red-400">4</h3></div>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
||||||
<div class="glass p-6 rounded-3xl border border-white/10 glow">
|
|
||||||
<p class="text-slate-400 text-sm mb-1">فواتير الشهر</p>
|
|
||||||
<h3 class="text-3xl font-bold">1,284</h3>
|
|
||||||
<div class="mt-2 text-xs text-primary flex items-center gap-1">
|
|
||||||
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12 7a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0V8.414l-4.293 4.293a1 1 0 01-1.414 0L8 10.414l-4.293 4.293a1 1 0 01-1.414-1.414l5-5a1 1 0 011.414 0L11 10.586 14.586 7H12z" clip-rule="evenodd"></path></svg>
|
|
||||||
12%+ زيادة عن الشهر الماضي
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="glass p-6 rounded-3xl border border-white/10">
|
|
||||||
<p class="text-slate-400 text-sm mb-1">تمت المصادقة</p>
|
|
||||||
<h3 class="text-3xl font-bold text-primary">1,150</h3>
|
|
||||||
<div class="w-full bg-white/5 h-1.5 rounded-full mt-4 overflow-hidden">
|
|
||||||
<div class="bg-primary h-full rounded-full" style="width: 89%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="glass p-6 rounded-3xl border border-white/10">
|
|
||||||
<p class="text-slate-400 text-sm mb-1">قيد المعالجة</p>
|
|
||||||
<h3 class="text-3xl font-bold text-yellow-400">94</h3>
|
|
||||||
<div class="w-full bg-white/5 h-1.5 rounded-full mt-4 overflow-hidden">
|
|
||||||
<div class="bg-yellow-400 h-full rounded-full" style="width: 25%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="glass p-6 rounded-3xl border border-red-500/20">
|
|
||||||
<p class="text-slate-400 text-sm mb-1">تنبيهات المخاطر</p>
|
|
||||||
<h3 class="text-3xl font-bold text-red-400">4</h3>
|
|
||||||
<p class="mt-2 text-xs text-red-400">تطلب تدخل بشري فوري</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
||||||
<div class="lg:col-span-2 glass p-8 rounded-3xl border border-white/10">
|
|
||||||
<h4 class="font-bold mb-6">تحليل الفواتير الأسبوعي</h4>
|
|
||||||
<canvas id="invoiceChart" height="250"></canvas>
|
|
||||||
</div>
|
|
||||||
<div class="glass p-8 rounded-3xl border border-white/10">
|
|
||||||
<h4 class="font-bold mb-6">توزيع الحالات</h4>
|
|
||||||
<canvas id="statusChart" height="250"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
<div class="glass p-8 rounded-3xl border border-white/10">
|
||||||
|
<h4 class="font-bold mb-6">تحليل الفواتير الأسبوعي</h4>
|
||||||
|
<canvas id="invoiceChart" height="250"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Chart Initialization ─────────────────────────────────
|
|
||||||
function initCharts() {
|
function initCharts() {
|
||||||
const ctx = document.getElementById('invoiceChart');
|
const ctx = document.getElementById('invoiceChart');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
@@ -171,46 +131,13 @@
|
|||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'],
|
labels: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت'],
|
||||||
datasets: [{
|
datasets: [{ label: 'الفواتير', data: [65, 59, 80, 81, 56, 55, 40], borderColor: '#10b981', tension: 0.4 }]
|
||||||
label: 'الفواتير المرفوعة',
|
|
||||||
data: [65, 59, 80, 81, 56, 55, 40],
|
|
||||||
borderColor: '#10b981',
|
|
||||||
tension: 0.4,
|
|
||||||
fill: true,
|
|
||||||
backgroundColor: 'rgba(16, 185, 129, 0.1)'
|
|
||||||
}]
|
|
||||||
},
|
},
|
||||||
options: {
|
options: { scales: { y: { grid: { color: 'rgba(255,255,255,0.05)' } }, x: { grid: { display: false } } } }
|
||||||
responsive: true,
|
|
||||||
plugins: { legend: { display: false } },
|
|
||||||
scales: {
|
|
||||||
y: { beginAtZero: true, grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8' } },
|
|
||||||
x: { grid: { display: false }, ticks: { color: '#94a3b8' } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const statusCtx = document.getElementById('statusChart');
|
|
||||||
if (statusCtx) {
|
|
||||||
new Chart(statusCtx, {
|
|
||||||
type: 'doughnut',
|
|
||||||
data: {
|
|
||||||
labels: ['مقبول', 'مرفوض', 'معلق'],
|
|
||||||
datasets: [{
|
|
||||||
data: [80, 10, 10],
|
|
||||||
backgroundColor: ['#10b981', '#ef4444', '#f59e0b'],
|
|
||||||
borderWidth: 0
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
cutout: '80%',
|
|
||||||
plugins: { legend: { position: 'bottom', labels: { color: '#94a3b8', padding: 20 } } }
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Router ───────────────────────────────────────────────
|
|
||||||
function navigate() {
|
function navigate() {
|
||||||
if (isLoggedIn()) {
|
if (isLoggedIn()) {
|
||||||
pageContent.innerHTML = dashboardPage();
|
pageContent.innerHTML = dashboardPage();
|
||||||
@@ -219,57 +146,38 @@
|
|||||||
} else {
|
} else {
|
||||||
pageContent.innerHTML = loginPage();
|
pageContent.innerHTML = loginPage();
|
||||||
document.getElementById('nav-user').style.display = 'none';
|
document.getElementById('nav-user').style.display = 'none';
|
||||||
|
|
||||||
// Attach login form handler
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const form = document.getElementById('login-form');
|
const form = document.getElementById('login-form');
|
||||||
if (form) {
|
if (form) form.onsubmit = async (e) => {
|
||||||
form.addEventListener('submit', async (e) => {
|
e.preventDefault();
|
||||||
e.preventDefault();
|
const email = document.getElementById('login-email').value;
|
||||||
const email = document.getElementById('login-email').value;
|
const password = document.getElementById('login-password').value;
|
||||||
const password = document.getElementById('login-password').value;
|
try {
|
||||||
const errorEl = document.getElementById('login-error');
|
const res = await API.post('/auth/login', { email, password });
|
||||||
|
localStorage.setItem('access_token', res.data.access_token);
|
||||||
try {
|
API.accessToken = res.data.access_token;
|
||||||
const result = await window.API.post('/auth/login', { email, password });
|
navigate();
|
||||||
if (result.success) {
|
} catch (err) {
|
||||||
localStorage.setItem('access_token', result.data.access_token);
|
const errEl = document.getElementById('login-error');
|
||||||
if (result.data.refresh_token) {
|
errEl.textContent = err.message || 'خطأ في الدخول';
|
||||||
localStorage.setItem('refresh_token', result.data.refresh_token);
|
errEl.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
window.API.accessToken = result.data.access_token;
|
};
|
||||||
navigate();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
errorEl.textContent = err.message || 'خطأ في البريد الإلكتروني أو كلمة المرور';
|
|
||||||
errorEl.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── AI Query Handler ─────────────────────────────────────
|
document.getElementById('ai-query').onkeydown = async (e) => {
|
||||||
document.getElementById('ai-query').addEventListener('keydown', async (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
const query = e.target.value;
|
const query = e.target.value;
|
||||||
const answerEl = document.getElementById('ai-answer');
|
document.getElementById('ai-answer').textContent = 'جاري التحليل...';
|
||||||
if (!query) return;
|
|
||||||
|
|
||||||
answerEl.textContent = 'جاري التحليل...';
|
|
||||||
e.target.value = '';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await window.API.post('/ai/query', { query });
|
const res = await API.post('/ai/query', { query });
|
||||||
answerEl.textContent = res.data.answer;
|
document.getElementById('ai-answer').textContent = res.data.answer;
|
||||||
} catch (err) {
|
} catch (err) { document.getElementById('ai-answer').textContent = 'خطأ في الاتصال.'; }
|
||||||
answerEl.textContent = 'عذراً، حدث خطأ أثناء معالجة طلبك.';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// ── Start the app ────────────────────────────────────────
|
|
||||||
navigate();
|
navigate();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user