Files
musadaq-saas/public/shell.php
2026-05-04 02:24:10 +03:00

352 lines
23 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>
<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>
<!-- (Other pages simplified for brevity in this tool call) -->
</div>
</main>
<!-- 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>
</div>
<div class="p-6 bg-gray-950/50 border-t border-gray-800 flex gap-4">
<button class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-3 rounded-xl font-bold transition"> اعتماد الفاتورة</button>
<button class="flex-1 border border-gray-800 hover:bg-gray-800 py-3 rounded-xl font-bold transition">📝 تعديل البيانات</button>
</div>
</div>
</div>
</div>
<!-- (Other modals like Upload, Add User, etc.) -->
</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,
isUploading: false,
globalError: '',
newUser: { name: '', email: '', password: '', role: 'employee', tenant_id: '' },
uploadData: { company_id: '' },
selectedFile: null,
currentInvoice: 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 apiGet(route) {
const res = await fetch('/index.php?route=' + route, { headers: { 'Authorization': 'Bearer ' + this.token() } });
const json = await res.json();
return json.success ? json.data : (this.showError(json.message), null);
},
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 viewInvoice(id) {
const data = await this.apiGet('v1/invoices/view&id=' + id);
if (data) {
this.currentInvoice = data;
this.showViewModal = true;
}
},
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);
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); // Open view modal immediately!
} else {
this.showError(json.message);
}
},
logout() { localStorage.clear(); window.location.href = '/login.php'; }
}));
});
</script>
</body>
</html>