Update: 2026-05-03 17:32:57
This commit is contained in:
476
public/shell.php
476
public/shell.php
@@ -1,476 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>مُصادَق | أتمتة الفواتير الضريبية</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Tailwind CSS (via CDN for simplicity in this prototype) -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--emerald: #10b981;
|
||||
--emerald-dim: rgba(16,185,129,0.12);
|
||||
--emerald-border: rgba(16,185,129,0.25);
|
||||
--bg-base: #080c14;
|
||||
--bg-surface: #0d1424;
|
||||
--bg-elevated: #111827;
|
||||
--bg-hover: rgba(255,255,255,0.04);
|
||||
--border-subtle: rgba(255,255,255,0.06);
|
||||
--border-default: rgba(255,255,255,0.10);
|
||||
--border-strong: rgba(255,255,255,0.18);
|
||||
--text-primary: #f0f6fc;
|
||||
--text-secondary: #8b949e;
|
||||
--text-muted: #484f58;
|
||||
--status-approved: #10b981;
|
||||
--status-pending: #f59e0b;
|
||||
--status-failed: #ef4444;
|
||||
--status-processing: #6366f1;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg-base: #f6f8fa;
|
||||
--bg-surface: #ffffff;
|
||||
--bg-elevated: #f0f3f7;
|
||||
--bg-hover: rgba(0,0,0,0.04);
|
||||
--border-subtle: rgba(0,0,0,0.05);
|
||||
--border-default: rgba(0,0,0,0.10);
|
||||
--text-primary: #0d1117;
|
||||
--text-secondary: #57606a;
|
||||
--text-muted: #afb8c1;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'IBM+Plex+Sans+Arabic', sans-serif;
|
||||
background-color: var(--bg-base);
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mono { font-family: 'IBM+Plex+Mono', monospace; }
|
||||
|
||||
/* Custom Scrollbar */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 10px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
|
||||
|
||||
#sidebar {
|
||||
width: 260px;
|
||||
background-color: var(--bg-surface);
|
||||
border-left: 1px solid var(--border-default);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
#main-layout {
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1.5rem;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s;
|
||||
border-right: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-hover);
|
||||
}
|
||||
|
||||
.nav-active {
|
||||
color: var(--emerald);
|
||||
background-color: var(--emerald-dim);
|
||||
border-right-color: var(--emerald);
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: var(--bg-surface);
|
||||
border: 1px solid var(--border-default);
|
||||
padding: 1.5rem;
|
||||
border-radius: 4px;
|
||||
transition: transform 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--emerald-border);
|
||||
}
|
||||
|
||||
#topbar {
|
||||
background-color: var(--bg-base);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Modal styling */
|
||||
.modal-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--bg-elevated);
|
||||
border: 1px solid var(--border-strong);
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
height: 2px;
|
||||
background: var(--emerald);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body x-data="musadaqApp" x-init="init()">
|
||||
<div id="loading-progress" class="loading-bar" :style="'width: ' + progress + '%'" x-show="loading"></div>
|
||||
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar" x-show="user">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-emerald-500 rounded flex items-center justify-center text-white font-bold">م</div>
|
||||
<h1 class="text-xl font-bold tracking-tight text-white">مُصادَق</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="mt-4 flex-1 overflow-y-auto">
|
||||
<template x-for="item in navItems" :key="item.page">
|
||||
<a href="#"
|
||||
class="nav-link"
|
||||
:class="currentPage === item.page ? 'nav-active' : ''"
|
||||
@click.prevent="navigate(item.page)"
|
||||
x-show="item.roles.includes(user.role)">
|
||||
<span x-html="item.icon" class="ml-3"></span>
|
||||
<span x-text="item.label"></span>
|
||||
</a>
|
||||
</template>
|
||||
</nav>
|
||||
|
||||
<div class="p-6 border-t border-gray-800">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center">
|
||||
<span x-text="user?.name?.charAt(0) || 'U'"></span>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<p class="text-sm font-medium truncate" x-text="user?.name"></p>
|
||||
<p class="text-xs text-gray-500 uppercase" x-text="user?.role"></p>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="logout()" class="w-full py-2 text-sm text-red-400 hover:bg-red-950 rounded transition">تسجيل الخروج</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="main-layout" class="flex-1">
|
||||
<header id="topbar" x-show="user">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold" x-text="pageTitle"></h2>
|
||||
<p class="text-xs text-gray-500">نظام أتمتة الفواتير الرقمي</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button @click="themeToggle()" class="p-2 hover:bg-gray-800 rounded">🌓</button>
|
||||
<div class="h-8 w-px bg-gray-800"></div>
|
||||
<button class="bg-emerald-600 hover:bg-emerald-500 text-white px-4 py-2 rounded text-sm font-medium transition" @click="openUploadModal()">+ فاتورة جديدة</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="content" class="p-8 flex-1 overflow-y-auto">
|
||||
<!-- Dynamic Content Injection -->
|
||||
<div x-show="currentPage === 'dashboard'">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||
<div class="stat-card">
|
||||
<p class="text-gray-500 text-sm mb-2">فواتير الشهر</p>
|
||||
<h3 class="text-3xl font-bold mono" x-text="stats.invoices_this_month || 0"></h3>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<p class="text-gray-500 text-sm mb-2">فواتير معتمدة</p>
|
||||
<h3 class="text-3xl font-bold mono text-emerald-500" x-text="stats.approved_invoices || 0"></h3>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<p class="text-gray-500 text-sm mb-2">عدد الشركات</p>
|
||||
<h3 class="text-3xl font-bold mono" x-text="stats.companies_count || 0"></h3>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<p class="text-gray-500 text-sm mb-2">استهلاك الباقة</p>
|
||||
<h3 class="text-3xl font-bold mono" x-text="(stats.subscription_usage_pct || 0) + '%'"></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2 bg-surface rounded p-6 border border-gray-800">
|
||||
<h4 class="font-bold mb-4">آخر الفواتير</h4>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm text-right">
|
||||
<thead>
|
||||
<tr class="text-gray-500 border-b border-gray-800">
|
||||
<th class="pb-3 pr-2">الشركة</th>
|
||||
<th class="pb-3">الرقم</th>
|
||||
<th class="pb-3">التاريخ</th>
|
||||
<th class="pb-3">الإجمالي</th>
|
||||
<th class="pb-3">الحالة</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="inv in stats.recent_invoices" :key="inv.id">
|
||||
<tr class="border-b border-gray-900 hover:bg-gray-800/50 cursor-pointer" @click="navigate('invoice-detail', {id: inv.id})">
|
||||
<td class="py-3 pr-2" x-text="inv.company_name"></td>
|
||||
<td class="py-3 mono" x-text="inv.invoice_number"></td>
|
||||
<td class="py-3" x-text="inv.invoice_date"></td>
|
||||
<td class="py-3 mono font-bold" x-text="inv.grand_total + ' JOD'"></td>
|
||||
<td class="py-3">
|
||||
<span class="px-2 py-1 rounded-full text-xs"
|
||||
:class="statusColors[inv.status]"
|
||||
x-text="statusLabels[inv.status]"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-surface rounded p-6 border border-gray-800">
|
||||
<h4 class="font-bold mb-4">المساعد الذكي</h4>
|
||||
<div class="bg-gray-900/50 p-4 rounded mb-4">
|
||||
<p class="text-xs text-gray-500 mb-2">🤖 اسأل عن بياناتك:</p>
|
||||
<textarea class="w-full bg-transparent border-none text-sm resize-none focus:ring-0" placeholder="كم فاتورة رفعت الشهر الماضي؟"></textarea>
|
||||
</div>
|
||||
<button class="w-full py-2 bg-gray-800 hover:bg-gray-700 text-sm rounded transition">إرسال ↵</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Companies List -->
|
||||
<div x-show="currentPage === 'companies'">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h3 class="text-2xl font-bold">إدارة الشركات</h3>
|
||||
<button class="bg-emerald-600 px-4 py-2 rounded text-sm" @click="openAddCompanyModal()">+ إضافة شركة</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<template x-for="comp in companies" :key="comp.id">
|
||||
<div class="bg-surface p-6 rounded border border-gray-800 hover:border-emerald-900 transition">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div class="w-12 h-12 bg-gray-800 rounded flex items-center justify-center text-xl font-bold" x-text="comp.name.charAt(0)"></div>
|
||||
<div>
|
||||
<h4 class="font-bold text-lg" x-text="comp.name"></h4>
|
||||
<p class="text-xs text-gray-500 mono" x-text="'TIN: ' + comp.tax_identification_number"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 mt-4 pt-4 border-t border-gray-800">
|
||||
<button class="px-3 py-1 bg-gray-800 rounded text-xs">إعدادات JoFotara</button>
|
||||
<button class="px-3 py-1 bg-gray-800 rounded text-xs">تعديل</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invoice List -->
|
||||
<div x-show="currentPage === 'invoices'">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h3 class="text-2xl font-bold">الفواتير والتدقيق</h3>
|
||||
</div>
|
||||
<div class="bg-surface rounded border border-gray-800 overflow-hidden">
|
||||
<table class="w-full text-sm text-right">
|
||||
<thead class="bg-gray-900/50 text-gray-500 uppercase text-xs">
|
||||
<tr>
|
||||
<th class="p-4">الشركة</th>
|
||||
<th class="p-4">الرقم</th>
|
||||
<th class="p-4">التاريخ</th>
|
||||
<th class="p-4">الإجمالي</th>
|
||||
<th class="p-4">الحالة</th>
|
||||
<th class="p-4">الثقة</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="inv in invoices" :key="inv.id">
|
||||
<tr class="border-t border-gray-800 hover:bg-gray-800/30 cursor-pointer" @click="navigate('invoice-detail', {id: inv.id})">
|
||||
<td class="p-4" x-text="inv.company_name"></td>
|
||||
<td class="p-4 mono" x-text="inv.invoice_number"></td>
|
||||
<td class="p-4" x-text="inv.invoice_date"></td>
|
||||
<td class="p-4 mono font-bold" x-text="inv.grand_total + ' JOD'"></td>
|
||||
<td class="p-4">
|
||||
<span class="px-2 py-1 rounded-full text-xs" :class="statusColors[inv.status]" x-text="statusLabels[inv.status]"></span>
|
||||
</td>
|
||||
<td class="p-4 mono">
|
||||
<span :class="inv.ai_confidence_score < 0.7 ? 'text-red-500' : 'text-emerald-500'" x-text="(inv.ai_confidence_score * 100).toFixed(0) + '%'"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
<div class="modal-overlay fixed inset-0 flex items-center justify-center z-[100]" x-show="showModal" x-cloak>
|
||||
<div class="modal-content p-8" @click.outside="closeModal()">
|
||||
<div id="modal-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('musadaqApp', () => ({
|
||||
user: JSON.parse(localStorage.getItem('user')),
|
||||
currentPage: 'dashboard',
|
||||
currentParams: {},
|
||||
pageTitle: 'لوحة التحكم',
|
||||
loading: false,
|
||||
progress: 0,
|
||||
showModal: false,
|
||||
stats: {},
|
||||
companies: [],
|
||||
invoices: [],
|
||||
|
||||
navItems: [
|
||||
{ page: 'dashboard', label: 'لوحة التحكم', icon: '📊', roles: ['admin', 'super_admin', 'accountant', 'viewer'] },
|
||||
{ page: 'invoices', label: 'الفواتير', icon: '📄', roles: ['admin', 'super_admin', 'accountant', 'viewer'] },
|
||||
{ page: 'companies', label: 'الشركات', icon: '🏢', roles: ['admin', 'super_admin'] },
|
||||
{ page: 'staff', label: 'الموظفون', icon: '👥', roles: ['admin', 'super_admin'] },
|
||||
{ page: 'settings', label: 'الإعدادات', icon: '⚙️', roles: ['admin', 'super_admin', 'accountant', 'viewer'] },
|
||||
],
|
||||
|
||||
statusLabels: {
|
||||
'uploaded': 'مرفوعة',
|
||||
'extracting': 'جاري الاستخراج...',
|
||||
'extracted': 'مستخرجة',
|
||||
'validated': 'مدققة',
|
||||
'approved': 'معتمدة ✓',
|
||||
'rejected': 'مرفوضة ✗'
|
||||
},
|
||||
|
||||
statusColors: {
|
||||
'uploaded': 'bg-gray-700 text-gray-200',
|
||||
'extracting': 'bg-indigo-900 text-indigo-200 animate-pulse',
|
||||
'extracted': 'bg-blue-900 text-blue-200',
|
||||
'validated': 'bg-cyan-900 text-cyan-200',
|
||||
'approved': 'bg-emerald-900 text-emerald-200',
|
||||
'rejected': 'bg-red-900 text-red-200'
|
||||
},
|
||||
|
||||
async init() {
|
||||
if (!this.user) {
|
||||
window.location.href = '/login.php'; // Or handle login view
|
||||
return;
|
||||
}
|
||||
this.navigate('dashboard');
|
||||
},
|
||||
|
||||
async navigate(page, params = {}) {
|
||||
this.currentPage = page;
|
||||
this.currentParams = params;
|
||||
this.pageTitle = this.navItems.find(i => i.page === page)?.label || 'التفاصيل';
|
||||
|
||||
this.loading = true;
|
||||
this.progress = 30;
|
||||
|
||||
try {
|
||||
if (page === 'dashboard') await this.loadStats();
|
||||
if (page === 'companies') await this.loadCompanies();
|
||||
if (page === 'invoices') await this.loadInvoices();
|
||||
|
||||
this.progress = 100;
|
||||
setTimeout(() => { this.loading = false; this.progress = 0; }, 300);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadStats() {
|
||||
const res = await this.apiGet('/dashboard');
|
||||
this.stats = res.data;
|
||||
},
|
||||
|
||||
async loadCompanies() {
|
||||
const res = await this.apiGet('/companies');
|
||||
this.companies = res.data;
|
||||
},
|
||||
|
||||
async loadInvoices() {
|
||||
const res = await this.apiGet('/invoices');
|
||||
this.invoices = res.data;
|
||||
},
|
||||
|
||||
async apiGet(path) {
|
||||
const res = await fetch('/api/v1' + path, {
|
||||
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('access_token') }
|
||||
});
|
||||
if (res.status === 401) this.logout();
|
||||
return await res.json();
|
||||
},
|
||||
|
||||
logout() {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
themeToggle() {
|
||||
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('theme', theme);
|
||||
},
|
||||
|
||||
openUploadModal() {
|
||||
this.showModal = true;
|
||||
document.getElementById('modal-body').innerHTML = `
|
||||
<h2 class="text-xl font-bold mb-6">رفع فاتورة جديدة</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm text-gray-500 mb-1">الشركة</label>
|
||||
<select class="w-full bg-gray-900 border border-gray-700 p-2 rounded">
|
||||
<option>اختر الشركة...</option>
|
||||
${this.companies.map(c => `<option value="${c.id}">${c.name}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="border-2 border-dashed border-gray-700 p-8 rounded text-center hover:border-emerald-500 transition cursor-pointer">
|
||||
<span>📁 اسحب الملف هنا أو اضغط للاختيار</span>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3 pt-4">
|
||||
<button @click="closeModal()" class="px-4 py-2 text-sm text-gray-400">إلغاء</button>
|
||||
<button class="px-6 py-2 bg-emerald-600 text-sm rounded font-bold">رفع ومعالجة</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user