477 lines
22 KiB
PHP
477 lines
22 KiB
PHP
<!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>
|