added User management

This commit is contained in:
Kwesi Banson Jnr
2026-04-24 12:09:36 +00:00
parent 16f2dbbdb6
commit 757f908404
16 changed files with 1156 additions and 366 deletions

View File

@@ -13,6 +13,7 @@
- https://smsportal.clickmlapps.com/ - https://smsportal.clickmlapps.com/
- info@sunking.com - info@sunking.com
- 847SIcgO9sUX - 847SIcgO9sUX
### add ### add
- change pagination to 1000 from 20 - change pagination to 1000 from 20
- remove delivery receipt - remove delivery receipt

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models;
use App\Utilities\ApiCalls;
use Session;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Hash;
class ClientUsersController extends Controller
{
public function indexBak(){
$user_list = Models\ClientSession::get();
$data = [
'page_title' => 'Users',
];
return view('client-users.index', $data);
}
public function index()
{
$data = [
'page_title' => 'Users',
];
return view('client-users.index', $data);
}
// Read: Fetch data for the jQuery table
// public function fetch()
// {
// $sessions = Models\ClientSession::orderBy('id', 'desc')->paginate(5);
// return response()->json(['sessions' => $sessions]);
// }
public function fetch(Request $request)
{
$query = Models\ClientSession::query();
// Check if the search parameter has a value
if ($request->has('search') && !empty($request->search)) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('email', 'LIKE', "%{$search}%")
->orWhere('role', 'LIKE', "%{$search}%");
});
}
$sessions = $query->orderBy('id', 'desc')->paginate(5);
return response()->json(['sessions' => $sessions]);
}
// Create: Store a new record
public function store(Request $request)
{
$request->validate([
'email' => 'required|email|unique:client_sessions,email',
'role' => 'required|string',
'password' => 'required|string'
]);
$session = Models\ClientSession::create([
'email' => $request->email,
'role' => $request->role,
'password' => Hash::make($request->password)
]);
return response()->json(['status' => 'success', 'message' => 'User created successfully!']);
}
public function edit($id)
{
$session = Models\ClientSession::findOrFail($id);
return response()->json(['session' => $session]);
}
public function update(Request $request, $id)
{
$request->validate([
'email' => 'required|email|unique:client_sessions,email,' . $id,
'role' => 'required|string',
'password' => 'required|string'
]);
$session = Models\ClientSession::findOrFail($id);
$session->update([
'email' => $request->email,
'role' => $request->role,
'password' => Hash::make($request->password)
]);
return response()->json(['status' => 'success', 'message' => 'User updated successfully!']);
}
// Delete: Remove record
public function destroy($id)
{
Models\ClientSession::findOrFail($id)->delete();
return response()->json(['status' => 'success', 'message' => 'User deleted successfully!']);
}
}

View File

@@ -83,7 +83,6 @@ class ClientsLoginController extends Controller
$logged_in = ''; $logged_in = '';
$client = Models\ClientSession::where('email', $request->email)->first(); $client = Models\ClientSession::where('email', $request->email)->first();
// dd($client);
if ($client == false) { if ($client == false) {
return redirect()->back()->withErrors(['Invalid credentials']); return redirect()->back()->withErrors(['Invalid credentials']);
} }
@@ -92,13 +91,14 @@ class ClientsLoginController extends Controller
$result_arr = json_decode($result, true); $result_arr = json_decode($result, true);
$logged_in = $result_arr; $logged_in = $result_arr;
// dd($logged_in);
$request->session()->regenerate(true); $request->session()->regenerate(true);
$request->session()->put('current_user.user_id', $logged_in['id']); $request->session()->put('current_user.user_id', $logged_in['id']);
$request->session()->put('current_user.org_id', $logged_in['id']); $request->session()->put('current_user.org_id', $logged_in['id']);
$request->session()->put('current_user.name', $logged_in['name']); $request->session()->put('current_user.name', $logged_in['name']);
$request->session()->put('current_user.email', $logged_in['email']); $request->session()->put('current_user.email', $logged_in['email']);
$request->session()->put('current_user.role', $client['role']);
$request->session()->put('current_user.phoneNumber', $logged_in['phoneNumber']); $request->session()->put('current_user.phoneNumber', $logged_in['phoneNumber']);
$request->session()->put('current_user.status', $logged_in['status']); $request->session()->put('current_user.status', $logged_in['status']);
$request->session()->put('current_user.createdAt', $logged_in['createdAt']); $request->session()->put('current_user.createdAt', $logged_in['createdAt']);

View File

@@ -68,7 +68,7 @@ class ClientsTrafficController extends Controller
'sms_units_arr' => $sms_units_arr, 'sms_units_arr' => $sms_units_arr,
'balance_arr' => $balance_arr 'balance_arr' => $balance_arr
]; ];
return view('client-traffic.index-test', $data); return view('client-traffic.index-main', $data);
} }
public function indexTabulator(Request $request){ public function indexTabulator(Request $request){
$client = new Client(); $client = new Client();

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class RoleMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
#public function handle(Request $request, Closure $next): Response
public function handle(Request $request, Closure $next, string $role): Response
{
if (session('current_user.role') !== $role) {
abort(403, 'You do not have access to this page.');
}
return $next($request);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Traits\HasRoles;
class ClientSession extends Model class ClientSession extends Model
{ {

View File

@@ -4,6 +4,7 @@ use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\CheckClientSession; use App\Http\Middleware\CheckClientSession;
use App\Http\Middleware\RoleMiddleware;
return Application::configure(basePath: dirname(__DIR__)) return Application::configure(basePath: dirname(__DIR__))
@@ -15,6 +16,7 @@ return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) { ->withMiddleware(function (Middleware $middleware) {
$middleware->alias([ $middleware->alias([
'checksession' => CheckClientSession::class, 'checksession' => CheckClientSession::class,
'checkrole' => RoleMiddleware::class,
]); ]);
}) })
->withExceptions(function (Exceptions $exceptions) { ->withExceptions(function (Exceptions $exceptions) {

206
config/permission.php Normal file
View File

@@ -0,0 +1,206 @@
<?php
use Spatie\Permission\DefaultTeamResolver;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related pivots other than defaults
*/
'role_pivot_key' => null, // default 'role_id',
'permission_pivot_key' => null, // default 'permission_id',
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
/*
* Change this if you want to use the teams feature and your related model's
* foreign key is other than `team_id`.
*/
'team_foreign_key' => 'team_id',
],
/*
* When set to true, the method for checking permissions will be registered on the gate.
* Set this to false if you want to implement custom logic for checking permissions.
*/
'register_permission_check_method' => true,
/*
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
*/
'register_octane_reset_listener' => false,
/*
* Events will fire when a role or permission is assigned/unassigned:
* \Spatie\Permission\Events\RoleAttached
* \Spatie\Permission\Events\RoleDetached
* \Spatie\Permission\Events\PermissionAttached
* \Spatie\Permission\Events\PermissionDetached
*
* To enable, set to true, and then create listeners to watch these events.
*/
'events_enabled' => false,
/*
* Teams Feature.
* When set to true the package implements teams using the 'team_foreign_key'.
* If you want the migrations to register the 'team_foreign_key', you must
* set this to true before doing the migration.
* If you already did the migration then you must make a new migration to also
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
* (view the latest version of this package's migration file)
*/
'teams' => false,
/*
* The class to use to resolve the permissions team id
*/
'team_resolver' => DefaultTeamResolver::class,
/*
* Passport Client Credentials Grant
* When set to true the package will use Passports Client to check permissions
*/
'use_passport_client_credentials' => false,
/*
* When set to true, the required permission names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_permission_in_exception' => false,
/*
* When set to true, the required role names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_role_in_exception' => false,
/*
* By default wildcard permission lookups are disabled.
* See documentation to understand supported syntax.
*/
'enable_wildcard_permission' => false,
/*
* The class to use for interpreting wildcard permissions.
* If you need to modify delimiters, override the class and specify its name here.
*/
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
/* Cache-specific settings */
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];

View File

@@ -0,0 +1,134 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$teams = config('permission.teams');
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // permission id
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tableNames = config('permission.table_names');
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
};

24
info.md
View File

@@ -9,3 +9,27 @@ Sam added that we can provide functionality for the App to be useable by multipl
Victor will be able to share feedback on the endpoint change by 24th April. Victor will be able to share feedback on the endpoint change by 24th April.
They have Mozambique and Madagascar offices that we could potentially onboard and Victor will loop us in but mentioned they would require the dashboard functionality They have Mozambique and Madagascar offices that we could potentially onboard and Victor will loop us in but mentioned they would require the dashboard functionality
User Management
Ability to have multiple user logins with different roles. The roles will have different functions and accessibility as shown below;
Administrator
Overall permissions with access to the following;
- SMS messages
- Finance related views ie Current account balance, SMS charges
- Create additional users
- View/dowload reports
- SMS sents
- SMS usage balance over a period
Finance
- Current balance
- SMS usage over a period
- Consumption/Usage reports
Customer Care
- SMS messages
Kindly provide a way to view or extract a report for the following
- Periodic SMS usage (Financial)
- Periodic SMS sent
administrator, finance, customercare

View File

@@ -0,0 +1,125 @@
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const startDateElement = document.getElementById('startDate');
const endDateElement = document.getElementById('endDate');
const startDatepicker = new Datepicker(startDateElement, {
autohide: true,
format: 'yyyy-mm-dd'
});
const endDatepicker = new Datepicker(endDateElement, {
autohide: true,
format: 'yyyy-mm-dd'
});
function sendDailySmsUnits() {
document.getElementById('loadingOverlay').style.display = 'flex';
const endpoint = "{{ route('client.dailysmsunits') }}";
const startDate = startDateElement.value;
const endDate = endDateElement.value;
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN': token },
body: 'start_date=' + encodeURIComponent(startDate) + '&end_date=' + encodeURIComponent(endDate)
})
.then(response => response.json())
.then(data => {
// document.getElementById('loadingSpinner').style.display = 'none';
document.getElementById('loadingOverlay').style.display = 'none';
const theReportRange = document.getElementById('reportRange');
const theSmsUnitsValue = document.getElementById('smsUnitsValue');
theReportRange.innerHTML = data.reportDate;
theSmsUnitsValue.innerHTML = "SMS Units : " + data.smsUnits + "| Charge : " + data.clientChargeTotal.toFixed(2);
})
.catch(error => console.error('Error:', error));
}
endDateElement.addEventListener('changeDate', sendDailySmsUnits);
function statusDesign (cell, formatterParams){
var value = cell.getValue();
if (value !== null) {
if(value.includes('SENT')){
return "<span style='color:#3FB449; font-weight:bold;'>" + value + "</span>";
}
else{
return "<span style='color:#E4A11B;'>" + value + "</span>";
}
}
}
var table = new Tabulator("#message-table", {
ajaxURL: base_url = "client-traffic-tabulator", // "https://smsportal.clickmlapps.com/client-traffic-tabulator/",
ajaxConfig: {
method: "GET",
headers: {
"Content-type": "application/json; charset=utf-8",
},
},
ajaxParams: {size: 1000},
pagination: "remote",
paginationSize: 20,
paginationDataSent: {
"page": "page",
"size": "size"
},
paginationDataReceived: {
"last_page": "totalPages",
"data": "content",
"current_page": "number",
"total": "totalElements"
},
ajaxResponse: function(url, params, response) {
return response.content;
},
columns: [
{title: "Sender", field: "from", width:150, headerFilter:"input"},
{title: "Msisdn", field: "to", width:150, headerFilter:"input"},
{title:"Message", field:"message", width:650, formatter:"textarea", headerFilter:"input"},
{
title:"Date Created",
field:"createdAt",
width:200,
formatter:"datetime",
formatterParams:{
inputFormat:"iso",
outputFormat:"yyyy-MM-dd",
invalidPlaceholder:"(invalid date)"
},
headerFilter:function(cell, onRendered, success, cancel){
// Create native date input
var input = document.createElement("input");
input.type = "date";
input.addEventListener("change", function(){
console.log(input.value);
success(input.value); // pass value to Tabulator filter
});
return input;
},
headerFilterFunc:function(headerValue, rowValue){
if(!headerValue){ return true; } // no filter
if(!rowValue){ return false; }
// Extract just the date portion from ISO timestamp
const rowDate = new Date(rowValue);
const formatted = rowDate.toISOString().split("T")[0]; // yyyy-MM-dd
return formatted === headerValue;
}
}
],
});
document.getElementById("download-pdf").addEventListener("click", function(){
table.download("pdf", "messages.pdf", {
orientation:"portrait", // portrait or landscape
title:"Messages Export", // document title
});
});
document.getElementById("download-xlsx").addEventListener("click", function(){
table.download("xlsx", "messages.xlsx", {sheetName:"Messages"});
});

View File

@@ -1,217 +1,192 @@
$(document).ready(function(){ $(document).ready(function() {
// $('.editUserBtn').click(function(evnt){ $.ajaxSetup({
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
// });
//
console.log('foo bar');
$('#editAllowedApps').select2({
// width: "resolve",
dropdownParent: $('#editUserModal'),
placeholder : "Select options, multiple allowed"
}); });
$('#allowedApps').select2({ const sessionModal = new bootstrap.Modal(document.getElementById('sessionModal'));
// width: "resolve",
dropdownParent: $('#addUserModal'), let currentPage = 1;
placeholder : "Select options, multiple allowed" let searchQuery = '';
let searchTimer;
fetchSessions(currentPage, searchQuery);
function fetchSessions(page, search = '') {
$.ajax({
type: "GET",
url: base_url + `/fetch-client-users?page=${page}&search=${encodeURIComponent(search)}`,
dataType: "json",
success: function(response) {
$('#clientUsersTableBody').html("");
if (response.sessions.data && response.sessions.data.length > 0) {
$.each(response.sessions.data, function(key, item) {
$('#clientUsersTableBody').append(`
<tr>
<td>${item.id}</td>
<td>${item.email}</td>
<td>${item.role}</td>
<td>
<button class="btn btn-success btn-sm editBtn" value="${item.id}">Edit</button>
<button class="btn btn-danger btn-sm deleteBtn" value="${item.id}">Delete</button>
</td>
</tr>
`);
}); });
$('#inputPermissions').select2({ let from = response.sessions.from;
// width: "resolve", let to = response.sessions.to;
dropdownParent: $('#editUserModal'), let total = response.sessions.total;
placeholder : "Select options, multiple allowed" $('#paginationCounter').html(`Showing ${from} to ${to} of ${total} results`);
} else {
$('#clientUsersTableBody').html("<tr><td colspan='4' class='text-center'>No users found</td></tr>");
$('#paginationCounter').html('Showing 0 to 0 of 0 results');
}
renderPagination(response);
}
});
}
$('#searchInput').on('keyup', function() {
clearTimeout(searchTimer);
searchQuery = $(this).val();
// Wait 500ms after the user stops typing to trigger the request
searchTimer = setTimeout(function() {
currentPage = 1; // Reset to page 1 for a brand new search
fetchSessions(currentPage, searchQuery);
}, 500);
}); });
$('.editUserBtn').click(function(evnt){ $(document).on('click', '.page-link', function(e) {
evnt.preventDefault(); e.preventDefault();
var selectedUserId = $(this).siblings('.userIdinput').val();
const formData = new FormData(); if ($(this).parent().hasClass('disabled')) {
formData.append('user_id', selectedUserId); return false;
}
let page = $(this).data('page');
if (page > 0) {
currentPage = page;
fetchSessions(currentPage, searchQuery);
}
});
function renderPagination(response) {
let linksHtml = '';
currentPage = response.sessions.current_page;
let lastPage = response.sessions.last_page;
let prevDisabled = currentPage === 1 ? 'disabled' : '';
let prevTabIndex = currentPage === 1 ? 'tabindex="-1"' : '';
linksHtml += `
<li class="page-item ${prevDisabled}">
<a class="page-link" href="#" data-page="${currentPage - 1}" ${prevTabIndex}>Previous</a>
</li>`;
for (let i = 1; i <= lastPage; i++) {
let activeClass = currentPage === i ? 'active' : '';
let activeAria = currentPage === i ? 'aria-current="page"' : '';
linksHtml += `
<li class="page-item ${activeClass}" ${activeAria}>
<a class="page-link" href="#" data-page="${i}">${i}</a>
</li>`;
}
let nextDisabled = currentPage === lastPage ? 'disabled' : '';
let nextTabIndex = currentPage === lastPage ? 'tabindex="-1"' : '';
linksHtml += `
<li class="page-item ${nextDisabled}">
<a class="page-link" href="#" data-page="${currentPage + 1}" ${nextTabIndex}>Next</a>
</li>`;
$('#paginationLinks').html(linksHtml);
}
$(document).on('click', '.page-link', function(e) {
e.preventDefault();
let page = $(this).data('page');
if (page > 0) {
fetchSessions(page);
}
});
$('#addNewBtn').click(function() {
$('#sessionForm')[0].reset();
$('#sessionId').val('');
$('#modalTitle').text('Add Session');
sessionModal.show();
});
$('#sessionForm').submit(function(e) {
e.preventDefault();
let id = $('#sessionId').val();
let data = {
email: $('#email').val(),
role: $('#role').val(),
password: $('#password').val()
};
let url = id ? base_url + `/client-users/${id}` : base_url + '/client-users';
let type = id ? "PUT" : "POST";
$.ajax({ $.ajax({
url: base_url + '/users/edit/' + selectedUserId, type: type,
type: 'GET', url: url,
processData: false, data: data,
contentType: false, dataType: "json",
beforeSend: function() { success: function(response) {
$('#editSuccessArea').text(""); sessionModal.hide();
$('#editErrorArea').text("Please wait ... loading user details!"); showAlert(response.message, 'success');
fetchSessions(currentPage);
}, },
success: function(data) { error: function(xhr) {
var jason = data.data; let errors = xhr.responseJSON.errors;
if(data.success == true){ if(errors.email) showAlert(errors.email, 'danger');
var allowedAppsArray = []; if(errors.role) showAlert(errors.role, 'danger');
if (jason['allowed_apps']) {
allowedAppsArray = jason['allowed_apps'].split(",");
}
console.log(jason['full_name']);
$('#editFullName').val(jason['full_name']);
$('#editEmail').val(jason['email']);
$('#editUsername').val(jason['username']);
$('#editGender').val(jason['gender']);
$('#editTitle').val(jason['title']);
$('#editUaPostion').val(jason['ua_position']);
$('#editPhone').val(jason['phone']);
$('#editAllowedApps').val(allowedAppsArray).trigger('change');
$('#editRegionID').val(jason['region_id']);
$('#editDistrictId').val(jason['district_id']);
$('#editUaPostion').val(jason['ua_position']);
$('#editGender').val(jason['gender']);
$("input[name='user_id']").val(jason.ua_id);
}
//$('#editUserModal').modal('show');
},
error: function(xhr, status, error) {
console.error('Error:', error);
$('#errorArea').text(error);
$('#errorArea').text(error);
} }
}); });
}); });
$('.viewUserBtn').click(function(evnt){ $(document).on('click', '.editBtn', function() {
evnt.preventDefault(); let id = $(this).val();
var selectedUserId = $(this).siblings('.userIdinput').val(); $.get(base_url + `/client-users/${id}/edit`, function(response) {
$('#sessionId').val(response.session.id);
const formData = new FormData(); $('#email').val(response.session.email);
formData.append('user_id', selectedUserId); $('#role').val(response.session.role);
$('#modalTitle').text('Edit Session');
sessionModal.show();
});
});
$(document).on('click', '.deleteBtn', function() {
let id = $(this).val();
if(confirm('Are you sure you want to delete this session?')) {
$.ajax({ $.ajax({
url: base_url + '/users/' + selectedUserId, type: "DELETE",
type: 'GET', url: base_url + `/client-users/${id}`,
processData: false, success: function(response) {
contentType: false, showAlert(response.message, 'success');
beforeSend: function() { fetchSessions(currentPage); // Stay on current page
$('#viewSuccessArea').text("");
$('#viewErrorArea').text("Please wait ... loading user details!");
},
success: function(data) {
var jason = data.data;
if(data.success == true){
var allowedAppsArray = [];
if (jason['allowed_apps']) {
allowedAppsArray = jason['allowed_apps'].split(",");
} }
console.log(jason['full_name']); });
$('#viewFullName').val(jason['full_name']);
$('#viewEmail').val(jason['email']);
$('#viewUsername').val(jason['username']);
$('#viewGender').val(jason['gender']);
$('#viewTitle').val(jason['title']);
$('#viewUaPostion').val(jason['ua_position']);
$('#viewPhone').val(jason['phone']);
$('#viewAllowedApps').val(allowedAppsArray).trigger('change');
$('#viewRegionID').val(jason['region_id']);
$('#viewDistrictId').val(jason['district_id']);
$('#viewUaPostion').val(jason['ua_position']);
$('#viewGender').val(jason['gender']);
$("input[name='user_id']").val(jason.ua_id);
}
//$('#editUserModal').modal('show');
},
error: function(xhr, status, error) {
console.error('Error:', error);
$('#errorArea').text(error);
$('#errorArea').text(error);
} }
}); });
}); function showAlert(message, type) {
$('#alertArea').html(`
$("#newUserForm").submit(function(evt){ <div class="alert alert-${type} alert-dismissible fade show" role="alert">
evt.preventDefault(); ${message}
$('#successArea').addClass('d-none'); <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
$('#errorsArea').removeClass('d-none'); </div>
var formData = new FormData($(this)[0]); `);
$.ajax({
url: base_url + '/users',
type: 'POST',
data: formData,
processData: false,
contentType: false,
beforeSend: function() {
$('#successArea').text("");
$('#successArea').text("Please wait ... user creation in progress!");
},
success: function(data) {
if (data['success'] == true) {
$('#successArea').removeClass('d-none');
$('#errorsArea').addClass('d-none');
$('#successArea').text("");
$('#successArea').text("User successfully created!");
// location.reload();
setTimeout(function() {
location.reload(); // Reloads the current page
}, 15000);
} }
else{
$('#successArea').addClass('d-none');
$('#errorArea').removeClass('d-none');
$('#errorArea').text("");
$('#errorArea').text("User could not be created!");
}
},
error: function(xhr, status, error) {
console.error('Error:', error);
$('#successArea').text(error);
$('#successArea').text(error);
}
});
});
$("#editUserForm").submit(function(evt){
evt.preventDefault();
$('#successArea').addClass('d-none');
$('#errorsArea').removeClass('d-none');
var formData = new FormData($(this)[0]);
$.ajax({
url: base_url + '/users/update/',
type: 'POST',
data: formData,
processData: false,
contentType: false,
beforeSend: function() {
// $('#updateBtn').addClass('d-none');
// $('#uodateProgressBtn').removeClass('d-none');
// $('#updateResultsDiv').removeClass('d-none');
// $('#updateResultsParagraph').text("Processing Please wait ...");
},
success: function(data) {
console.log(data);
$('#editSuccessArea').removeClass('d-none');
$('#editErrorArea').addClass('d-none');
$('#editSuccessArea').text("");
$('#editSuccessArea').text("User successfully details updated!");
},
error: function(xhr, status, error) {
console.error('Error:', error);
$('#editSuccessArea').text(error);
$('#editErrorArea').text(error);
location.reload();
}
});
});
$('#regionID').change(function(){
var options = $('#districtID');
var region_id = $('#regionID').val();
$.get( base_url + '/admin/districts/' + region_id, function (data) {
$('#districtID').empty();
$.each(data['districts'], function(id, row) {
$('#districtID').append($("<option />").val(row.districtid).text(row.district_name));
});
});
});
}); });

View File

@@ -35,10 +35,12 @@
</div> </div>
</div> --> </div> -->
<div class="col-12"> <div class="col-12">
@if(in_array(session('current_user.role'), ['administrator', 'finance']))
<div class="rounded-4 p-3 bg-white bg-opacity-10"> <div class="rounded-4 p-3 bg-white bg-opacity-10">
<div class="small opacity-75">SMS Account Balance</div> <div class="small opacity-75">SMS Account Balance</div>
<div class="h3 mb-0" id="mainSmsBalance">{{ number_format($balance_arr['balance']) }}</div> <div class="h3 mb-0" id="mainSmsBalance">{{ number_format($balance_arr['balance']) }}</div>
</div> </div>
@endif
</div> </div>
</div> </div>
</div> </div>
@@ -79,9 +81,9 @@
</div> </div>
<div class="muted-label mb-2">Sent Messages</div> <div class="muted-label mb-2">Sent Messages</div>
<!-- <div class="h3 mb-2">183,372</div> --> <!-- <div class="h3 mb-2">183,372</div> -->
<div class="h3 mb-2 pt-1" id="smsUnitsValue">SMS Units : {{ $sms_units_arr['smsUnits'] }}</div> <div class="h3 mb-2 pt-1" id="smsUnitsValue">SMS Units : {{ $sms_units_arr['smsUnits'] }} | Charge : {{ number_format($sms_units_arr['clientChargeTotal'], 2) }}</div>
<div class="mini-chart"><span style="width: 98%;"></span></div> <div class="mini-chart"><span style="width: 57%;"></span></div>
</article> </article>
</div> </div>
@@ -151,156 +153,6 @@
<script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script> <script type="text/javascript" src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script>
<script src="{{ url('public/libs/tabulator-master/dist/js/autotable.min.js') }}"></script> <script src="{{ url('public/libs/tabulator-master/dist/js/autotable.min.js') }}"></script>
<script src=" https://cdn.jsdelivr.net/npm/luxon@3.7.2/build/global/luxon.min.js "></script> <script src="{{ url('public/assets/js/traffic-mgt.js') }}"></script>
<script>
console.log(base_url);
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const startDateElement = document.getElementById('startDate');
const endDateElement = document.getElementById('endDate');
const startDatepicker = new Datepicker(startDateElement, {
autohide: true,
format: 'yyyy-mm-dd'
});
const endDatepicker = new Datepicker(endDateElement, {
autohide: true,
format: 'yyyy-mm-dd'
});
function sendDailySmsUnits() {
// document.getElementById('loadingSpinner').style.display = 'inline-block';
document.getElementById('loadingOverlay').style.display = 'flex';
const endpoint = "{{ route('client.dailysmsunits') }}";
const startDate = startDateElement.value;
const endDate = endDateElement.value;
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRF-TOKEN': token },
body: 'start_date=' + encodeURIComponent(startDate) + '&end_date=' + encodeURIComponent(endDate)
})
.then(response => response.json())
.then(data => {
// document.getElementById('loadingSpinner').style.display = 'none';
document.getElementById('loadingOverlay').style.display = 'none';
const theReportRange = document.getElementById('reportRange');
const theSmsUnitsValue = document.getElementById('smsUnitsValue');
theReportRange.innerHTML = data.reportDate;
theSmsUnitsValue.innerHTML = "SMS Units : " + data.smsUnits;
})
.catch(error => console.error('Error:', error));
}
//startDateElement.addEventListener('changeDate', sendDailySmsUnits);
endDateElement.addEventListener('changeDate', sendDailySmsUnits);
function statusDesign (cell, formatterParams){
var value = cell.getValue();
// if(value === 'Approved'){
// console.log(value !== null);
if (value !== null) {
if(value.includes('SENT')){
return "<span style='color:#3FB449; font-weight:bold;'>" + value + "</span>";
}
// else if(value.includes('Active')){
// return "<span style='color:#3FB449; font-weight:bold;'>" + value + "</span>";
// }
else{
return "<span style='color:#E4A11B;'>" + value + "</span>";
}
}
}
var table = new Tabulator("#message-table", {
ajaxURL: base_url = "client-traffic-tabulator", // "https://smsportal.clickmlapps.com/client-traffic-tabulator/",
ajaxConfig: {
method: "GET",
headers: {
"Content-type": "application/json; charset=utf-8",
},
},
ajaxParams: {size: 1000},
pagination: "remote",
paginationSize: 20,
paginationDataSent: {
"page": "page",
"size": "size"
},
paginationDataReceived: {
"last_page": "totalPages",
"data": "content",
"current_page": "number",
"total": "totalElements"
},
ajaxResponse: function(url, params, response) {
return response.content;
},
// columns: [
// {title: "Sender", field: "from", width:150, headerFilter:"input"},
// {title: "Msisdn", field: "to", width:150, headerFilter:"input"},
// {title:"Message", field:"message", width:650, formatter:"textarea", headerFilter:"input"},
// {title:"Date Created ", field:"createdAt", width:200, formatter:"datetime", headerFilter:"input", formatterParams:{
// inputFormat: "iso",
// outputFormat: "dd-MM-yyyy HH:mm:ss",
// invalidPlaceholder: "(invalid date)"
// }}
// ],
columns: [
{title: "Sender", field: "from", width:150, headerFilter:"input"},
{title: "Msisdn", field: "to", width:150, headerFilter:"input"},
{title:"Message", field:"message", width:650, formatter:"textarea", headerFilter:"input"},
{
title:"Date Created",
field:"createdAt",
width:200,
formatter:"datetime",
formatterParams:{
inputFormat:"iso",
outputFormat:"yyyy-MM-dd",
invalidPlaceholder:"(invalid date)"
},
headerFilter:function(cell, onRendered, success, cancel){
// Create native date input
var input = document.createElement("input");
input.type = "date";
input.addEventListener("change", function(){
console.log(input.value);
success(input.value); // pass value to Tabulator filter
});
return input;
},
headerFilterFunc:function(headerValue, rowValue){
if(!headerValue){ return true; } // no filter
if(!rowValue){ return false; }
// Extract just the date portion from ISO timestamp
const rowDate = new Date(rowValue);
const formatted = rowDate.toISOString().split("T")[0]; // yyyy-MM-dd
return formatted === headerValue;
}
}
],
});
document.getElementById("download-pdf").addEventListener("click", function(){
table.download("pdf", "messages.pdf", {
orientation:"portrait", // portrait or landscape
title:"Messages Export", // document title
});
});
document.getElementById("download-xlsx").addEventListener("click", function(){
table.download("xlsx", "messages.xlsx", {sheetName:"Messages"});
});
</script>
@endsection @endsection

View File

@@ -0,0 +1,327 @@
@extends('layouts.master')
@section('page-title')
{{ $page_title }}
@endsection
@section('page-css')
@endsection
@section('content')
<section class="row g-4">
<div class="col-xl-10">
<div class="filter-card mb-4">
<div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3 mb-3">
<div>
<h2 class="h4 mb-1">User Management</h2>
</div>
</div>
<div class="row g-3">
<div class="col-md-6 col-lg-3">
<!-- <label for="search" class="form-label fw-semibold">Search</label> -->
<input id="searchInput" type="text" class="form-control" placeholder="search email, role">
</div>
</div>
</div>
<div class="traffic-table-card">
<div class="table-responsive">
<div class="float-end">
<button class="btn btn-ghost px-4" id="addNewBtn"><i class="bi bi-person me-2 text-danger"></i>Add New User</button>
</div>
<table class="table align-middle mb-0">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
<th scope="col">Date Created</th>
</tr>
</thead>
<tbody id="clientUsersTableBody"></tbody>
</table>
<!-- <div class="d-flex justify-content-center mt-4">
<nav aria-label="Page navigation">
<ul class="pagination" id="paginationLinks"></ul>
</nav>
</div> -->
<div class="d-flex justify-content-between align-items-center mt-4">
<div class="text-muted" id="paginationCounter"></div>
<nav aria-label="Page navigation">
<ul class="pagination mb-0" id="paginationLinks"></ul>
</nav>
</div>
</div>
</div>
</div>
<div class="col-xl-3">
<!-- <aside class="detail-card">
<h2 class="h5 mb-3">Recent activity</h2>
<div class="timeline-item pt-0 mt-0 border-0">
<div class="fw-semibold">Sender ID approved</div>
<div class="muted-label">CLICKINFO added </div>
<div class="small text-secondary mt-1">09:04</div>
</div>
<div class="timeline-item">
<div class="fw-semibold">Retry queue triggered</div>
<div class="muted-label">42 Zambia messages re-routed after timeout</div>
<div class="small text-secondary mt-1">08:57</div>
</div>
<div class="timeline-item">
<div class="fw-semibold">Campaign completed</div>
<div class="muted-label">[campaign name] batch finished</div>
<div class="small text-secondary mt-1">08:41</div>
</div>
</aside> -->
</div>
</section>
<div class="modal fade" id="sessionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTitle">Add Session</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="sessionForm">
<div class="modal-body">
<input type="hidden" id="sessionId">
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" required>
</div>
<div class="mb-3">
<label for="role" class="form-label">Role</label>
<select class="form-control" name="role" id="role">
<option value="">--Select--</option>
<option value="administrator">Administrator</option>
<option value="customercare">Customer Care</option>
<option value="finance">Finance</option>
</select>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" name="password" class="form-control" id="password" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" id="saveBtn">Save changes</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('page-js')
<script src="{{ url('public/assets/js/usermgt.js') }}"></script>
<script>
// $(document).ready(function() {
// $.ajaxSetup({
// headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
// });
// const sessionModal = new bootstrap.Modal(document.getElementById('sessionModal'));
// let currentPage = 1;
// let searchQuery = '';
// let searchTimer; // For debouncing
// fetchSessions(currentPage, searchQuery);
// // 1. UPDATED FETCH SESSIONS METHOD
// function fetchSessions(page, search = '') {
// $.ajax({
// type: "GET",
// // Pass both page and search queries to the URL
// url: base_url + `/fetch-client-users?page=${page}&search=${encodeURIComponent(search)}`,
// dataType: "json",
// success: function(response) {
// $('#clientUsersTableBody').html("");
// if (response.sessions.data && response.sessions.data.length > 0) {
// $.each(response.sessions.data, function(key, item) {
// $('#clientUsersTableBody').append(`
// <tr>
// <td>${item.id}</td>
// <td>${item.email}</td>
// <td>${item.role}</td>
// <td>
// <button class="btn btn-success btn-sm editBtn" value="${item.id}">Edit</button>
// <button class="btn btn-danger btn-sm deleteBtn" value="${item.id}">Delete</button>
// </td>
// </tr>
// `);
// });
// let from = response.sessions.from;
// let to = response.sessions.to;
// let total = response.sessions.total;
// $('#paginationCounter').html(`Showing ${from} to ${to} of ${total} results`);
// } else {
// $('#clientUsersTableBody').html("<tr><td colspan='4' class='text-center'>No users found</td></tr>");
// $('#paginationCounter').html('Showing 0 to 0 of 0 results');
// }
// renderPagination(response);
// }
// });
// }
// // 2. LIVE SEARCH EVENT (WITH DEBOUNCE)
// $('#searchInput').on('keyup', function() {
// clearTimeout(searchTimer);
// searchQuery = $(this).val();
// // Wait 500ms after the user stops typing to trigger the request
// searchTimer = setTimeout(function() {
// currentPage = 1; // Reset to page 1 for a brand new search
// fetchSessions(currentPage, searchQuery);
// }, 500);
// });
// // 3. UPDATED PAGINATION CLICK LISTENER
// $(document).on('click', '.page-link', function(e) {
// e.preventDefault();
// if ($(this).parent().hasClass('disabled')) {
// return false;
// }
// let page = $(this).data('page');
// if (page > 0) {
// currentPage = page;
// // Pass both the target page and the current typed search string
// fetchSessions(currentPage, searchQuery);
// }
// });
// function renderPagination(response) {
// let linksHtml = '';
// currentPage = response.sessions.current_page;
// let lastPage = response.sessions.last_page;
// let prevDisabled = currentPage === 1 ? 'disabled' : '';
// let prevTabIndex = currentPage === 1 ? 'tabindex="-1"' : '';
// linksHtml += `
// <li class="page-item ${prevDisabled}">
// <a class="page-link" href="#" data-page="${currentPage - 1}" ${prevTabIndex}>Previous</a>
// </li>`;
// for (let i = 1; i <= lastPage; i++) {
// let activeClass = currentPage === i ? 'active' : '';
// let activeAria = currentPage === i ? 'aria-current="page"' : '';
// linksHtml += `
// <li class="page-item ${activeClass}" ${activeAria}>
// <a class="page-link" href="#" data-page="${i}">${i}</a>
// </li>`;
// }
// let nextDisabled = currentPage === lastPage ? 'disabled' : '';
// let nextTabIndex = currentPage === lastPage ? 'tabindex="-1"' : '';
// linksHtml += `
// <li class="page-item ${nextDisabled}">
// <a class="page-link" href="#" data-page="${currentPage + 1}" ${nextTabIndex}>Next</a>
// </li>`;
// $('#paginationLinks').html(linksHtml);
// }
// $(document).on('click', '.page-link', function(e) {
// e.preventDefault();
// let page = $(this).data('page');
// // Prevent clicking disabled boundaries
// if (page > 0) {
// fetchSessions(page);
// }
// });
// // Reset forms and trigger reloads to remain on active pages
// $('#addNewBtn').click(function() {
// $('#sessionForm')[0].reset();
// $('#sessionId').val('');
// $('#modalTitle').text('Add Session');
// sessionModal.show();
// });
// $('#sessionForm').submit(function(e) {
// e.preventDefault();
// let id = $('#sessionId').val();
// let data = {
// email: $('#email').val(),
// role: $('#role').val()
// };
// let url = id ? base_url + `/client-users/${id}` : base_url + '/client-users';
// let type = id ? "PUT" : "POST";
// $.ajax({
// type: type,
// url: url,
// data: data,
// dataType: "json",
// success: function(response) {
// sessionModal.hide();
// showAlert(response.message, 'success');
// fetchSessions(currentPage); // Stay on current page
// },
// error: function(xhr) {
// let errors = xhr.responseJSON.errors;
// if(errors.email) showAlert(errors.email, 'danger');
// if(errors.role) showAlert(errors.role, 'danger');
// }
// });
// });
// $(document).on('click', '.editBtn', function() {
// let id = $(this).val();
// $.get(base_url + `/client-users/${id}/edit`, function(response) {
// $('#sessionId').val(response.session.id);
// $('#email').val(response.session.email);
// $('#role').val(response.session.role);
// $('#modalTitle').text('Edit Session');
// sessionModal.show();
// });
// });
// $(document).on('click', '.deleteBtn', function() {
// let id = $(this).val();
// if(confirm('Are you sure you want to delete this session?')) {
// $.ajax({
// type: "DELETE",
// url: base_url + `/client-users/${id}`,
// success: function(response) {
// showAlert(response.message, 'success');
// fetchSessions(currentPage); // Stay on current page
// }
// });
// }
// });
// function showAlert(message, type) {
// $('#alertArea').html(`
// <div class="alert alert-${type} alert-dismissible fade show" role="alert">
// ${message}
// <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
// </div>
// `);
// }
// });
</script>
@endsection

View File

@@ -18,7 +18,11 @@
@yield('page-css') @yield('page-css')
<script type="text/javascript"> <script type="text/javascript">
@if(env('APP_ENV') == 'production')
var base_url = "https://smsportal.clickmlapps.com"; var base_url = "https://smsportal.clickmlapps.com";
@else
var base_url = "{{ url('/') }}";
@endif
//"{!! url('/') !!}"; //"{!! url('/') !!}";
</script> </script>
</head> </head>
@@ -34,12 +38,15 @@
<div class="fw-semibold">{{ session('current_user.name') }}</div> <div class="fw-semibold">{{ session('current_user.name') }}</div>
</div> </div>
</div> </div>
<div class="d-flex flex-wrap align-items-center gap-2"> <div class="d-flex flex-wrap align-items-center gap-2">
<span class="badge rounded-pill text-bg-success px-3 py-2">{{ session('current_user.role') }}</span>
<!-- <span class="badge rounded-pill text-bg-light px-3 py-2">{{ session('current_user.name') }}</span> --> <!-- <span class="badge rounded-pill text-bg-light px-3 py-2">{{ session('current_user.name') }}</span> -->
<!-- <span class="badge rounded-pill text-bg-light px-3 py-2"><i class="bi bi-clock-history me-1"></i> Last sync 09:12</span> --> <span class="badge rounded-pill text-bg-light px-3 py-2"><i class="bi bi-clock-history me-1"></i> </span>
@if($page_title == 'SMS Traffic') @if(session('current_user.role') == 'administrator')
<!-- <a href="{{ url('send-sms') }}" class="btn btn-click px-4"><i class="bi bi-plus-circle me-2"></i>New SMS</a> --> <a href="{{ url('client-users') }}" class="btn btn-click px-4"><i class="bi bi-people me-2"></i>Users</a>
@else @endif
@if(in_array(session('current_user.role'), ['administrator', 'finance']))
<a href="{{ url('client-traffic') }}" class="btn btn-click px-4"><i class="bi bi-chat-right-text me-2"></i>Messages</a> <a href="{{ url('client-traffic') }}" class="btn btn-click px-4"><i class="bi bi-chat-right-text me-2"></i>Messages</a>
@endif @endif

View File

@@ -49,7 +49,7 @@ Route::post('/client-activation', [App\Http\Controllers\ClientsLoginController::
#Route::resource('posts', AdminController::class); #Route::resource('posts', AdminController::class);
}); });
Route::middleware(['checksession'])->group(function () { Route::middleware(['checksession', 'checkrole:administrator'])->group(function () {
Route::get('/', [App\Http\Controllers\ClientsTrafficController::class, 'index']); Route::get('/', [App\Http\Controllers\ClientsTrafficController::class, 'index']);
Route::get('client-traffic', [App\Http\Controllers\ClientsTrafficController::class, 'index']); Route::get('client-traffic', [App\Http\Controllers\ClientsTrafficController::class, 'index']);
Route::get('client-traffic-tabulator', [App\Http\Controllers\ClientsTrafficController::class, 'indexTabulator']); Route::get('client-traffic-tabulator', [App\Http\Controllers\ClientsTrafficController::class, 'indexTabulator']);
@@ -62,6 +62,15 @@ Route::post('/client-activation', [App\Http\Controllers\ClientsLoginController::
Route::post('client-dailysmsunits', [App\Http\Controllers\ClientsTrafficController::class, 'dailySmsUnits'])->name('client.dailysmsunits'); Route::post('client-dailysmsunits', [App\Http\Controllers\ClientsTrafficController::class, 'dailySmsUnits'])->name('client.dailysmsunits');
Route::get('/client-users', [App\Http\Controllers\ClientUsersController::class, 'index']);
Route::get('/fetch-client-users', [App\Http\Controllers\ClientUsersController::class, 'fetch']);
Route::post('/client-users', [App\Http\Controllers\ClientUsersController::class, 'store']);
Route::get('/client-users/{id}/edit', [App\Http\Controllers\ClientUsersController::class, 'edit']);
Route::put('/client-users/{id}', [App\Http\Controllers\ClientUsersController::class, 'update']);
Route::delete('/client-users/{id}', [App\Http\Controllers\ClientUsersController::class, 'destroy']);
// Route::post('send-sms', [App\Http\Controllers\ClientsTrafficController::class, 'store'])->name('client.sendsms'); // Route::post('send-sms', [App\Http\Controllers\ClientsTrafficController::class, 'store'])->name('client.sendsms');
}); });