Files
musadaq-saas/public/shell.php
2026-05-04 18:05:37 +03:00

595 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;
--emerald-glow: rgba(16, 185, 129, 0.4);
--bg-base: #030712;
--bg-surface: #0f172a;
--border-default: rgba(255,255,255,0.08);
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
}
body {
font-family: 'IBM Plex Sans Arabic', sans-serif;
background-color: var(--bg-base);
color: var(--text-primary);
background-image: radial-gradient(circle at top right, rgba(16,185,129,0.05), transparent 400px);
}
[x-cloak] { display: none !important; }
.glass { background: rgba(15, 23, 42, 0.7); backdrop-filter: blur(16px); border: 1px solid var(--border-default); }
.glass-elevated { background: rgba(30, 41, 59, 0.8); backdrop-filter: blur(24px); border: 1px solid rgba(255,255,255,0.1); }
.btn-glow:hover { box-shadow: 0 0 20px var(--emerald-glow); transform: translateY(-1px); }
.scrollbar-hide::-webkit-scrollbar { display: none; }
.nav-active { background: rgba(16,185,129,0.1); border-right: 4px solid var(--emerald); color: var(--emerald); }
</style>
</head>
<body x-data="app" x-init="init()">
<!-- Global Error Toast -->
<div x-show="globalError" x-cloak
class="fixed top-6 left-1/2 -translate-x-1/2 z-[100] bg-red-900/90 border border-red-500 text-white px-8 py-4 rounded-2xl 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 glass">
<div class="p-8">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-emerald-500 flex items-center justify-center text-white font-bold"></div>
<h1 class="text-2xl font-bold tracking-tight">مُصادَق</h1>
</div>
<p class="text-[10px] text-gray-500 mt-2 uppercase tracking-widest">Enterprise SaaS</p>
</div>
<nav class="flex-1 px-4 space-y-2 mt-4">
<button @click="setPage('dashboard')" class="w-full flex items-center gap-3 p-3.5 rounded-xl transition-all" :class="page==='dashboard'?'nav-active':'text-gray-400 hover:bg-gray-800/50'">
<span class="text-xl">📊</span><span class="font-medium">لوحة التحكم</span>
</button>
<button x-show="user?.role === 'super_admin'" @click="setPage('tenants')" class="w-full flex items-center gap-3 p-3.5 rounded-xl transition-all" :class="page==='tenants'?'nav-active':'text-gray-400 hover:bg-gray-800/50'">
<span class="text-xl">🏢</span><span class="font-medium">المكاتب المحاسبية</span>
</button>
<button x-show="user?.role !== 'viewer'" @click="setPage('companies')" class="w-full flex items-center gap-3 p-3.5 rounded-xl transition-all" :class="page==='companies'?'nav-active':'text-gray-400 hover:bg-gray-800/50'">
<span class="text-xl">🏭</span><span class="font-medium">الشركات</span>
</button>
<button x-show="user?.role !== 'viewer'" @click="setPage('invoices')" class="w-full flex items-center gap-3 p-3.5 rounded-xl transition-all" :class="page==='invoices'?'nav-active':'text-gray-400 hover:bg-gray-800/50'">
<span class="text-xl">📄</span><span class="font-medium">الفواتير</span>
</button>
<button x-show="user?.role === 'super_admin' || user?.role === 'admin'" @click="setPage('users')" class="w-full flex items-center gap-3 p-3.5 rounded-xl transition-all" :class="page==='users'?'nav-active':'text-gray-400 hover:bg-gray-800/50'">
<span class="text-xl">👥</span><span class="font-medium">المستخدمون</span>
</button>
</nav>
<div class="p-6 border-t border-gray-800">
<div class="flex items-center gap-3 mb-4 p-3 rounded-xl bg-gray-950/50 border border-gray-800">
<div class="w-10 h-10 rounded-full bg-emerald-500/20 flex items-center justify-center text-emerald-500 font-bold" 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-colors">🚪 تسجيل الخروج</button>
</div>
</aside>
<!-- Main -->
<main class="flex-1 overflow-y-auto p-10 bg-[#060910]">
<header class="mb-12 flex justify-between items-end">
<div>
<h2 class="text-4xl font-bold tracking-tight" x-text="title()"></h2>
<p class="text-gray-500 mt-2 text-sm" x-text="subtitle()"></p>
</div>
<div class="flex items-center gap-4">
<button x-show="page==='tenants'" @click="showAddTenantModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-6 py-3 rounded-xl text-sm font-bold shadow-lg transition-all active:scale-95 btn-glow"> إضافة مكتب</button>
<button x-show="page==='users'" @click="showAddModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-6 py-3 rounded-xl text-sm font-bold shadow-lg transition-all active:scale-95 btn-glow"> إضافة مستخدم</button>
<button x-show="page==='companies'" @click="showAddCompanyModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-6 py-3 rounded-xl text-sm font-bold shadow-lg transition-all active:scale-95 btn-glow"> إضافة شركة</button>
<button x-show="page==='invoices'" @click="showUploadModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-6 py-3 rounded-xl text-sm font-bold shadow-lg transition-all active:scale-95 btn-glow">📤 رفع فواتير</button>
</div>
</header>
<div id="content" x-transition>
<!-- Dashboard -->
<div x-show="page === 'dashboard'" class="space-y-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="p-8 bg-surface border border-gray-800 rounded-3xl shadow-xl relative overflow-hidden group glass">
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest">إجمالي الفواتير</p>
<p class="text-5xl font-bold mt-4" x-text="stats.total || 0"></p>
<div class="absolute top-0 right-0 p-6 opacity-5 group-hover:opacity-10 transition-opacity"><span class="text-6xl">📄</span></div>
</div>
<div class="p-8 bg-surface border border-gray-800 rounded-3xl shadow-xl relative overflow-hidden group glass">
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest text-yellow-500">بانتظار الاعتماد</p>
<p class="text-5xl font-bold mt-4 text-yellow-500" x-text="stats.pending || 0"></p>
<div class="absolute top-0 right-0 p-6 opacity-5 group-hover:opacity-10 transition-opacity"><span class="text-6xl"></span></div>
</div>
<div class="p-8 bg-surface border border-gray-800 rounded-3xl shadow-xl relative overflow-hidden group glass">
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest text-emerald-500">تم الاعتماد</p>
<p class="text-5xl font-bold mt-4 text-emerald-500" x-text="stats.approved || 0"></p>
<div class="absolute top-0 right-0 p-6 opacity-5 group-hover:opacity-10 transition-opacity"><span class="text-6xl"></span></div>
</div>
</div>
</div>
<!-- Companies Table with Stats -->
<div x-show="page === 'companies'">
<div class="bg-surface border border-gray-800 rounded-3xl overflow-hidden shadow-2xl glass">
<table class="w-full text-right divide-y divide-gray-800">
<thead class="bg-gray-950/50">
<tr>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">الشركة</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">الأرقام الرسمية</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">العنوان</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">الإحصائيات</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">إجراءات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800/50">
<tr x-show="companies.length === 0"><td colspan="5" class="p-20 text-center text-gray-600 text-lg">لا توجد شركات مسجلة</td></tr>
<template x-for="c in companies" :key="c.id">
<tr class="hover:bg-white/[0.02] transition-colors group">
<td class="p-6"><p class="font-bold text-emerald-500 text-lg" x-text="c.name"></p></td>
<td class="p-6">
<p class="text-xs text-gray-400">TIN: <span class="font-mono text-gray-200" x-text="c.tax_identification_number"></span></p>
<p class="text-xs text-gray-400">CRN: <span class="font-mono text-gray-200" x-text="c.commercial_registration_number"></span></p>
</td>
<td class="p-6 text-sm text-gray-500" x-text="c.address"></td>
<td class="p-6">
<button @click="showCompanyStats(c.id)" class="text-xs bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-400 px-4 py-2 rounded-xl border border-emerald-500/20 transition-all font-bold">📊 عرض التقارير</button>
</td>
<td class="p-6">
<div class="flex gap-2">
<button @click="confirmDeleteCompany(c)" class="text-gray-500 hover:text-red-500 p-2.5 rounded-xl hover:bg-red-500/10 transition">🗑️</button>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- Invoices Table -->
<div x-show="page === 'invoices'">
<div class="bg-surface border border-gray-800 rounded-3xl overflow-hidden shadow-2xl glass">
<table class="w-full text-right border-collapse">
<thead class="bg-gray-950/50 border-b border-gray-800">
<tr>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">الشركة / المورد</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">التاريخ</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">المجموع</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">الحالة</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase">إجراءات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800/50">
<tr x-show="invoices.length === 0"><td colspan="5" class="p-20 text-center text-gray-600 text-lg">لا توجد فواتير مرفوعة</td></tr>
<template x-for="inv in invoices" :key="inv.id">
<tr class="hover:bg-white/[0.02] transition-all group">
<td class="p-6">
<p class="font-bold text-emerald-500" x-text="inv.company_name"></p>
<p class="text-sm font-medium text-gray-200 mt-1" x-text="inv.supplier_name"></p>
<p class="text-[10px] text-gray-500 mt-0.5" x-text="inv.supplier_tin"></p>
</td>
<td class="p-6 text-sm text-gray-400 font-mono" x-text="inv.invoice_date || '-'"></td>
<td class="p-6">
<span class="font-mono text-lg font-bold" x-text="parseFloat(inv.grand_total).toLocaleString()"></span>
<span class="text-[10px] text-gray-600 mr-1">JOD</span>
</td>
<td class="p-6">
<span class="px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-wider"
:class="inv.status==='extracted'?'bg-blue-900/40 text-blue-400':(inv.status==='approved'?'bg-emerald-900/40 text-emerald-400':'bg-gray-800 text-gray-400')"
x-text="inv.status"></span>
</td>
<td class="p-6">
<button @click="viewInvoice(inv.id)" class="text-gray-500 hover:text-emerald-400 p-3 rounded-2xl hover:bg-emerald-400/10 transition-all active:scale-95">👁️ عرض</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-6 z-[110]">
<div class="bg-surface border border-gray-800 w-full max-w-4xl p-10 rounded-[40px] shadow-2xl glass-elevated" @click.away="showCompanyStatsModal = false">
<div class="flex justify-between items-start mb-12">
<div>
<h3 class="text-3xl font-bold text-emerald-400" x-text="companyStats?.company?.name"></h3>
<p class="text-xs text-gray-500 mt-2 uppercase tracking-widest font-mono" x-text="'TIN: ' + companyStats?.company?.tax_identification_number"></p>
</div>
<button @click="showCompanyStatsModal = false" class="text-gray-500 hover:text-white text-3xl transition"></button>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-12">
<div class="p-8 bg-gray-950/50 border border-gray-800 rounded-3xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-3">إجمالي الفواتير</p>
<p class="text-3xl font-bold" x-text="companyStats?.totals?.total_invoices || 0"></p>
</div>
<div class="p-8 bg-gray-950/50 border border-gray-800 rounded-3xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-3">المجموع (JOD)</p>
<p class="text-3xl font-bold text-emerald-400 font-mono" x-text="parseFloat(companyStats?.totals?.total_amount || 0).toLocaleString()"></p>
</div>
<div class="p-8 bg-gray-950/50 border border-gray-800 rounded-3xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-3">إجمالي الضريبة</p>
<p class="text-3xl font-bold text-yellow-500 font-mono" x-text="parseFloat(companyStats?.totals?.total_tax || 0).toLocaleString()"></p>
</div>
<div class="p-8 bg-gray-950/50 border border-gray-800 rounded-3xl">
<p class="text-[10px] text-gray-500 font-bold uppercase mb-3">المعتمدة</p>
<p class="text-3xl 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-6 tracking-widest">تحليل الأداء الشهري</h4>
<div class="bg-gray-950/30 border border-gray-800 rounded-3xl overflow-hidden">
<table class="w-full text-right text-xs">
<thead class="bg-gray-900/50">
<tr>
<th class="p-5 text-gray-500 font-bold">الشهر</th>
<th class="p-5 text-gray-500 font-bold">عدد الفواتير</th>
<th class="p-5 text-gray-500 font-bold">مجموع الضريبة</th>
<th class="p-5 text-gray-500 font-bold text-emerald-500">المجموع النهائي</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-5 font-mono font-bold text-gray-200" x-text="month.month"></td>
<td class="p-5" x-text="month.total_invoices"></td>
<td class="p-5 text-yellow-500/80 font-mono" x-text="parseFloat(month.total_tax || 0).toLocaleString()"></td>
<td class="p-5 font-bold text-emerald-500 font-mono text-sm" 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-[40px] shadow-2xl flex overflow-hidden glass-elevated">
<!-- Left: File Preview -->
<div class="w-1/2 bg-black/40 border-l border-gray-800 flex flex-col relative">
<div class="p-6 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-emerald-500/10 hover:bg-emerald-500/20 text-emerald-400 px-4 py-2 rounded-xl border border-emerald-500/20 transition-all font-bold">تحميل 📥</a>
</div>
<div class="flex-1 overflow-auto p-8 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-2xl shadow-2xl" 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-2xl shadow-2xl border border-white/5">
</template>
</div>
</div>
<!-- Right: Extracted Data -->
<div class="w-1/2 flex flex-col">
<div class="p-8 border-b border-gray-800 flex justify-between items-start bg-emerald-500/5">
<div>
<h3 class="text-2xl font-bold text-white">تفاصيل الفاتورة المستخرجة</h3>
<p class="text-[10px] text-emerald-500/70 mt-2 uppercase tracking-tighter">التحليل الذكي (Gemini 1.5 Flash)</p>
</div>
<button @click="showViewModal = false" class="text-gray-500 hover:text-white text-3xl 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-6 bg-gray-950/50 border border-gray-800 rounded-3xl hover:border-emerald-500/30 transition-all">
<label class="block text-[10px] text-gray-500 font-bold uppercase mb-3">المورد</label>
<p class="text-lg font-bold text-emerald-400" x-text="currentInvoice?.supplier_name"></p>
<p class="text-xs text-gray-500 mt-2 font-mono">TIN: <span class="text-gray-300" x-text="currentInvoice?.supplier_tin"></span></p>
<p class="text-xs text-gray-500 mt-1" x-text="currentInvoice?.supplier_address"></p>
</div>
<div class="p-6 bg-gray-950/50 border border-gray-800 rounded-3xl hover:border-blue-500/30 transition-all">
<label class="block text-[10px] text-gray-500 font-bold uppercase mb-3">بيانات الفاتورة</label>
<p class="text-lg font-bold font-mono" x-text="currentInvoice?.invoice_number"></p>
<p class="text-xs text-gray-400 mt-2 font-mono" x-text="currentInvoice?.invoice_date"></p>
<p class="text-[10px] text-gray-600 mt-2 uppercase font-bold" 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-3xl 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>
<th class="p-4 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] transition-colors">
<td class="p-4 text-gray-600 font-mono" x-text="item.line_number"></td>
<td class="p-4 font-medium text-gray-200" x-text="item.description"></td>
<td class="p-4 font-mono" x-text="parseFloat(item.quantity).toLocaleString()"></td>
<td class="p-4 font-mono" x-text="parseFloat(item.unit_price).toLocaleString()"></td>
<td class="p-4 font-bold text-emerald-500 font-mono" x-text="parseFloat(item.line_total).toLocaleString()"></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- Totals Section -->
<div class="flex justify-end">
<div class="w-80 space-y-4 p-8 bg-emerald-500/5 border border-emerald-500/20 rounded-3xl glass">
<div class="flex justify-between text-xs">
<span class="text-gray-500 font-bold uppercase">المجموع الفرعي</span>
<span class="font-mono text-gray-200" x-text="parseFloat(currentInvoice?.subtotal || 0).toLocaleString()"></span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-500 font-bold uppercase">ضريبة المبيعات</span>
<span class="font-mono text-yellow-500/80" x-text="parseFloat(currentInvoice?.tax_amount || 0).toLocaleString()"></span>
</div>
<div class="border-t border-emerald-500/20 pt-4 flex justify-between items-center">
<span class="text-sm font-bold text-emerald-400 uppercase">الإجمالي النهائي</span>
<span class="text-2xl font-bold text-emerald-400 font-mono" x-text="parseFloat(currentInvoice?.grand_total || 0).toLocaleString() + ' JOD'"></span>
</div>
</div>
</div>
<!-- QR / Certification Display -->
<template x-if="currentInvoice?.qr_code || currentInvoice?.jofotara?.qr_image_uri">
<div class="p-8 bg-emerald-950/20 border border-emerald-500/30 rounded-[32px] flex items-center gap-8 glass">
<div class="bg-white p-3 rounded-2xl shadow-2xl transform hover:scale-105 transition-transform">
<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-3 flex-1">
<h4 class="text-emerald-400 font-bold text-lg" x-text="currentInvoice?.jofotara ? '✅ فاتورة معتمدة رسمياً' : '✅ تم الاعتماد محلياً'"></h4>
<p class="text-xs text-gray-500 font-mono">الرقم الموحد: <span class="text-gray-200" x-text="currentInvoice?.jofotara?.uuid || currentInvoice?.id"></span></p>
<p class="text-xs text-gray-500">تاريخ الرفع: <span class="text-gray-300" x-text="currentInvoice?.jofotara?.submitted_at || currentInvoice?.updated_at"></span></p>
<div class="pt-2" 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-xs bg-emerald-600/20 hover:bg-emerald-600/30 text-emerald-400 px-5 py-2.5 rounded-xl border border-emerald-500/30 transition-all font-bold">
📥 تحميل ملف XML الرسمي
</a>
</div>
</div>
</div>
</template>
</div>
<div class="p-8 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-4 rounded-2xl font-bold transition-all flex items-center justify-center gap-3 disabled:opacity-50 btn-glow"
:disabled="isApproving">
<span x-show="!isApproving"> اعتماد الفاتورة وتوليد الـ QR الرسمي</span>
<span x-show="isApproving" class="flex items-center gap-2">جارِ المعالجة... </span>
</button>
</template>
<button @click="showViewModal = false" class="px-10 py-4 border border-gray-800 rounded-2xl hover:bg-gray-800 transition text-sm font-bold">إغلاق</button>
</div>
</div>
</div>
</div>
<!-- Upload Invoice Modal -->
<div x-show="showUploadModal" x-cloak class="fixed inset-0 bg-black/90 backdrop-blur-md flex items-center justify-center p-6 z-50">
<div class="bg-surface border border-gray-800 w-full max-w-lg p-10 rounded-[40px] shadow-2xl glass-elevated" @click.away="showUploadModal = false">
<h3 class="text-3xl font-bold mb-3">رفع فواتير 📤</h3>
<p class="text-gray-500 text-sm mb-10">سيقوم النظام بتحليل الفاتورة آلياً</p>
<form @submit.prevent="uploadInvoice" class="space-y-8">
<div>
<label class="block text-[10px] text-gray-500 font-bold uppercase mb-3">اختر الشركة</label>
<select x-model="uploadData.company_id" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none focus:ring-2 focus:ring-emerald-500/20 transition-all cursor-pointer" required>
<option value="">-- اختر الشركة --</option>
<template x-for="c in companies" :key="c.id"><option :value="c.id" x-text="c.name"></option></template>
</select>
</div>
<div class="border-2 border-dashed border-gray-800 rounded-[32px] p-16 text-center hover:border-emerald-500/50 transition-all cursor-pointer group relative">
<input type="file" @change="handleFile" class="absolute inset-0 opacity-0 cursor-pointer" required>
<div class="space-y-4">
<span class="text-5xl block group-hover:scale-110 transition-transform">📄</span>
<p class="text-sm font-bold text-gray-400" x-text="selectedFile ? selectedFile.name : 'اختر ملف الفاتورة'"></p>
</div>
</div>
<div class="pt-4 flex gap-4">
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold shadow-lg transition-all active:scale-95 disabled:opacity-50 btn-glow" :disabled="isUploading">
<span x-show="!isUploading">بدء المعالجة الذكية</span>
<span x-show="isUploading">جارِ التحليل... </span>
</button>
<button type="button" @click="showUploadModal = false" class="px-8 py-4 border border-gray-800 rounded-2xl hover:bg-gray-800 transition-all font-bold">إلغاء</button>
</div>
</form>
</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: 'إدارة الصلاحيات والوصول', companies: 'إدارة بيانات الشركات والضرائب', invoices: 'معالجة واعتماد الفواتير المستخرجة' }[this.page] || ''; },
showError(msg) { this.globalError = msg; setTimeout(() => this.globalError = '', 8000); },
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);
// Session Expired logic
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: 300,
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>