Deploy: 2026-05-23 02:42:32
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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']));
|
||||||
|
|
||||||
|
|||||||
@@ -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'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
backend/migrate_super_admin_features.php
Normal file
36
backend/migrate_super_admin_features.php
Normal 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";
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user