1067 lines
42 KiB
HTML
1067 lines
42 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ar" dir="rtl">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>نابه | لوحة التحكم للمشرف العام</title>
|
|
|
|
<!-- Premium Google Fonts -->
|
|
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
|
|
<!-- Alpine.js CDN -->
|
|
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
|
|
<style>
|
|
:root {
|
|
--bg-app: #030712;
|
|
--bg-glow-1: rgba(99, 102, 241, 0.12); /* Indigo glow */
|
|
--bg-glow-2: rgba(14, 165, 233, 0.08); /* Cyan glow */
|
|
--card-bg: rgba(17, 24, 39, 0.55);
|
|
--card-border: rgba(255, 255, 255, 0.06);
|
|
--card-border-hover: rgba(255, 255, 255, 0.15);
|
|
--primary: #6366f1;
|
|
--primary-hover: #4f46e5;
|
|
--secondary: #0ea5e9;
|
|
--text-main: #f3f4f6;
|
|
--text-muted: #9ca3af;
|
|
--success: #10b981;
|
|
--warning: #f59e0b;
|
|
--danger: #ef4444;
|
|
--font-ar: 'Cairo', sans-serif;
|
|
--font-en: 'Outfit', sans-serif;
|
|
--glass-blur: blur(20px);
|
|
}
|
|
|
|
[dir="ltr"] {
|
|
--font-ar: 'Outfit', sans-serif;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
body {
|
|
background-color: var(--bg-app);
|
|
font-family: var(--font-ar);
|
|
color: var(--text-main);
|
|
min-height: 100vh;
|
|
overflow-x: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
/* Ambient Animated Background Glows */
|
|
.ambient-glows {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
z-index: -1;
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.glow-1 {
|
|
position: absolute;
|
|
top: -10%;
|
|
right: 15%;
|
|
width: 50vw;
|
|
height: 50vw;
|
|
background: radial-gradient(circle, var(--bg-glow-1) 0%, transparent 70%);
|
|
filter: blur(80px);
|
|
animation: floatGlow 12s infinite alternate;
|
|
}
|
|
|
|
.glow-2 {
|
|
position: absolute;
|
|
bottom: -5%;
|
|
left: 10%;
|
|
width: 45vw;
|
|
height: 45vw;
|
|
background: radial-gradient(circle, var(--bg-glow-2) 0%, transparent 75%);
|
|
filter: blur(80px);
|
|
animation: floatGlow 18s infinite alternate-reverse;
|
|
}
|
|
|
|
@keyframes floatGlow {
|
|
0% { transform: translate(0, 0) scale(1); }
|
|
100% { transform: translate(4%, 5%) scale(1.15); }
|
|
}
|
|
|
|
/* Navigation Header */
|
|
header {
|
|
position: sticky;
|
|
top: 0;
|
|
width: 100%;
|
|
background: rgba(3, 7, 18, 0.6);
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
border-bottom: 1px solid var(--card-border);
|
|
z-index: 100;
|
|
padding: 1rem 2rem;
|
|
}
|
|
|
|
.nav-container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.logo-section {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
}
|
|
|
|
.logo-section img {
|
|
height: 36px;
|
|
width: auto;
|
|
}
|
|
|
|
.logo-text {
|
|
font-size: 1.5rem;
|
|
font-weight: 800;
|
|
background: linear-gradient(135deg, #f3f4f6 0%, #a5b4fc 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.nav-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
/* Glassmorphism Buttons */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
padding: 0.6rem 1.2rem;
|
|
border-radius: 12px;
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
cursor: pointer;
|
|
border: 1px solid transparent;
|
|
text-decoration: none;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%);
|
|
color: #ffffff;
|
|
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.35);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 24px rgba(99, 102, 241, 0.5);
|
|
}
|
|
|
|
.btn-glass {
|
|
background: rgba(255, 255, 255, 0.04);
|
|
border: 1px solid var(--card-border);
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.btn-glass:hover {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border-color: var(--card-border-hover);
|
|
}
|
|
|
|
.btn-danger {
|
|
background: rgba(239, 68, 68, 0.15);
|
|
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
color: #fca5a5;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: var(--danger);
|
|
color: #ffffff;
|
|
}
|
|
|
|
/* Container & Grid Layouts */
|
|
.main-container {
|
|
max-width: 1400px;
|
|
margin: 2rem auto;
|
|
padding: 0 1.5rem;
|
|
}
|
|
|
|
/* Stats Cards */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2.5rem;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 18px;
|
|
padding: 1.75rem;
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.stat-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: radial-gradient(circle at 80% 20%, rgba(99, 102, 241, 0.06) 0%, transparent 50%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
border-color: var(--card-border-hover);
|
|
transform: translateY(-3px);
|
|
}
|
|
|
|
.stat-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.stat-title {
|
|
font-size: 0.95rem;
|
|
color: var(--text-muted);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 2.2rem;
|
|
font-weight: 800;
|
|
font-family: var(--font-en);
|
|
background: linear-gradient(135deg, #ffffff 0%, #cbd5e1 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 14px;
|
|
background: rgba(99, 102, 241, 0.1);
|
|
color: var(--primary);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
}
|
|
|
|
/* Glassmorphic Table Card */
|
|
.card {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 20px;
|
|
backdrop-filter: var(--glass-blur);
|
|
-webkit-backdrop-filter: var(--glass-blur);
|
|
padding: 2rem;
|
|
margin-bottom: 2rem;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* Search input styling */
|
|
.search-box {
|
|
display: flex;
|
|
align-items: center;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 12px;
|
|
padding: 0.5rem 1rem;
|
|
max-width: 320px;
|
|
width: 100%;
|
|
}
|
|
|
|
.search-box:focus-within {
|
|
border-color: var(--primary);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.search-box input {
|
|
background: transparent;
|
|
border: none;
|
|
outline: none;
|
|
color: var(--text-main);
|
|
width: 100%;
|
|
font-family: inherit;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Table Design */
|
|
.table-responsive {
|
|
width: 100%;
|
|
overflow-x: auto;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
text-align: right;
|
|
}
|
|
|
|
[dir="ltr"] table {
|
|
text-align: left;
|
|
}
|
|
|
|
th {
|
|
background: rgba(255, 255, 255, 0.02);
|
|
padding: 1rem 1.25rem;
|
|
color: var(--text-muted);
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
border-bottom: 1px solid var(--card-border);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
td {
|
|
padding: 1.25rem;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
|
font-size: 0.9rem;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
tr:hover td {
|
|
background: rgba(255, 255, 255, 0.015);
|
|
}
|
|
|
|
/* Badges */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0.25rem 0.65rem;
|
|
border-radius: 8px;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.badge-success {
|
|
background: rgba(16, 185, 129, 0.12);
|
|
color: var(--success);
|
|
border: 1px solid rgba(16, 185, 129, 0.25);
|
|
}
|
|
|
|
.badge-warning {
|
|
background: rgba(245, 158, 11, 0.12);
|
|
color: var(--warning);
|
|
border: 1px solid rgba(245, 158, 11, 0.25);
|
|
}
|
|
|
|
.badge-danger {
|
|
background: rgba(239, 68, 68, 0.12);
|
|
color: var(--danger);
|
|
border: 1px solid rgba(239, 68, 68, 0.25);
|
|
}
|
|
|
|
.badge-primary {
|
|
background: rgba(99, 102, 241, 0.12);
|
|
color: #a5b4fc;
|
|
border: 1px solid rgba(99, 102, 241, 0.25);
|
|
}
|
|
|
|
/* Usage bars */
|
|
.usage-bar-container {
|
|
width: 100%;
|
|
max-width: 150px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.usage-bar-track {
|
|
height: 6px;
|
|
background: rgba(255, 255, 255, 0.06);
|
|
border-radius: 3px;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.usage-bar-fill {
|
|
height: 100%;
|
|
border-radius: 3px;
|
|
background: linear-gradient(90deg, var(--secondary) 0%, var(--primary) 100%);
|
|
}
|
|
|
|
.usage-bar-text {
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
font-family: var(--font-en);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
/* Modal Overlay & Card styling */
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(3, 7, 18, 0.8);
|
|
backdrop-filter: blur(12px);
|
|
z-index: 200;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.modal-overlay.active {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.modal-content {
|
|
background: #0b0f19;
|
|
border: 1px solid var(--card-border-hover);
|
|
border-radius: 24px;
|
|
max-width: 500px;
|
|
width: 90%;
|
|
padding: 2.25rem;
|
|
transform: scale(0.95);
|
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.modal-overlay.active .modal-content {
|
|
transform: scale(1);
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.modal-close {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.modal-close:hover {
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 1.25rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.form-group label {
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.form-input, select {
|
|
background: rgba(255, 255, 255, 0.04);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 10px;
|
|
padding: 0.75rem 1rem;
|
|
color: var(--text-main);
|
|
outline: none;
|
|
font-family: inherit;
|
|
width: 100%;
|
|
}
|
|
|
|
.form-input:focus, select:focus {
|
|
border-color: var(--primary);
|
|
background: rgba(255, 255, 255, 0.08);
|
|
}
|
|
|
|
/* Multi-Language Custom Font Handling */
|
|
[dir="ltr"] body {
|
|
font-family: var(--font-en);
|
|
}
|
|
|
|
/* Toast Notifications */
|
|
.toast {
|
|
position: fixed;
|
|
bottom: 2rem;
|
|
left: 2rem;
|
|
background: rgba(17, 24, 39, 0.9);
|
|
border-left: 4px solid var(--success);
|
|
padding: 1rem 1.5rem;
|
|
border-radius: 12px;
|
|
color: var(--text-main);
|
|
box-shadow: 0 10px 25px rgba(0,0,0,0.4);
|
|
backdrop-filter: blur(8px);
|
|
z-index: 300;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
transform: translateY(150%);
|
|
opacity: 0;
|
|
}
|
|
|
|
[dir="rtl"] .toast {
|
|
left: auto;
|
|
right: 2rem;
|
|
border-left: none;
|
|
border-right: 4px solid var(--success);
|
|
}
|
|
|
|
.toast.error {
|
|
border-color: var(--danger);
|
|
}
|
|
|
|
.toast.active {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body x-data="superAdminDashboard()" x-init="initDashboard()">
|
|
|
|
<!-- Ambient glowing backgrounds -->
|
|
<div class="ambient-glows">
|
|
<div class="glow-1"></div>
|
|
<div class="glow-2"></div>
|
|
</div>
|
|
|
|
<!-- Navigation Header -->
|
|
<header>
|
|
<div class="nav-container">
|
|
<a href="/admin" class="logo-section">
|
|
<svg width="36" height="36" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<rect width="48" height="48" rx="14" fill="url(#paint0_linear_logo)" />
|
|
<path d="M14 26C14 20.4772 18.4772 16 24 16C29.5228 16 34 20.4772 34 26C34 31.5228 29.5228 36 24 36" stroke="white" stroke-width="3" stroke-linecap="round" />
|
|
<circle cx="24" cy="26" r="4" fill="white" />
|
|
<path d="M21 13L24 10L27 13" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
<defs>
|
|
<linearGradient id="paint0_linear_logo" x1="0" y1="0" x2="48" y2="48" gradientUnits="userSpaceOnUse">
|
|
<stop stop-color="#818cf8" />
|
|
<stop offset="1" stop-color="#4f46e5" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
<span class="logo-text">نابه <span style="font-size: 0.75rem; font-weight: 500; color: var(--secondary);">SuperAdmin</span></span>
|
|
</a>
|
|
|
|
<div class="nav-actions">
|
|
<button class="btn btn-glass" @click="exportChatsHistory()" :disabled="exportLoading">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="margin: 0;" x-show="!exportLoading">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
|
</svg>
|
|
<span x-text="exportLoading ? t('exporting') : t('export_chats')">تصدير المحادثات</span>
|
|
</button>
|
|
<button class="btn btn-glass" @click="toggleLanguage()">
|
|
<span x-text="lang === 'ar' ? 'English' : 'العربية'"></span>
|
|
</button>
|
|
<button class="btn btn-danger" @click="logout()">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
|
</svg>
|
|
<span x-text="t('logout')">تسجيل الخروج</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content Area -->
|
|
<main class="main-container">
|
|
|
|
<!-- Platform statistics -->
|
|
<section class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<span class="stat-title" x-text="t('total_companies')">إجمالي الشركات</span>
|
|
<span class="stat-value" x-text="stats.total_companies">-</span>
|
|
</div>
|
|
<div class="stat-icon">
|
|
<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5m0 0V11m0 5H9m4-3h2m-2 0h-5m-9 0H3m2" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<span class="stat-title" x-text="t('active_connections')">الجلسات المتصلة</span>
|
|
<span class="stat-value" x-text="stats.connected_sessions + ' / ' + stats.total_sessions">-</span>
|
|
</div>
|
|
<div class="stat-icon" style="background: rgba(14, 165, 233, 0.1); border-color: rgba(14, 165, 233, 0.2); color: var(--secondary)">
|
|
<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-info">
|
|
<span class="stat-title" x-text="t('system_status')">حالة النظام</span>
|
|
<span class="stat-value" style="font-size: 1.4rem; font-weight: 700; color: var(--success); margin-top: 0.5rem;" x-text="t('operational')">مستقر</span>
|
|
</div>
|
|
<div class="stat-icon" style="background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.2); color: var(--success)">
|
|
<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Tenancy List -->
|
|
<section class="card">
|
|
<div class="card-header">
|
|
<div class="card-title">
|
|
<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="color: var(--primary);">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
|
</svg>
|
|
<span x-text="t('companies_list')">قائمة الشركات والاشتراكات</span>
|
|
</div>
|
|
|
|
<div class="search-box">
|
|
<input type="text" x-model="searchQuery" :placeholder="t('search_placeholder')">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th x-text="t('col_id')">ID</th>
|
|
<th x-text="t('col_company')">الشركة</th>
|
|
<th x-text="t('col_plan')">الباقة</th>
|
|
<th x-text="t('col_sessions')">الأرقام النشطة</th>
|
|
<th x-text="t('col_usage_api')">رسائل الـ API</th>
|
|
<th x-text="t('col_usage_voice')">الرسائل الصوتية</th>
|
|
<th x-text="t('col_ends_at')">تاريخ الانتهاء</th>
|
|
<th x-text="t('col_actions')">إجراءات</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template x-for="company in filteredCompanies()" :key="company.id">
|
|
<tr>
|
|
<td style="font-family: var(--font-en); font-weight: 600;" x-text="company.id"></td>
|
|
<td>
|
|
<div style="font-weight: 600;" x-text="company.name"></div>
|
|
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 0.15rem;" x-text="company.status"></div>
|
|
</td>
|
|
<td>
|
|
<span class="badge"
|
|
:class="company.plan_name ? 'badge-primary' : 'badge-danger'"
|
|
x-text="company.plan_name ? company.plan_name : t('no_active_plan')">
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-success" x-text="company.active_sessions + ' / ' + company.sessions_count"></span>
|
|
</td>
|
|
<td>
|
|
<!-- Request Usage -->
|
|
<div class="usage-bar-container">
|
|
<div class="usage-bar-track">
|
|
<div class="usage-bar-fill" :style="'width: ' + getUsagePercentage(company.request_usage, company.id === 1 ? 999999 : (company.plan_id == 1 ? 1000 : (company.plan_id == 2 ? 5000 : 20000))) + '%'"></div>
|
|
</div>
|
|
<div class="usage-bar-text">
|
|
<span x-text="company.request_usage"></span>
|
|
<span x-text="company.id === 1 ? '∞' : (company.plan_id == 1 ? '1k' : (company.plan_id == 2 ? '5k' : '20k'))"></span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<!-- Voice Notes Usage -->
|
|
<div class="usage-bar-container">
|
|
<div class="usage-bar-track">
|
|
<div class="usage-bar-fill" style="background: linear-gradient(90deg, #38bdf8 0%, #0ea5e9 100%);" :style="'width: ' + getUsagePercentage(company.voice_usage, company.id === 1 ? 999999 : (company.plan_id == 1 ? 0 : (company.plan_id == 2 ? 100 : 500))) + '%'"></div>
|
|
</div>
|
|
<div class="usage-bar-text">
|
|
<span x-text="company.voice_usage"></span>
|
|
<span x-text="company.id === 1 ? '∞' : (company.plan_id == 1 ? '0' : (company.plan_id == 2 ? '100' : '500'))"></span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span style="font-family: var(--font-en); font-size: 0.85rem;"
|
|
x-text="company.subscription_ends ? formatDate(company.subscription_ends) : '-'">
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-glass" style="padding: 0.4rem 0.8rem; font-size: 0.8rem;" @click="openSubscriptionModal(company)">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="margin: 0;">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
|
</svg>
|
|
<span x-text="t('edit_plan')">تحديث الاشتراك</span>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
<tr x-show="filteredCompanies().length === 0">
|
|
<td colspan="8" style="text-align: center; color: var(--text-muted); padding: 3rem;" x-text="t('no_results')">لا توجد نتائج مطابقة للبحث</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Upgrade / Modify Plan Subscription Modal -->
|
|
<div class="modal-overlay" :class="isModalOpen ? 'active' : ''" @click.self="closeSubscriptionModal()">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 style="font-weight: 800; font-size: 1.2rem;" x-text="t('modal_title') + ': ' + selectedCompany?.name">تحديث اشتراك الشركة</h3>
|
|
<button class="modal-close" @click="closeSubscriptionModal()">×</button>
|
|
</div>
|
|
|
|
<form @submit.prevent="submitSubscription()">
|
|
<div class="form-group">
|
|
<label x-text="t('select_plan')">اختر الباقة</label>
|
|
<select x-model="modalData.plan_id" required>
|
|
<template x-for="plan in plans" :key="plan.id">
|
|
<option :value="plan.id" x-text="plan.name + ' (' + plan.price + '$ / ' + plan.max_sessions + ' ' + t('sessions_label') + ')'"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label x-text="t('duration_days')">مدة الاشتراك بالأيام</label>
|
|
<input type="number" min="1" max="1000" class="form-input" x-model.number="modalData.duration_days" required>
|
|
</div>
|
|
|
|
<div style="margin-top: 2rem; display: flex; gap: 1rem; justify-content: flex-end;">
|
|
<button type="button" class="btn btn-glass" @click="closeSubscriptionModal()" x-text="t('cancel')">إلغاء</button>
|
|
<button type="submit" class="btn btn-primary" :disabled="isSubmitting">
|
|
<span x-show="isSubmitting" x-text="t('saving')">جاري الحفظ...</span>
|
|
<span x-show="!isSubmitting" x-text="t('save_changes')">حفظ التغييرات</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast Notification alerts -->
|
|
<div class="toast" :class="{ 'active': toast.show, 'error': toast.isError }">
|
|
<svg x-show="!toast.isError" width="20" height="20" fill="none" stroke="var(--success)" stroke-width="2.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<svg x-show="toast.isError" width="20" height="20" fill="none" stroke="var(--danger)" stroke-width="2.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
<span x-text="toast.message"></span>
|
|
</div>
|
|
|
|
<!-- App Frontend Dashboard Controller Script -->
|
|
<script>
|
|
function superAdminDashboard() {
|
|
// Localized Bilingual Translations
|
|
const translations = {
|
|
ar: {
|
|
logout: 'تسجيل الخروج',
|
|
total_companies: 'إجمالي الشركات والعملاء',
|
|
active_connections: 'الجلسات والخطوط المتصلة',
|
|
system_status: 'حالة منصة نابه',
|
|
operational: 'يعمل بكفاءة',
|
|
companies_list: 'لوحة التحكم وإدارة الاشتراكات للشركات',
|
|
search_placeholder: 'ابحث باسم الشركة أو الـ ID...',
|
|
col_id: 'الرقم المرجعي',
|
|
col_company: 'الشركة',
|
|
col_plan: 'الباقة الحالية',
|
|
col_sessions: 'الأرقام النشطة',
|
|
col_usage_api: 'الرسائل النصية المستهلكة',
|
|
col_usage_voice: 'الرسائل الصوتية المستهلكة',
|
|
col_ends_at: 'تاريخ انتهاء الباقة',
|
|
col_actions: 'إجراءات',
|
|
no_active_plan: 'بدون باقة نشطة',
|
|
edit_plan: 'تحديث الاشتراك',
|
|
no_results: 'لم يتم العثور على أي نتائج مطابقة لبحثك.',
|
|
modal_title: 'تعديل وتحديث اشتراك شركة',
|
|
select_plan: 'اختر باقة الاشتراك الجديدة',
|
|
sessions_label: 'أرقام',
|
|
duration_days: 'مدة تفعيل الباقة (بالأيام)',
|
|
cancel: 'إلغاء الأمر',
|
|
save_changes: 'حفظ وترقية الباقة',
|
|
saving: 'جاري الحفظ والترقية...',
|
|
success_update: 'تم تحديث اشتراك الشركة وترقية الباقة بنجاح!',
|
|
fail_update: 'فشل تفعيل الاشتراك الجديد. يرجى المحاولة لاحقاً.',
|
|
export_chats: 'تصدير المحادثات',
|
|
exporting: 'جاري التصدير...',
|
|
export_success: 'تم تصدير سجل المحادثات بنجاح!'
|
|
},
|
|
en: {
|
|
logout: 'Log Out',
|
|
total_companies: 'Total Companies',
|
|
active_connections: 'Connected Sessions',
|
|
system_status: 'System Status',
|
|
operational: 'Operational',
|
|
companies_list: 'Platform Tenancy & Subscriptions',
|
|
search_placeholder: 'Search by company name or ID...',
|
|
col_id: 'ID',
|
|
col_company: 'Company',
|
|
col_plan: 'Active Plan',
|
|
col_sessions: 'Sessions Active',
|
|
col_usage_api: 'API Requests Usage',
|
|
col_usage_voice: 'Voice Notes Usage',
|
|
col_ends_at: 'Ends At',
|
|
col_actions: 'Actions',
|
|
no_active_plan: 'No active subscription',
|
|
edit_plan: 'Edit Subscription',
|
|
no_results: 'No companies match your search queries.',
|
|
modal_title: 'Edit Subscription for',
|
|
select_plan: 'Choose Subscription Plan',
|
|
sessions_label: 'sessions',
|
|
duration_days: 'Subscription Duration (Days)',
|
|
cancel: 'Cancel',
|
|
save_changes: 'Apply Subscription',
|
|
saving: 'Updating...',
|
|
success_update: 'Company subscription plan updated successfully!',
|
|
fail_update: 'Failed to update subscription. Please try again.',
|
|
export_chats: 'Export Chats',
|
|
exporting: 'Exporting...',
|
|
export_success: 'Chat history exported successfully!'
|
|
}
|
|
};
|
|
|
|
return {
|
|
lang: localStorage.getItem('nabeh_admin_lang') || 'ar',
|
|
token: localStorage.getItem('nabeh_token') || '',
|
|
user: null,
|
|
stats: {
|
|
total_companies: 0,
|
|
total_sessions: 0,
|
|
connected_sessions: 0
|
|
},
|
|
companies: [],
|
|
plans: [],
|
|
searchQuery: '',
|
|
isModalOpen: false,
|
|
selectedCompany: null,
|
|
modalData: {
|
|
plan_id: null,
|
|
duration_days: 30
|
|
},
|
|
isSubmitting: false,
|
|
exportLoading: false,
|
|
toast: {
|
|
show: false,
|
|
message: '',
|
|
isError: false
|
|
},
|
|
|
|
initDashboard() {
|
|
this.lang = localStorage.getItem('nabeh_admin_lang') || 'ar';
|
|
document.documentElement.dir = this.lang === 'ar' ? 'rtl' : 'ltr';
|
|
document.documentElement.lang = this.lang;
|
|
|
|
// Verify Auth & Admin Privileges
|
|
const storedUser = localStorage.getItem('nabeh_user');
|
|
if (!this.token || !storedUser) {
|
|
this.redirectToLogin();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.user = JSON.parse(storedUser);
|
|
if (parseInt(this.user.company_id) !== 1 || this.user.role !== 'admin') {
|
|
this.redirectToLogin();
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
this.redirectToLogin();
|
|
return;
|
|
}
|
|
|
|
// Load platform stats & plans
|
|
this.fetchStats();
|
|
},
|
|
|
|
toggleLanguage() {
|
|
this.lang = this.lang === 'ar' ? 'en' : 'ar';
|
|
localStorage.setItem('nabeh_admin_lang', this.lang);
|
|
document.documentElement.dir = this.lang === 'ar' ? 'rtl' : 'ltr';
|
|
document.documentElement.lang = this.lang;
|
|
},
|
|
|
|
t(key) {
|
|
return translations[this.lang][key] || key;
|
|
},
|
|
|
|
redirectToLogin() {
|
|
localStorage.removeItem('nabeh_token');
|
|
localStorage.removeItem('nabeh_user');
|
|
window.location.href = '/';
|
|
},
|
|
|
|
logout() {
|
|
this.redirectToLogin();
|
|
},
|
|
|
|
async exportChatsHistory() {
|
|
this.exportLoading = true;
|
|
try {
|
|
const response = await fetch('/api/admin/export-chats', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${this.token}`,
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
this.exportLoading = false;
|
|
|
|
if (result.status === 'success') {
|
|
this.showToast(this.t('export_success'), false);
|
|
window.open(result.download_url, '_blank');
|
|
} else {
|
|
this.showToast(result.error || 'Failed to export chats', true);
|
|
}
|
|
} catch (err) {
|
|
this.exportLoading = false;
|
|
this.showToast('Network error while exporting chats', true);
|
|
}
|
|
},
|
|
|
|
async fetchStats() {
|
|
try {
|
|
const response = await fetch('/api/admin/stats', {
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `Bearer ${this.token}`,
|
|
'Accept': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
this.redirectToLogin();
|
|
return;
|
|
}
|
|
|
|
const result = await response.json();
|
|
if (result.status === 'success') {
|
|
this.stats = result.data.stats;
|
|
this.companies = result.data.companies;
|
|
this.plans = result.data.plans;
|
|
} else {
|
|
this.showToast(result.error || 'Failed to load stats', true);
|
|
}
|
|
} catch (err) {
|
|
this.showToast('Network error loading admin stats', true);
|
|
}
|
|
},
|
|
|
|
filteredCompanies() {
|
|
if (!this.searchQuery) return this.companies;
|
|
const query = this.searchQuery.toLowerCase().trim();
|
|
return this.companies.filter(c =>
|
|
c.name.toLowerCase().includes(query) ||
|
|
c.id.toString() === query
|
|
);
|
|
},
|
|
|
|
getUsagePercentage(usage, limit) {
|
|
if (!limit) return 0;
|
|
if (limit === 999999) return Math.min((usage / 20000) * 100, 100); // Admin Tenancy cap visual at 20k
|
|
return Math.min((usage / limit) * 100, 100);
|
|
},
|
|
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString(this.lang === 'ar' ? 'ar-EG' : 'en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
},
|
|
|
|
openSubscriptionModal(company) {
|
|
this.selectedCompany = company;
|
|
this.modalData.plan_id = company.plan_id || (this.plans[0]?.id || 1);
|
|
this.modalData.duration_days = 30;
|
|
this.isModalOpen = true;
|
|
},
|
|
|
|
closeSubscriptionModal() {
|
|
this.isModalOpen = false;
|
|
this.selectedCompany = null;
|
|
},
|
|
|
|
async submitSubscription() {
|
|
if (!this.selectedCompany) return;
|
|
this.isSubmitting = true;
|
|
|
|
try {
|
|
const response = await fetch('/api/admin/companies/subscribe', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${this.token}`
|
|
},
|
|
body: JSON.stringify({
|
|
company_id: this.selectedCompany.id,
|
|
plan_id: this.modalData.plan_id,
|
|
duration_days: this.modalData.duration_days
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
this.isSubmitting = false;
|
|
|
|
if (result.status === 'success') {
|
|
this.showToast(this.t('success_update'), false);
|
|
this.closeSubscriptionModal();
|
|
this.fetchStats(); // refresh database records visual
|
|
} else {
|
|
this.showToast(result.error || this.t('fail_update'), true);
|
|
}
|
|
} catch (err) {
|
|
this.isSubmitting = false;
|
|
this.showToast('Network error while updating subscription', true);
|
|
}
|
|
},
|
|
|
|
showToast(message, isError) {
|
|
this.toast.message = message;
|
|
this.toast.isError = isError;
|
|
this.toast.show = true;
|
|
|
|
setTimeout(() => {
|
|
this.toast.show = false;
|
|
}, 4000);
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|