Files
musadaq-saas/public/shell.php
2026-05-04 17:59:11 +03:00

602 lines
41 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
<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">
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
<style>
:root {
--emerald: #10b981;
--bg-base: #080c14;
--bg-surface: #0d1424;
--border-default: rgba(255,255,255,0.1);
--text-primary: #f0f6fc;
}
body { font-family: 'IBM Plex Sans Arabic', sans-serif; background-color: var(--bg-base); color: var(--text-primary); }
[x-cloak] { display: none !important; }
.glass { background: rgba(13, 20, 36, 0.7); backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.05); }
.scrollbar-hide::-webkit-scrollbar { display: none; }
</style>
</head>
<body x-data="app" x-init="init()">
<!-- Global Error Toast -->
<div x-show="globalError" x-cloak
class="fixed top-4 left-1/2 -translate-x-1/2 z-[100] bg-red-900/90 border border-red-500 text-white px-6 py-3 rounded-lg shadow-2xl max-w-lg text-center glass"
x-text="globalError"
x-transition
@click="globalError = ''">
</div>
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<aside class="w-64 bg-surface border-l border-gray-800 flex flex-col">
<div class="p-6">
<h1 class="text-2xl font-bold text-emerald-500 tracking-tight">مُصادَق</h1>
<p class="text-[10px] text-gray-500 mt-1 uppercase tracking-widest">Enterprise SaaS</p>
</div>
<nav class="flex-1 px-4 space-y-2 mt-4">
<a href="#" @click="setPage('dashboard')" class="flex items-center gap-3 p-3 rounded-lg transition-all" :class="page==='dashboard'?'bg-emerald-500/10 text-emerald-500 shadow-[inset_0_0_20px_rgba(16,185,129,0.05)]':'text-gray-400 hover:bg-gray-800'">
<span class="text-xl">📊</span>
<span class="font-medium">لوحة التحكم</span>
</a>
<a x-show="user?.role === 'super_admin'" href="#" @click="setPage('tenants')" class="flex items-center gap-3 p-3 rounded-lg transition-all" :class="page==='tenants'?'bg-emerald-500/10 text-emerald-500':'text-gray-400 hover:bg-gray-800'">
<span class="text-xl">🏢</span>
<span class="font-medium">المكاتب المحاسبية</span>
</a>
<a x-show="user?.role !== 'viewer'" href="#" @click="setPage('companies')" class="flex items-center gap-3 p-3 rounded-lg transition-all" :class="page==='companies'?'bg-emerald-500/10 text-emerald-500':'text-gray-400 hover:bg-gray-800'">
<span class="text-xl">🏭</span>
<span class="font-medium">الشركات</span>
</a>
<a x-show="user?.role !== 'viewer'" href="#" @click="setPage('invoices')" class="flex items-center gap-3 p-3 rounded-lg transition-all" :class="page==='invoices'?'bg-emerald-500/10 text-emerald-500':'text-gray-400 hover:bg-gray-800'">
<span class="text-xl">📄</span>
<span class="font-medium">الفواتير</span>
</a>
<a x-show="user?.role === 'super_admin' || user?.role === 'admin'" href="#" @click="setPage('users')" class="flex items-center gap-3 p-3 rounded-lg transition-all" :class="page==='users'?'bg-emerald-500/10 text-emerald-500':'text-gray-400 hover:bg-gray-800'">
<span class="text-xl">👥</span>
<span class="font-medium">المستخدمون</span>
</a>
</nav>
<div class="p-6 border-t border-gray-800">
<div class="flex items-center gap-3 mb-4">
<div class="w-8 h-8 rounded-full bg-emerald-500/20 flex items-center justify-center text-emerald-500 font-bold text-xs" x-text="user?.name?.[0]"></div>
<div class="truncate">
<p class="text-xs font-bold truncate" x-text="user?.name"></p>
<p class="text-[10px] text-gray-500 uppercase" x-text="user?.role"></p>
</div>
</div>
<button @click="logout()" class="w-full text-right text-red-400 text-xs font-bold hover:text-red-300 transition">🚪 تسجيل الخروج</button>
</div>
</aside>
<!-- Main -->
<main class="flex-1 overflow-y-auto p-10 bg-[#060910]">
<header class="mb-10 flex justify-between items-end">
<div>
<h2 class="text-3xl font-bold tracking-tight" x-text="title()"></h2>
<p class="text-gray-500 mt-1 text-sm" x-text="subtitle()"></p>
</div>
<div class="flex items-center gap-3">
<button x-show="page==='tenants'" @click="showAddTenantModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-5 py-2.5 rounded-lg text-sm font-bold shadow-lg shadow-emerald-900/20 transition-all active:scale-95"> إضافة مكتب</button>
<button x-show="page==='users'" @click="showAddModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-5 py-2.5 rounded-lg text-sm font-bold shadow-lg shadow-emerald-900/20 transition-all active:scale-95"> إضافة مستخدم</button>
<button x-show="page==='companies'" @click="showAddCompanyModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-5 py-2.5 rounded-lg text-sm font-bold shadow-lg shadow-emerald-900/20 transition-all active:scale-95"> إضافة شركة</button>
<button x-show="page==='invoices'" @click="showUploadModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-5 py-2.5 rounded-lg text-sm font-bold shadow-lg shadow-emerald-900/20 transition-all active:scale-95">📤 رفع فواتير</button>
</div>
</header>
<div id="content" x-transition>
<!-- Invoices -->
<div x-show="page === 'invoices'">
<div class="bg-surface border border-gray-800 rounded-2xl overflow-hidden shadow-2xl">
<table class="w-full text-right border-collapse">
<thead class="bg-gray-900/50">
<tr>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الشركة</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المورد</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">التاريخ</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المجموع</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الحالة</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">إجراءات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800">
<tr x-show="invoices.length === 0"><td colspan="6" class="p-12 text-center text-gray-600">لا توجد فواتير بعد</td></tr>
<template x-for="inv in invoices" :key="inv.id">
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="p-5">
<p class="font-bold text-emerald-500" x-text="inv.company_name"></p>
</td>
<td class="p-5">
<p class="text-sm font-medium" x-text="inv.supplier_name"></p>
<p class="text-[10px] text-gray-500 mt-1" x-text="inv.supplier_tin"></p>
</td>
<td class="p-5 text-sm text-gray-400" x-text="inv.invoice_date || '-'"></td>
<td class="p-5">
<span class="font-mono text-sm" x-text="parseFloat(inv.grand_total).toLocaleString()"></span>
<span class="text-[10px] text-gray-600 mr-1">JOD</span>
</td>
<td class="p-5">
<span class="px-2 py-1 rounded-md text-[10px] font-bold uppercase"
:class="inv.status==='extracted'?'bg-blue-900/30 text-blue-400':(inv.status==='approved'?'bg-emerald-900/30 text-emerald-400':'bg-gray-800 text-gray-400')"
x-text="inv.status"></span>
</td>
<td class="p-5">
<button @click="viewInvoice(inv.id)" class="text-gray-500 hover:text-emerald-400 p-2 rounded-lg hover:bg-emerald-400/10 transition">👁️</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- Dashboard -->
<div x-show="page === 'dashboard'">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="p-8 bg-surface border border-gray-800 rounded-2xl shadow-xl relative overflow-hidden group">
<div class="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity"><span class="text-4xl">📄</span></div>
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest">إجمالي الفواتير</p>
<p class="text-4xl font-bold mt-3" x-text="stats.total || 0"></p>
</div>
<div class="p-8 bg-surface border border-gray-800 rounded-2xl shadow-xl relative overflow-hidden group">
<div class="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity"><span class="text-4xl text-yellow-500"></span></div>
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest">بانتظار الاعتماد</p>
<p class="text-4xl font-bold mt-3 text-yellow-500" x-text="stats.pending || 0"></p>
</div>
<div class="p-8 bg-surface border border-gray-800 rounded-2xl shadow-xl relative overflow-hidden group">
<div class="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity"><span class="text-4xl text-emerald-500"></span></div>
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest">تم الاعتماد</p>
<p class="text-4xl font-bold mt-3 text-emerald-500" x-text="stats.approved || 0"></p>
</div>
</div>
</div>
<!-- Companies -->
<div x-show="page === 'companies'">
<div class="bg-surface border border-gray-800 rounded-2xl overflow-hidden shadow-2xl">
<table class="w-full text-right divide-y divide-gray-800">
<thead class="bg-gray-900/50">
<tr>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الشركة</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الأرقام الرسمية</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">العنوان</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المكتب</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">إحصائيات</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">إجراءات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800">
<tr x-show="companies.length === 0"><td colspan="6" class="p-12 text-center text-gray-600">لا توجد شركات بعد</td></tr>
<template x-for="c in companies" :key="c.id">
<tr class="hover:bg-white/[0.01] transition-colors group">
<td class="p-5"><p class="font-bold text-emerald-500" x-text="c.name"></p></td>
<td class="p-5">
<p class="text-xs text-gray-400">TIN: <span class="font-mono" x-text="c.tax_identification_number"></span></p>
<p class="text-xs text-gray-400">CRN: <span class="font-mono" x-text="c.commercial_registration_number"></span></p>
</td>
<td class="p-5 text-sm text-gray-500" x-text="c.address"></td>
<td class="p-5 text-xs text-gray-500" x-text="c.tenant_name || '-'"></td>
<td class="p-5">
<button @click="showCompanyStats(c.id)" class="text-xs bg-gray-800 hover:bg-emerald-600/20 text-emerald-400 px-3 py-1.5 rounded-lg border border-gray-700 hover:border-emerald-500/30 transition-all">📊 عرض</button>
</td>
<td class="p-5 flex gap-2">
<button x-show="user?.role === 'super_admin' || user?.role === 'admin'" @click="confirmDeleteCompany(c)" class="text-gray-500 hover:text-red-500 p-2 rounded-lg hover:bg-red-500/10 transition">🗑️</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- Users List -->
<div x-show="page === 'users'">
<div class="bg-surface border border-gray-800 rounded-2xl overflow-hidden shadow-2xl">
<table class="w-full text-right divide-y divide-gray-800">
<thead class="bg-gray-900/50">
<tr>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المستخدم</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المكتب</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الدور</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الحالة</th>
<th class="p-5 text-xs font-bold text-gray-500 uppercase">إجراءات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800">
<tr x-show="users.length === 0"><td colspan="5" class="p-12 text-center text-gray-600">لا يوجد مستخدمون بعد</td></tr>
<template x-for="u in users" :key="u.id">
<tr class="hover:bg-white/[0.01] transition-colors group">
<td class="p-5">
<p class="font-bold text-emerald-500" x-text="u.name"></p>
<p class="text-xs text-gray-500" x-text="u.email"></p>
</td>
<td class="p-5 text-sm text-gray-400" x-text="u.tenant_name || '-'"></td>
<td class="p-5"><span class="px-2 py-1 bg-gray-800 rounded text-[10px] font-bold uppercase" x-text="u.role"></span></td>
<td class="p-5"><span class="w-2 h-2 rounded-full inline-block" :class="u.is_active?'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]':'bg-red-500'"></span></td>
<td class="p-5 flex gap-2">
<button x-show="u.id !== user.id" @click="confirmDeleteUser(u)" class="text-gray-500 hover:text-red-500 p-2 rounded-lg hover:bg-red-500/10 transition">🗑️</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</main>
<!-- Company Stats Modal -->
<div x-show="showCompanyStatsModal" x-cloak class="fixed inset-0 bg-black/90 backdrop-blur-md flex items-center justify-center p-4 z-[110]">
<div class="bg-surface border border-gray-800 w-full max-w-4xl p-10 rounded-3xl shadow-2xl glass" @click.away="showCompanyStatsModal = false">
<div class="flex justify-between items-center mb-10">
<div>
<h3 class="text-2xl font-bold" x-text="companyStats?.company?.name"></h3>
<p class="text-xs text-gray-500 mt-1 uppercase tracking-widest" x-text="'TIN: ' + companyStats?.company?.tax_identification_number"></p>
</div>
<button @click="showCompanyStatsModal = false" class="text-gray-500 hover:text-white text-2xl"></button>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-10">
<div class="p-6 bg-gray-950/50 border border-gray-800 rounded-2xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-2">إجمالي الفواتير</p>
<p class="text-2xl font-bold" x-text="companyStats?.totals?.total_invoices || 0"></p>
</div>
<div class="p-6 bg-gray-950/50 border border-gray-800 rounded-2xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-2">المجموع الكلي (JOD)</p>
<p class="text-2xl font-bold text-emerald-400" x-text="parseFloat(companyStats?.totals?.total_amount || 0).toLocaleString()"></p>
</div>
<div class="p-6 bg-gray-950/50 border border-gray-800 rounded-2xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-2">إجمالي الضريبة</p>
<p class="text-2xl font-bold text-yellow-500" x-text="parseFloat(companyStats?.totals?.total_tax || 0).toLocaleString()"></p>
</div>
<div class="p-6 bg-gray-950/50 border border-gray-800 rounded-2xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-2">فواتير معتمدة</p>
<p class="text-2xl font-bold text-blue-400" x-text="companyStats?.totals?.approved_count || 0"></p>
</div>
</div>
<h4 class="text-sm font-bold text-gray-500 uppercase mb-4 tracking-widest">الإحصائيات الشهرية (آخر 12 شهر)</h4>
<div class="bg-gray-950/30 border border-gray-800 rounded-2xl overflow-hidden">
<table class="w-full text-right text-xs">
<thead class="bg-gray-900/50">
<tr>
<th class="p-4 text-gray-500 font-bold">الشهر</th>
<th class="p-4 text-gray-500 font-bold">عدد الفواتير</th>
<th class="p-4 text-gray-500 font-bold">المعتمدة</th>
<th class="p-4 text-gray-500 font-bold">المجموع الكلي</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800">
<template x-for="month in companyStats?.monthly" :key="month.month">
<tr class="hover:bg-white/[0.02]">
<td class="p-4 font-mono font-bold" x-text="month.month"></td>
<td class="p-4" x-text="month.total_invoices"></td>
<td class="p-4 text-emerald-500" x-text="month.approved_count"></td>
<td class="p-4 font-bold" x-text="parseFloat(month.total_amount).toLocaleString() + ' JOD'"></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
<!-- Full Invoice View Modal -->
<div x-show="showViewModal" x-cloak class="fixed inset-0 bg-black/95 flex items-center justify-center p-6 z-[100]">
<div class="bg-surface border border-gray-800 w-full h-full max-w-7xl rounded-3xl shadow-2xl flex overflow-hidden glass">
<!-- Left: File Preview -->
<div class="w-1/2 bg-black/40 border-l border-gray-800 flex flex-col relative">
<div class="p-4 border-b border-gray-800 flex justify-between items-center bg-gray-950/50">
<span class="text-xs font-bold text-gray-500 uppercase tracking-widest">معاينة المستند الأصلي</span>
<a :href="currentInvoice?.file_url + '&token=' + token()" target="_blank" class="text-[10px] bg-gray-800 px-3 py-1 rounded hover:bg-gray-700 transition">تحميل الملف 📥</a>
</div>
<div class="flex-1 overflow-auto p-4 flex items-start justify-center scrollbar-hide">
<template x-if="currentInvoice?.original_file_path?.toLowerCase().endsWith('.pdf')">
<iframe :src="currentInvoice?.file_url + '&token=' + token()" class="w-full h-full rounded-lg" frameborder="0"></iframe>
</template>
<template x-if="!currentInvoice?.original_file_path?.toLowerCase().endsWith('.pdf')">
<img :src="currentInvoice?.file_url + '&token=' + token()" @error="$el.src='https://placehold.co/600x800?text=Error+Loading+Image'" class="max-w-full rounded-lg shadow-2xl border border-white/5">
</template>
</div>
</div>
<!-- Right: Extracted Data -->
<div class="w-1/2 flex flex-col">
<div class="p-6 border-b border-gray-800 flex justify-between items-center bg-emerald-900/10">
<div>
<h3 class="text-xl font-bold">تفاصيل الفاتورة المستخرجة</h3>
<p class="text-[10px] text-emerald-500/70 mt-1 uppercase tracking-tighter">AI-Powered Extraction (Gemini 1.5 Flash)</p>
</div>
<button @click="showViewModal = false" class="text-gray-500 hover:text-white text-2xl transition"></button>
</div>
<div class="flex-1 overflow-y-auto p-8 space-y-10 scrollbar-hide">
<!-- Supplier Info -->
<div class="grid grid-cols-2 gap-8">
<div class="p-4 bg-gray-950/50 border border-gray-800 rounded-2xl">
<label class="block text-[10px] text-gray-500 font-bold uppercase mb-2">المورد</label>
<p class="text-sm font-bold text-emerald-400" x-text="currentInvoice?.supplier_name"></p>
<p class="text-[10px] text-gray-500 mt-1">TIN: <span x-text="currentInvoice?.supplier_tin"></span></p>
<p class="text-[10px] text-gray-500 mt-1" x-text="currentInvoice?.supplier_address"></p>
</div>
<div class="p-4 bg-gray-950/50 border border-gray-800 rounded-2xl">
<label class="block text-[10px] text-gray-500 font-bold uppercase mb-2">رقم وتاريخ الفاتورة</label>
<p class="text-sm font-bold" x-text="currentInvoice?.invoice_number"></p>
<p class="text-[10px] text-gray-400 mt-1" x-text="currentInvoice?.invoice_date"></p>
<p class="text-[10px] text-gray-500 mt-1 uppercase" x-text="currentInvoice?.invoice_type + ' / ' + currentInvoice?.invoice_category"></p>
</div>
</div>
<!-- Line Items -->
<div>
<label class="block text-[10px] text-gray-500 font-bold uppercase mb-4 tracking-widest">بنود الفاتورة التفصيلية</label>
<div class="bg-gray-950/30 border border-gray-800 rounded-2xl overflow-hidden">
<table class="w-full text-right text-xs">
<thead class="bg-gray-900/50">
<tr>
<th class="p-3 text-gray-500 font-bold">#</th>
<th class="p-3 text-gray-500 font-bold">الوصف</th>
<th class="p-3 text-gray-500 font-bold">الكمية</th>
<th class="p-3 text-gray-500 font-bold">السعر</th>
<th class="p-3 text-gray-500 font-bold">الضريبة</th>
<th class="p-3 text-gray-500 font-bold text-emerald-500">الإجمالي</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800">
<template x-for="item in currentInvoice?.items" :key="item.id">
<tr class="hover:bg-white/[0.02]">
<td class="p-3 text-gray-600" x-text="item.line_number"></td>
<td class="p-3 font-medium" x-text="item.description"></td>
<td class="p-3" x-text="parseFloat(item.quantity).toLocaleString()"></td>
<td class="p-3" x-text="parseFloat(item.unit_price).toLocaleString()"></td>
<td class="p-3 text-gray-500" x-text="(item.tax_rate * 100) + '%'"></td>
<td class="p-3 font-bold text-emerald-500" x-text="parseFloat(item.line_total).toLocaleString()"></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- Totals Section -->
<div class="flex justify-end">
<div class="w-64 space-y-3 p-6 bg-emerald-500/5 border border-emerald-500/20 rounded-2xl">
<div class="flex justify-between text-xs">
<span class="text-gray-500 font-bold">المجموع الفرعي</span>
<span class="font-mono" x-text="parseFloat(currentInvoice?.subtotal || 0).toLocaleString()"></span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-500 font-bold">الخصم الإجمالي</span>
<span class="font-mono text-red-400" x-text="'-' + parseFloat(currentInvoice?.discount_total || 0).toLocaleString()"></span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-500 font-bold">ضريبة المبيعات</span>
<span class="font-mono" x-text="parseFloat(currentInvoice?.tax_amount || 0).toLocaleString()"></span>
</div>
<div class="border-t border-emerald-500/20 pt-3 flex justify-between">
<span class="text-sm font-bold text-emerald-400">الإجمالي النهائي</span>
<span class="text-lg font-bold text-emerald-400 font-mono" x-text="parseFloat(currentInvoice?.grand_total || 0).toLocaleString() + ' JOD'"></span>
</div>
</div>
</div>
<!-- Official / Local QR Display -->
<template x-if="currentInvoice?.qr_code || currentInvoice?.jofotara?.qr_image_uri">
<div class="p-6 bg-emerald-950/20 border border-emerald-500/30 rounded-2xl flex items-center gap-8">
<div class="bg-white p-2 rounded-lg shadow-xl">
<img :src="currentInvoice?.jofotara?.qr_image_uri || ('data:image/png;base64,' + generateQRPng(currentInvoice.qr_code))" class="w-32 h-32" alt="QR Code">
</div>
<div class="space-y-2 flex-1">
<h4 class="text-emerald-500 font-bold" x-text="currentInvoice?.jofotara ? '✅ فاتورة معتمدة رسمياً' : '✅ تم الاعتماد محلياً'"></h4>
<p class="text-xs text-gray-400">الرقم الموحد: <span class="font-mono select-all text-gray-200" x-text="currentInvoice?.jofotara?.uuid || currentInvoice?.id"></span></p>
<p class="text-xs text-gray-400">تاريخ الرفع: <span x-text="currentInvoice?.jofotara?.submitted_at || currentInvoice?.updated_at"></span></p>
<div class="pt-2 flex gap-3" x-show="currentInvoice?.jofotara">
<a :href="'/index.php?route=v1/invoices/download_xml&id=' + currentInvoice.id + '&token=' + token()"
class="inline-flex items-center gap-2 text-[10px] bg-emerald-600/20 hover:bg-emerald-600/40 text-emerald-400 px-4 py-2 rounded-lg transition-all border border-emerald-500/30">
⬇️ تحميل ملف XML الرسمي
</a>
</div>
</div>
</div>
</template>
</div>
<div class="p-6 bg-gray-950/50 border-t border-gray-800 flex gap-4">
<template x-if="currentInvoice?.status === 'extracted'">
<button @click="approveInvoice(currentInvoice.id)"
class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-3 rounded-xl font-bold transition flex items-center justify-center gap-2 disabled:opacity-50"
:disabled="isApproving">
<span x-show="!isApproving"> اعتماد الفاتورة وتوليد QR</span>
<span x-show="isApproving" class="flex items-center gap-2">
<svg class="animate-spin h-4 w-4 text-white" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
جارِ المعالجة...
</span>
</button>
</template>
<button @click="showViewModal = false" class="px-8 py-3 border border-gray-800 rounded-xl hover:bg-gray-800 transition text-sm">إغلاق</button>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
user: JSON.parse(localStorage.getItem('user')),
page: 'dashboard',
users: [],
companies: [],
tenants: [],
invoices: [],
stats: { total: 0, pending: 0, approved: 0 },
showAddModal: false,
showAddCompanyModal: false,
showAddTenantModal: false,
showUploadModal: false,
showViewModal: false,
showCompanyStatsModal: false,
isUploading: false,
isApproving: false,
globalError: '',
newUser: { name: '', email: '', password: '', role: 'employee', tenant_id: '' },
uploadData: { company_id: '' },
selectedFile: null,
currentInvoice: null,
companyStats: null,
init() {
if (!this.user) { window.location.href = '/login.php'; return; }
this.loadAll();
},
setPage(p) {
this.page = p;
this.loadAll();
},
title() { return { dashboard: 'نظرة عامة', users: 'إدارة المستخدمين', companies: 'الشركات', invoices: 'الفواتير' }[this.page] || ''; },
subtitle() { return { dashboard: 'تحليلات المنصة', users: 'إدارة أدوار الوصول', invoices: 'إدارة الفواتير المرفوعة' }[this.page] || ''; },
showError(msg) { this.globalError = msg; setTimeout(() => this.globalError = '', 6000); },
token() { return localStorage.getItem('access_token'); },
async apiRequest(route, method = 'GET', body = null) {
const options = {
method: method,
headers: {
'Authorization': 'Bearer ' + this.token(),
'Content-Type': 'application/json'
}
};
if (body) options.body = JSON.stringify(body);
try {
const res = await fetch('/index.php?route=' + route, options);
if (res.status === 401) {
const json = await res.json();
if (json.code === 'TOKEN_EXPIRED') {
localStorage.clear();
window.location.href = '/login.php';
return null;
}
}
const json = await res.json();
return json.success ? json.data : (this.showError(json.message), null);
} catch (e) {
this.showError('فشل الاتصال بالخادم');
return null;
}
},
async apiGet(route) { return this.apiRequest(route); },
async loadAll() {
this.loadStats();
this.loadCompanies();
if (this.page === 'users') this.loadUsers();
if (this.page === 'invoices') this.loadInvoices();
if (this.page === 'tenants') this.loadTenants();
},
async loadUsers() { this.users = await this.apiGet('v1/users') || []; },
async loadCompanies() { this.companies = await this.apiGet('v1/companies') || []; },
async loadInvoices() { this.invoices = await this.apiGet('v1/invoices') || []; },
async loadStats() { this.stats = await this.apiGet('v1/dashboard/stats') || {}; },
async loadTenants() { this.tenants = await this.apiGet('v1/tenants') || []; },
async showCompanyStats(id) {
const data = await this.apiGet('v1/companies/stats&id=' + id);
if (data) {
this.companyStats = data;
this.showCompanyStatsModal = true;
}
},
async viewInvoice(id) {
const data = await this.apiGet('v1/invoices/view&id=' + id);
if (data) {
this.currentInvoice = data;
this.showViewModal = true;
}
},
async approveInvoice(id) {
if (!confirm('هل أنت متأكد من اعتماد الفاتورة؟')) return;
this.isApproving = true;
try {
const data = await this.apiRequest('v1/invoices/approve', 'POST', { id: id });
if (data) {
alert(data.message || '✅ تم الاعتماد بنجاح!');
this.viewInvoice(id);
this.loadInvoices();
this.loadStats();
}
} catch (e) {
this.showError('خطأ غير متوقع: ' + e.message);
} finally {
this.isApproving = false;
}
},
generateQRPng(base64Tlv) {
if (!base64Tlv) return '';
try {
const qr = new QRious({
value: base64Tlv,
size: 200,
level: 'M'
});
return qr.toDataURL().split(',')[1];
} catch (e) {
return '';
}
},
handleFile(e) { this.selectedFile = e.target.files[0]; },
async uploadInvoice() {
if (!this.selectedFile) return alert('الرجاء اختيار ملف');
this.isUploading = true;
const formData = new FormData();
formData.append('company_id', this.uploadData.company_id);
formData.append('invoice', this.selectedFile);
try {
const res = await fetch('/index.php?route=v1/invoices/upload', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + this.token() },
body: formData
});
const json = await res.json();
this.isUploading = false;
if (json.success) {
this.showUploadModal = false;
this.loadInvoices();
this.viewInvoice(json.data.id);
} else {
this.showError(json.message);
}
} catch (e) {
this.isUploading = false;
this.showError('فشل رفع الملف');
}
},
logout() { localStorage.clear(); window.location.href = '/login.php'; }
}));
});
</script>
</body>
</html>