Files
musadaq-saas/public/shell.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>