Update: 2026-05-04 21:54:02

This commit is contained in:
Hamza-Ayed
2026-05-04 21:54:02 +03:00
parent 3d21444d1f
commit 6b940fc4b1
5 changed files with 200 additions and 40 deletions

View File

@@ -37,6 +37,7 @@ $routes = [
'v1/tenants' => ['GET', 'tenants/index.php'],
'v1/tenants/create' => ['POST', 'tenants/create.php'],
'v1/tenants/update' => ['POST', 'tenants/update.php'],
'v1/tenants/stats' => ['GET', 'tenants/stats.php'],
];
if (isset($routes[$route])) {

View File

@@ -128,9 +128,11 @@
<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 text-center">الفواتير</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase text-center">الإجمالي</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>
<th class="p-6 text-xs font-bold text-gray-500 uppercase text-left">إجراءات</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-800/50">
@@ -145,18 +147,20 @@
<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 text-center font-mono text-gray-300" x-text="c.invoices_count || 0"></td>
<td class="p-6 text-center font-mono text-emerald-400 font-bold" x-text="parseFloat(c.total_amount || 0).toLocaleString() + ' JOD'"></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>
<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>
<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">
<td class="p-6 text-left">
<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>
@@ -250,9 +254,10 @@
<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 text-center">الشركات</th>
<th class="p-6 text-xs font-bold text-gray-500 uppercase text-center">الفواتير</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>
@@ -262,6 +267,13 @@
<tr class="hover:bg-white/[0.02] transition-colors">
<td class="p-6">
<p class="font-bold text-emerald-500" x-text="t.name"></p>
<p class="text-[10px] text-gray-500 mt-1" x-text="'انضم: ' + t.created_at.split(' ')[0]"></p>
</td>
<td class="p-6 text-center">
<p class="font-mono text-gray-300" x-text="t.companies_count || 0"></p>
</td>
<td class="p-6 text-center">
<p class="font-mono text-gray-300" x-text="t.invoices_count || 0"></p>
</td>
<td class="p-6 text-sm">
<p class="text-gray-200" x-text="t.email"></p>
@@ -272,9 +284,9 @@
:class="t.status==='active'?'bg-emerald-900/40 text-emerald-400':'bg-yellow-900/40 text-yellow-400'"
x-text="t.status"></span>
</td>
<td class="p-6 text-xs text-gray-500 font-mono" x-text="t.created_at"></td>
<td class="p-6">
<button @click="openEditTenantModal(t)" class="text-gray-500 hover:text-emerald-400 p-2 rounded-lg hover:bg-emerald-400/10 transition-all">⚙️</button>
<button @click="openEditTenantModal(t)" class="text-gray-500 hover:text-emerald-400 p-2 rounded-lg hover:bg-emerald-400/10 transition-all" title="تعديل">⚙️</button>
<button class="text-gray-500 hover:text-blue-400 p-2 rounded-lg hover:bg-blue-400/10 transition-all ml-2" title="تقرير شامل" @click="showTenantStats(t)">📊</button>
</td>
</tr>
</template>
@@ -282,6 +294,62 @@
</table>
</div>
</div>
<!-- Tenant Stats Modal -->
<div x-show="showTenantStatsModal" 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="showTenantStatsModal = false">
<div class="flex justify-between items-start mb-12">
<div>
<h3 class="text-3xl font-bold text-emerald-400" x-text="tenantStats?.tenant?.name"></h3>
<p class="text-xs text-gray-500 mt-2 uppercase tracking-widest font-mono">تقرير أداء المكتب المحاسبي</p>
</div>
<button @click="showTenantStatsModal = 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="tenantStats?.summary?.total_companies || 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">إجمالي الفواتير</p>
<p class="text-3xl font-bold" x-text="tenantStats?.summary?.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(tenantStats?.summary?.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(tenantStats?.summary?.total_tax || 0).toLocaleString()"></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 tenantStats?.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>
</div>
</main>
@@ -619,7 +687,7 @@
showAddUserModal: false, showAddCompanyModal: false, showConnectModal: false,
showUploadModal: false, showViewModal: false, showCompanyStatsModal: false,
showAddTenantModal: false, showEditTenantModal: false,
showAddTenantModal: false, showEditTenantModal: false, showTenantStatsModal: false,
isBusy: false, globalError: '',
newUser: { name: '', email: '', password: '', role: 'accountant', tenant_id: '' },
@@ -627,7 +695,8 @@
newTenant: { name: '', email: '', phone: '', manager_name: '', manager_email: '', manager_password: '' },
connectData: { client_id: '', secret_key: '', income_source_sequence: '1' },
uploadData: { company_id: '' },
currentCompany: null, currentInvoice: null, companyStats: null, currentTenant: { name: '', email: '', phone: '', status: '' },
currentCompany: null, currentInvoice: null, companyStats: null,
currentTenant: { name: '', email: '', phone: '', status: '' }, tenantStats: null,
init() {
if (!this.user) { window.location.href = '/login.php'; return; }
@@ -665,10 +734,7 @@
if (!inv) return '';
if (inv.jofotara?.qr_image_uri) return inv.jofotara.qr_image_uri;
if (inv.qr_code) {
// Check if it's already a data URI
if (inv.qr_code.startsWith('data:')) return inv.qr_code;
// Otherwise, it's raw TLV, generate it using QRious
try {
const qr = new QRious({
value: inv.qr_code,
@@ -680,6 +746,46 @@
return '';
},
async showCompanyStats(companyId) {
this.isBusy = true;
const res = await this.apiRequest('v1/companies/stats&company_id=' + companyId);
if (res) {
this.companyStats = res;
this.showCompanyStatsModal = true;
}
this.isBusy = false;
},
async showTenantStats(tenant) {
this.isBusy = true;
const res = await this.apiRequest('v1/tenants/stats&tenant_id=' + tenant.id);
if (res) {
this.tenantStats = res;
this.tenantStats.tenant = tenant;
this.showTenantStatsModal = true;
}
this.isBusy = false;
},
async viewInvoice(id) {
this.isBusy = true;
const res = await this.apiRequest('v1/invoices/view&id=' + id);
if (res) {
this.currentInvoice = res;
this.showViewModal = true;
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) {}
}
}
this.isBusy = false;
},
async createUser() {
this.isBusy = true;
const res = await this.apiRequest('v1/users/create', 'POST', this.newUser);
@@ -739,29 +845,6 @@
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,