Deploy: 2026-05-23 02:42:32

This commit is contained in:
Hamza-Ayed
2026-05-23 02:42:32 +03:00
parent 71e609d869
commit 1814f69ecb
6 changed files with 196 additions and 8 deletions

View File

@@ -114,7 +114,8 @@ class AuthController extends BaseController
'id' => $user['id'], 'id' => $user['id'],
'company_id' => $user['company_id'], 'company_id' => $user['company_id'],
'name' => $user['name'], 'name' => $user['name'],
'role' => $user['role'] 'role' => $user['role'],
'is_super_admin' => (int)$user['company_id'] === 1
] ]
], 200); ], 200);
} }

View File

@@ -60,6 +60,24 @@ class StaffController extends BaseController
return; return;
} }
// Fetch subscription limits for agents
$activeSub = \App\Models\CompanySubscription::findActiveByCompany($companyId);
$maxAgents = 1;
if (isset($request->is_super_admin) && $request->is_super_admin) {
$maxAgents = 999;
} elseif ($activeSub) {
$maxAgents = (int)($activeSub['max_agents'] ?? 1);
}
$currentStaffCount = Database::selectOne("SELECT COUNT(*) as count FROM users WHERE company_id = ? AND role = 'staff'", [$companyId])['count'] ?? 0;
if ($currentStaffCount >= $maxAgents) {
$response->status(400)->json([
'status' => 'error',
'error' => "You have reached the maximum number of staff agents allowed by your plan ({$maxAgents})."
]);
return;
}
$body = $request->getBody(); $body = $request->getBody();
$email = strtolower(trim($body['email'])); $email = strtolower(trim($body['email']));

View File

@@ -102,9 +102,18 @@ class WhatsAppController extends BaseController
$companyId = $request->company_id; $companyId = $request->company_id;
$sessions = WhatsAppSession::findAllByCompany($companyId); $sessions = WhatsAppSession::findAllByCompany($companyId);
$activeSub = \App\Models\CompanySubscription::findActiveByCompany($companyId);
$maxSessions = 1;
if (isset($request->is_super_admin) && $request->is_super_admin) {
$maxSessions = 999;
} elseif ($activeSub) {
$maxSessions = (int)$activeSub['max_sessions'];
}
$response->json([ $response->json([
'status' => 'success', 'status' => 'success',
'data' => $sessions 'data' => $sessions,
'max_sessions' => $maxSessions
]); ]);
} }
@@ -120,8 +129,8 @@ class WhatsAppController extends BaseController
// Fetch subscription limits // Fetch subscription limits
$activeSub = \App\Models\CompanySubscription::findActiveByCompany($companyId); $activeSub = \App\Models\CompanySubscription::findActiveByCompany($companyId);
$maxSessions = 1; $maxSessions = 1;
if ($companyId === 1) { if (isset($request->is_super_admin) && $request->is_super_admin) {
$maxSessions = 10; $maxSessions = 999; // Unlimited for Super Admin
} elseif ($activeSub) { } elseif ($activeSub) {
$maxSessions = (int)$activeSub['max_sessions']; $maxSessions = (int)$activeSub['max_sessions'];
} }

View File

@@ -38,5 +38,6 @@ class AuthMiddleware
$request->user_id = $payload['user_id']; $request->user_id = $payload['user_id'];
$request->company_id = $payload['company_id']; $request->company_id = $payload['company_id'];
$request->role = $payload['role']; $request->role = $payload['role'];
$request->is_super_admin = (int)$payload['company_id'] === 1;
} }
} }

View File

@@ -0,0 +1,36 @@
<?php
require 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();
try {
$pdo = new PDO(
"mysql:host=" . $_ENV['DB_HOST'] . ";dbname=" . $_ENV['DB_NAME'],
$_ENV['DB_USER'],
$_ENV['DB_PASS']
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "=== Running Database Migrations: Super Admin Features ===\n";
// 1. Add max_agents to subscription_plans if not exists
$result = $pdo->query("SHOW COLUMNS FROM `subscription_plans` LIKE 'max_agents'");
if ($result->rowCount() === 0) {
$pdo->exec("ALTER TABLE `subscription_plans` ADD COLUMN `max_agents` INT DEFAULT 1 AFTER `max_sessions`");
echo "✅ Added 'max_agents' column to 'subscription_plans' table.\n";
} else {
echo " Column 'max_agents' already exists in 'subscription_plans' table. Skipping.\n";
}
// 2. Update default plans limits (Starter: 1, Growth: 3, Pro: 10)
$pdo->exec("UPDATE `subscription_plans` SET `max_agents` = 1 WHERE `id` = 1");
$pdo->exec("UPDATE `subscription_plans` SET `max_agents` = 3 WHERE `id` = 2");
$pdo->exec("UPDATE `subscription_plans` SET `max_agents` = 10 WHERE `id` = 3");
echo "✅ Updated default subscription plans limits.\n";
echo "Migration completed successfully!\n";
} catch (PDOException $e) {
echo "❌ Database error: " . $e->getMessage() . "\n";
}

View File

@@ -759,6 +759,12 @@
<div class="dashboard-layout"> <div class="dashboard-layout">
<!-- Left Sidebar Nav --> <!-- Left Sidebar Nav -->
<div class="nav-menu"> <div class="nav-menu">
<!-- Super Admin Dashboard -->
<button class="nav-item" x-show="user?.is_super_admin" :class="{ 'active': activeDashboardTab === 'super_admin' }" @click="activeDashboardTab = 'super_admin'; fetchSuperAdminStats()" id="nav-superadmin-btn">
<span class="nav-icon">👑</span>
<span class="nav-text" x-text="lang === 'ar' ? 'لوحة المشرف العام' : 'Super Admin'"></span>
</button>
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'whatsapp' }" @click="activeDashboardTab = 'whatsapp'" id="nav-whatsapp-btn"> <button class="nav-item" :class="{ 'active': activeDashboardTab === 'whatsapp' }" @click="activeDashboardTab = 'whatsapp'" id="nav-whatsapp-btn">
<span>📱</span> <span x-text="lang === 'ar' ? 'اتصال الواتساب' : 'WhatsApp Connection'"></span> <span>📱</span> <span x-text="lang === 'ar' ? 'اتصال الواتساب' : 'WhatsApp Connection'"></span>
</button> </button>
@@ -799,12 +805,66 @@
</template> </template>
<!-- Panel: WhatsApp Connection --> <!-- Panel: WhatsApp Connection -->
<!-- Super Admin Panel -->
<div class="panel" x-show="activeDashboardTab === 'super_admin'" id="panel-superadmin">
<h2 x-text="lang === 'ar' ? 'لوحة المشرف العام' : 'Super Admin Dashboard'"></h2>
<p class="text-muted" x-text="lang === 'ar' ? 'إدارة الشركات والباقات.' : 'Manage tenants and subscriptions.'"></p>
<div class="dashboard-stats" style="margin-top: 1rem;" x-show="superAdminStats">
<div class="stat-card">
<div class="stat-title" x-text="lang === 'ar' ? 'إجمالي الشركات' : 'Total Companies'"></div>
<div class="stat-value" x-text="superAdminStats?.total_companies"></div>
</div>
<div class="stat-card">
<div class="stat-title" x-text="lang === 'ar' ? 'أرقام الواتساب المربوطة' : 'Connected WhatsApps'"></div>
<div class="stat-value" x-text="superAdminStats?.connected_sessions + ' / ' + superAdminStats?.total_sessions"></div>
</div>
</div>
<h3 style="margin-top:2rem;" x-text="lang === 'ar' ? 'قائمة الشركات' : 'Companies List'"></h3>
<div class="data-table" style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>ID</th>
<th x-text="lang === 'ar' ? 'اسم الشركة' : 'Company Name'"></th>
<th x-text="lang === 'ar' ? 'الباقة الحالية' : 'Current Plan'"></th>
<th x-text="lang === 'ar' ? 'استهلاك الرسائل' : 'Request Usage'"></th>
<th x-text="lang === 'ar' ? 'حالة الواتساب' : 'WhatsApp Status'"></th>
<th x-text="lang === 'ar' ? 'الإجراءات' : 'Actions'"></th>
</tr>
</thead>
<tbody>
<template x-for="company in superAdminCompanies" :key="company.id">
<tr>
<td x-text="company.id"></td>
<td x-text="company.name"></td>
<td x-text="company.plan_name ? company.plan_name : 'No Plan'"></td>
<td x-text="company.request_usage"></td>
<td x-text="company.active_sessions + ' / ' + company.sessions_count"></td>
<td>
<select class="form-input" style="padding: 0.2rem; font-size: 0.85rem;" @change="changeCompanyPlan(company.id, $event.target.value)">
<option value="" disabled selected x-text="lang === 'ar' ? 'تغيير الباقة...' : 'Change Plan...'"></option>
<template x-for="plan in superAdminPlans" :key="plan.id">
<option :value="plan.id" x-text="plan.name"></option>
</template>
</select>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<div class="panel" x-show="activeDashboardTab === 'whatsapp'" id="panel-whatsapp"> <div class="panel" x-show="activeDashboardTab === 'whatsapp'" id="panel-whatsapp">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''">
<h2 style="font-size: 1.4rem; margin: 0;" x-text="lang === 'ar' ? 'إدارة قنوات اتصال واتساب' : 'WhatsApp Session Management'"></h2> <h2 style="font-size: 1.4rem; margin: 0;" x-text="lang === 'ar' ? 'إدارة قنوات اتصال واتساب' : 'WhatsApp Session Management'"></h2>
<div style="display: flex; gap: 0.5rem; align-items: center;" :style="lang === 'ar' ? 'flex-direction: row-reverse' : ''"> <div style="display: flex; gap: 0.5rem; align-items: center;">
<input type="text" x-model="newSessionName" :placeholder="lang === 'ar' ? 'اسم الرقم (مثال: الدعم)' : 'Session Name (e.g. Sales)'" class="form-input" style="max-width: 200px; padding: 0.5rem 0.8rem; font-size: 0.85rem;" id="new-session-name-input"> <span class="badge badge-warning" x-show="whatsappSessions.length >= whatsappMaxSessions" x-text="lang === 'ar' ? 'تم الوصول للحد الأقصى (' + whatsappSessions.length + '/' + whatsappMaxSessions + ')' : 'Limit Reached (' + whatsappSessions.length + '/' + whatsappMaxSessions + ')'"></span>
<button @click="createWhatsappSession()" class="btn btn-primary" style="padding: 0.5rem 1rem; font-size: 0.85rem;" id="btn-create-session" :disabled="actionLoading"> <span class="badge badge-success" x-show="whatsappSessions.length < whatsappMaxSessions" x-text="whatsappSessions.length + ' / ' + whatsappMaxSessions + (lang === 'ar' ? ' أرقام' : ' Numbers')"></span>
<input type="text" x-model="newSessionName" class="form-input" :placeholder="lang === 'ar' ? 'اسم الرقم (مثال: الدعم)' : 'Session Name (e.g. Sales)'" style="width: 250px;" id="new-session-name-input">
<button @click="createWhatsappSession()" class="btn btn-primary" style="padding: 0.5rem 1rem; font-size: 0.85rem;" id="btn-create-session" :disabled="actionLoading || whatsappSessions.length >= whatsappMaxSessions">
<span x-text="lang === 'ar' ? '+ إضافة خط جديد' : '+ Add New Line'"></span> <span x-text="lang === 'ar' ? '+ إضافة خط جديد' : '+ Add New Line'"></span>
</button> </button>
</div> </div>
@@ -1649,9 +1709,13 @@
user: null, user: null,
// Dashboard States // Dashboard States
superAdminStats: null,
superAdminCompanies: [],
superAdminPlans: [],
activeDashboardTab: 'whatsapp', activeDashboardTab: 'whatsapp',
whatsappSession: null, whatsappSession: null,
whatsappSessions: [], whatsappSessions: [],
whatsappMaxSessions: 1,
newSessionName: '', newSessionName: '',
staff: [], staff: [],
showAddStaffModal: false, showAddStaffModal: false,
@@ -1895,7 +1959,57 @@
console.error('Failed to retrieve session status:', err); console.error('Failed to retrieve session status:', err);
} }
}, },
// Super Admin methods
async fetchSuperAdminStats() {
if (!this.user?.is_super_admin) return;
this.actionLoading = true;
try {
const response = await fetch('/api/admin/stats', {
headers: { 'Authorization': `Bearer ${this.token}` }
});
const data = await response.json();
if (response.ok && data.status === 'success') {
this.superAdminStats = data.data.stats;
this.superAdminCompanies = data.data.companies;
this.superAdminPlans = data.data.plans;
}
} catch (err) {
console.error(err);
} finally {
this.actionLoading = false;
}
},
async changeCompanyPlan(companyId, planId) {
if (!planId) return;
if (!confirm(this.lang === 'ar' ? 'هل أنت متأكد من تغيير الباقة لهذه الشركة؟' : 'Are you sure you want to change the subscription plan for this company?')) return;
this.actionLoading = 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: companyId,
plan_id: planId,
duration_days: 30
})
});
const data = await response.json();
if (response.ok && data.status === 'success') {
alert(this.lang === 'ar' ? 'تم تحديث الباقة بنجاح' : 'Plan updated successfully');
await this.fetchSuperAdminStats();
} else {
alert(data.error || 'Failed to update plan');
}
} catch (err) {
console.error(err);
} finally {
this.actionLoading = false;
}
},
async fetchWhatsappSessions() { async fetchWhatsappSessions() {
if (!this.token) return; if (!this.token) return;
try { try {
@@ -1905,6 +2019,15 @@
const data = await response.json(); const data = await response.json();
if (response.ok && data.status === 'success') { if (response.ok && data.status === 'success') {
this.whatsappSessions = data.data || []; this.whatsappSessions = data.data || [];
this.whatsappMaxSessions = data.max_sessions || 1;
if (this.whatsappSessions.length > 0 && (!this.whatsappSession || !this.whatsappSessions.find(s => s.id === this.whatsappSession.id))) {
this.whatsappSession = this.whatsappSessions[0];
this.fetchChatbotSettings();
}
if (this.whatsappSessions.length === 0) {
this.whatsappSession = null;
}
} }
} catch (err) { } catch (err) {
console.error('Failed to retrieve sessions list:', err); console.error('Failed to retrieve sessions list:', err);