Update: 2026-05-04 21:54:02
This commit is contained in:
@@ -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])) {
|
||||
|
||||
155
public/shell.php
155
public/shell.php
@@ -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,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user