Files
musadaq-saas/public/shell.php
2026-05-04 20:12:58 +03:00

639 lines
45 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==='users'" @click="showAddUserModal = 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 -->
<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>
<p class="text-xs text-gray-500" x-text="c.address"></p>
</td>
<td class="p-6 text-xs font-mono">
<p>TIN: <span x-text="c.tax_identification_number"></span></p>
<p>CRN: <span x-text="c.commercial_registration_number || '-'"></span></p>
</td>
<td class="p-6">
<div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full" :class="c.jofotara_client_id_encrypted ? 'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]' : 'bg-gray-700'"></span>
<button @click="openConnectModal(c)" class="text-xs font-bold hover:underline" :class="c.jofotara_client_id_encrypted ? 'text-emerald-400' : 'text-gray-400'">
<span x-text="c.jofotara_client_id_encrypted ? '✅ تم الربط (تعديل)' : '🔗 ربط الفوترة'"></span>
</button>
</div>
</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">
<button @click="confirmDeleteCompany(c)" class="text-gray-500 hover:text-red-500 p-2.5 rounded-xl hover:bg-red-500/10 transition">🗑️</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<!-- Users Table -->
<div x-show="page === 'users'">
<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="users.length === 0"><td colspan="5" class="p-20 text-center text-gray-600 text-lg">لا يوجد مستخدمون مسجلون</td></tr>
<template x-for="u in users" :key="u.id">
<tr class="hover:bg-white/[0.02] transition-colors">
<td class="p-6">
<p class="font-bold text-emerald-500" x-text="u.name"></p>
<p class="text-xs text-gray-500 font-mono" x-text="u.email"></p>
</td>
<td class="p-6 text-sm text-gray-400" x-text="u.tenant_name || '-'"></td>
<td class="p-6">
<span class="px-3 py-1 bg-gray-800/50 border border-gray-700 rounded-full text-[10px] font-bold uppercase tracking-wide" x-text="u.role"></span>
</td>
<td class="p-6">
<span class="w-2.5 h-2.5 rounded-full inline-block" :class="u.is_active?'bg-emerald-400':'bg-red-400'"></span>
</td>
<td class="p-6">
<button x-show="u.id !== user.id" @click="confirmDeleteUser(u)" class="text-gray-500 hover:text-red-500 p-2.5 transition">🗑️</button>
</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">
<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>
</td>
<td class="p-6 text-sm text-gray-400 font-mono" x-text="inv.invoice_date || '-'"></td>
<td class="p-6 font-mono font-bold" x-text="parseFloat(inv.grand_total).toLocaleString() + ' JOD'"></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">👁️ عرض</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 max-h-64 overflow-y-auto">
<table class="w-full text-right text-xs">
<thead class="bg-gray-900/50 sticky top-0">
<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="m in companyStats?.monthly" :key="m.month">
<tr>
<td class="p-5 font-mono text-gray-200" x-text="m.month"></td>
<td class="p-5" x-text="m.total_invoices"></td>
<td class="p-5 text-yellow-500 font-mono" x-text="parseFloat(m.total_tax || 0).toLocaleString() + ' JOD'"></td>
<td class="p-5 font-bold text-emerald-500 font-mono" x-text="parseFloat(m.total_amount || 0).toLocaleString() + ' JOD'"></td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
<!-- Add User Modal -->
<div x-show="showAddUserModal" x-cloak class="fixed inset-0 bg-black/90 flex items-center justify-center p-6 z-[120]">
<div class="bg-surface border border-gray-800 w-full max-w-lg p-10 rounded-[40px] shadow-2xl glass-elevated">
<h3 class="text-2xl font-bold mb-8">إضافة مستخدم جديد</h3>
<form @submit.prevent="createUser" class="space-y-6">
<input type="text" x-model="newUser.name" placeholder="الاسم الكامل" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
<input type="email" x-model="newUser.email" placeholder="البريد الإلكتروني" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
<input type="password" x-model="newUser.password" placeholder="كلمة المرور" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
<select x-model="newUser.role" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
<option value="accountant">محاسب</option>
<option value="viewer">مشاهد فقط</option>
<option x-show="user?.role === 'super_admin'" value="admin">مدير مكتب</option>
</select>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold transition-all btn-glow" :disabled="isBusy">حفظ المستخدم</button>
<button type="button" @click="showAddUserModal = false" class="px-8 py-4 border border-gray-800 rounded-2xl">إلغاء</button>
</div>
</form>
</div>
</div>
<!-- Add Company Modal -->
<div x-show="showAddCompanyModal" x-cloak class="fixed inset-0 bg-black/90 flex items-center justify-center p-6 z-[120]">
<div class="bg-surface border border-gray-800 w-full max-w-lg p-10 rounded-[40px] shadow-2xl glass-elevated">
<h3 class="text-2xl font-bold mb-8">إضافة شركة جديدة</h3>
<form @submit.prevent="createCompany" class="space-y-6">
<input type="text" x-model="newCompany.name" placeholder="اسم الشركة (بالعربي)" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
<input type="text" x-model="newCompany.tax_identification_number" placeholder="الرقم الضريبي (TIN)" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
<input type="text" x-model="newCompany.commercial_registration_number" placeholder="رقم السجل التجاري" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none">
<input type="text" x-model="newCompany.address" placeholder="العنوان" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none">
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold transition-all btn-glow" :disabled="isBusy">إضافة الشركة</button>
<button type="button" @click="showAddCompanyModal = false" class="px-8 py-4 border border-gray-800 rounded-2xl">إلغاء</button>
</div>
</form>
</div>
</div>
<!-- JoFotara Connect Modal -->
<div x-show="showConnectModal" x-cloak class="fixed inset-0 bg-black/90 backdrop-blur-sm flex items-center justify-center p-6 z-[130]">
<div class="bg-surface border border-gray-800 w-full max-w-lg p-10 rounded-[40px] shadow-2xl glass-elevated">
<h3 class="text-2xl font-bold mb-4">ربط نظام الفوترة الحكومي</h3>
<p class="text-gray-500 text-sm mb-8" x-text="'الشركة: ' + currentCompany?.name"></p>
<form @submit.prevent="connectJoFotara" class="space-y-6">
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-2">Client ID</label>
<input type="text" x-model="connectData.client_id" placeholder="أدخل Client ID" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none font-mono text-sm" required>
</div>
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-2">Secret Key</label>
<input type="password" x-model="connectData.secret_key" placeholder="أدخل Secret Key" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none font-mono text-sm" required>
</div>
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-2">تسلسل مصدر الدخل</label>
<input type="text" x-model="connectData.income_source_sequence" placeholder="مثال: 1" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none font-mono text-sm">
</div>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold transition-all btn-glow" :disabled="isBusy">حفظ وتفعيل الربط</button>
<button type="button" @click="showConnectModal = false" class="px-8 py-4 border border-gray-800 rounded-2xl">إلغاء</button>
</div>
</form>
</div>
</div>
<!-- Upload Invoice Modal -->
<div x-show="showUploadModal" x-cloak class="fixed inset-0 bg-black/90 flex items-center justify-center p-6 z-[120]">
<div class="bg-surface border border-gray-800 w-full max-w-lg p-10 rounded-[40px] shadow-2xl glass-elevated">
<h3 class="text-2xl font-bold mb-8">رفع فاتورة جديدة</h3>
<form @submit.prevent="uploadInvoice" class="space-y-6">
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-2">اختر الشركة</label>
<select x-model="uploadData.company_id" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" 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>
<label class="block text-xs font-bold text-gray-500 uppercase mb-2">ملف الفاتورة (صورة أو PDF)</label>
<input type="file" @change="selectedFile = $event.target.files[0]" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
</div>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold transition-all 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">إلغاء</button>
</div>
</form>
</div>
</div>
<!-- View Invoice Modal -->
<div x-show="showViewModal" 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-6xl h-[90vh] p-8 rounded-[40px] shadow-2xl glass-elevated flex flex-col md:flex-row gap-8" @click.away="showViewModal = false">
<!-- Left: Document Preview -->
<div class="flex-1 bg-gray-950 rounded-3xl overflow-hidden border border-gray-800 relative">
<template x-if="currentInvoice?.file_url">
<iframe :src="currentInvoice.file_url + '&token=' + token()" class="w-full h-full border-0"></iframe>
</template>
<div x-show="!currentInvoice?.file_url" class="absolute inset-0 flex items-center justify-center text-gray-600">لا يوجد ملف مرفق</div>
</div>
<!-- Right: Invoice Data -->
<div class="w-full md:w-96 flex flex-col h-full overflow-y-auto pr-4 scrollbar-hide">
<div class="flex justify-between items-start mb-6">
<h3 class="text-2xl font-bold text-emerald-400">تفاصيل الفاتورة</h3>
<button @click="showViewModal = false" class="text-gray-500 hover:text-white text-3xl transition"></button>
</div>
<div class="space-y-4 flex-1">
<div class="p-4 bg-gray-900/50 rounded-2xl border border-gray-800">
<p class="text-[10px] text-gray-500 uppercase tracking-widest mb-1">المورد</p>
<p class="font-bold text-lg text-emerald-300" x-text="currentInvoice?.supplier_name || 'غير متوفر'"></p>
<p class="text-xs text-gray-400 font-mono mt-1" x-text="'TIN: ' + (currentInvoice?.supplier_tin || '-')"></p>
</div>
<div class="p-4 bg-gray-900/50 rounded-2xl border border-gray-800">
<p class="text-[10px] text-gray-500 uppercase tracking-widest mb-1">الرقم والتاريخ</p>
<p class="font-bold" x-text="currentInvoice?.invoice_number || '-'"></p>
<p class="text-sm text-gray-400 font-mono" x-text="currentInvoice?.invoice_date || '-'"></p>
</div>
<div class="p-4 bg-gray-900/50 rounded-2xl border border-gray-800">
<p class="text-[10px] text-gray-500 uppercase tracking-widest mb-1">المجموع الكلي</p>
<p class="text-3xl font-bold font-mono text-emerald-400" x-text="parseFloat(currentInvoice?.grand_total || 0).toLocaleString() + ' JOD'"></p>
<p class="text-xs text-yellow-500 mt-1 font-mono" x-text="'الضريبة: ' + parseFloat(currentInvoice?.tax_amount || 0).toLocaleString()"></p>
</div>
<!-- Items -->
<div x-show="currentInvoice?.items?.length > 0" class="mt-4 border border-gray-800 rounded-2xl overflow-hidden">
<table class="w-full text-xs text-right">
<thead class="bg-gray-900">
<tr><th class="p-2">البند</th><th class="p-2">الكمية</th><th class="p-2">السعر</th></tr>
</thead>
<tbody class="divide-y divide-gray-800 bg-gray-900/30">
<template x-for="item in currentInvoice?.items" :key="item.id">
<tr>
<td class="p-2" x-text="item.description"></td>
<td class="p-2 font-mono text-gray-400" x-text="item.quantity"></td>
<td class="p-2 font-mono text-emerald-400" x-text="item.unit_price"></td>
</tr>
</template>
</tbody>
</table>
</div>
<!-- QR Code -->
<div x-show="currentInvoice?.jofotara?.qr_image_uri || currentInvoice?.qr_code" class="mt-6 p-4 bg-white rounded-2xl flex justify-center items-center">
<img :src="currentInvoice?.jofotara?.qr_image_uri || currentInvoice?.qr_code" class="w-40 h-40 object-contain" alt="QR Code">
</div>
</div>
<!-- Actions -->
<div class="mt-6 pt-6 border-t border-gray-800 space-y-3">
<button x-show="currentInvoice?.status === 'extracted'" @click="approveInvoice" class="w-full bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold shadow-lg shadow-emerald-900/50 transition-all active:scale-95 btn-glow" :disabled="isBusy">
<span x-show="!isBusy">✔️ اعتماد وإرسال للفوترة</span>
<span x-show="isBusy"> جاري الاعتماد...</span>
</button>
<button x-show="currentInvoice?.status === 'extracted'" class="w-full bg-red-900/30 text-red-400 hover:bg-red-900/50 py-3 rounded-2xl font-bold transition-all"> رفض</button>
<div x-show="currentInvoice?.status === 'approved'" class="w-full bg-emerald-900/30 text-emerald-400 py-3 rounded-2xl font-bold text-center border border-emerald-900/50">
معتمدة مسبقاً
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
user: JSON.parse(localStorage.getItem('user')),
page: 'dashboard',
users: [], companies: [], invoices: [], tenants: [],
stats: { total: 0, pending: 0, approved: 0 },
showAddUserModal: false, showAddCompanyModal: false, showConnectModal: false,
showUploadModal: false, showViewModal: false, showCompanyStatsModal: false,
isBusy: false, globalError: '',
newUser: { name: '', email: '', password: '', role: 'accountant' },
newCompany: { name: '', tax_identification_number: '', commercial_registration_number: '', address: '' },
connectData: { client_id: '', secret_key: '', income_source_sequence: '1' },
uploadData: { company_id: '' },
currentCompany: 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] || ''; },
token() { return localStorage.getItem('access_token'); },
showError(msg) { this.globalError = msg; setTimeout(() => this.globalError = '', 8000); },
async apiRequest(route, method = 'GET', body = null) {
try {
const options = {
method,
headers: { 'Authorization': 'Bearer ' + this.token(), 'Content-Type': 'application/json' }
};
if (body) options.body = JSON.stringify(body);
const res = await fetch('/index.php?route=' + route, options);
const json = await res.json();
if (res.status === 401) { localStorage.clear(); window.location.href = '/login.php'; return null; }
return json.success ? json.data : (this.showError(json.message), null);
} catch (e) { this.showError('فشل الاتصال بالخادم'); return null; }
},
async loadAll() {
this.stats = await this.apiRequest('v1/dashboard/stats') || { total: 0, pending: 0, approved: 0 };
this.companies = await this.apiRequest('v1/companies') || [];
if (this.page === 'users') this.users = await this.apiRequest('v1/users') || [];
if (this.page === 'invoices') this.invoices = await this.apiRequest('v1/invoices') || [];
},
async createUser() {
this.isBusy = true;
const res = await this.apiRequest('v1/users/create', 'POST', this.newUser);
if (res) { this.showAddUserModal = false; this.loadAll(); alert('تم إضافة المستخدم بنجاح'); }
this.isBusy = false;
},
async createCompany() {
this.isBusy = true;
const res = await this.apiRequest('v1/companies/create', 'POST', this.newCompany);
if (res) { this.showAddCompanyModal = false; this.loadAll(); alert('تم إضافة الشركة بنجاح'); }
this.isBusy = false;
},
openConnectModal(company) {
this.currentCompany = company;
this.connectData.client_id = '';
this.connectData.secret_key = '';
this.showConnectModal = true;
},
async connectJoFotara() {
this.isBusy = true;
const res = await this.apiRequest('v1/companies/connect', 'POST', {
id: this.currentCompany.id,
...this.connectData
});
if (res) { this.showConnectModal = false; this.loadAll(); alert('تم تفعيل الربط الضريبي بنجاح'); }
this.isBusy = false;
},
async showCompanyStats(id) {
this.companyStats = await this.apiRequest('v1/companies/stats&id=' + id);
if (this.companyStats) this.showCompanyStatsModal = true;
},
async viewInvoice(id) {
this.currentInvoice = await this.apiRequest('v1/invoices/view&id=' + id);
if (this.currentInvoice) {
this.showViewModal = true;
// Fallback QR code generation if missing from DB but we have invoice data
if (!this.currentInvoice.jofotara?.qr_image_uri && !this.currentInvoice.qr_code && this.currentInvoice.status === 'approved') {
try {
const qr = new QRious({
value: 'Invoice: ' + this.currentInvoice.invoice_number + '\nSeller: ' + this.currentInvoice.supplier_name + '\nTotal: ' + this.currentInvoice.grand_total,
size: 250
});
this.currentInvoice.qr_code = qr.toDataURL();
} catch (e) {}
}
}
},
selectedFile: null,
isUploading: false,
async uploadInvoice() {
if (!this.selectedFile) return alert('الرجاء اختيار ملف');
if (!this.uploadData.company_id) 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.selectedFile = null;
await this.loadAll();
this.viewInvoice(json.data.id);
} else {
this.showError(json.message);
}
} catch (e) {
this.isUploading = false;
this.showError('فشل الاتصال بالخادم أثناء الرفع');
}
},
async approveInvoice() {
if (!this.currentInvoice || this.isBusy) return;
this.isBusy = true;
try {
const res = await fetch('/index.php?route=v1/invoices/approve', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + this.token(), 'Content-Type': 'application/json' },
body: JSON.stringify({ id: this.currentInvoice.id })
});
const json = await res.json();
this.isBusy = false;
if (json.success) {
alert('تم اعتماد الفاتورة بنجاح!');
this.showViewModal = false;
this.loadAll();
} else {
this.showError(json.message);
}
} catch (e) {
this.isBusy = false;
this.showError('حدث خطأ أثناء الاعتماد');
}
},
logout() { localStorage.clear(); window.location.href = '/login.php'; }
}));
});
</script>
</body>
</html>