Compare commits
14 Commits
19a33bc2eb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f50a8549 | ||
|
|
757f908404 | ||
|
|
16f2dbbdb6 | ||
|
|
d42764a89d | ||
|
|
431dbc7da6 | ||
|
|
db00b8e2ca | ||
|
|
eb8ef2ee8e | ||
|
|
a459e35982 | ||
|
|
759c516714 | ||
|
|
814761ef83 | ||
|
|
fc6f5e8300 | ||
|
|
ffde370ed3 | ||
|
|
99871bc100 | ||
|
|
c2d8632045 |
12
accounts.md
12
accounts.md
@@ -8,3 +8,15 @@
|
||||
|
||||
### client
|
||||
- hixu@mailinator.com/frantic@300
|
||||
|
||||
## Sunking
|
||||
- https://smsportal.clickmlapps.com/
|
||||
- info@sunking.com
|
||||
- 847SIcgO9sUX
|
||||
|
||||
### add
|
||||
- change pagination to 1000 from 20
|
||||
- remove delivery receipt
|
||||
|
||||
|
||||
http://10.199.0.11/botswana/bpc/web
|
||||
103
app/Http/Controllers/ClientUsersController.php
Normal file
103
app/Http/Controllers/ClientUsersController.php
Normal 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]);
|
||||
}
|
||||
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,
|
||||
'client_id' => session('current_user.org_id'),
|
||||
'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,
|
||||
'client_id' => session('current_user.org_id'),
|
||||
'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!']);
|
||||
}
|
||||
}
|
||||
@@ -83,20 +83,22 @@ class ClientsLoginController extends Controller
|
||||
$logged_in = '';
|
||||
|
||||
$client = Models\ClientSession::where('email', $request->email)->first();
|
||||
|
||||
if ($client == false) {
|
||||
return redirect()->back()->withErrors(['Invalid credentials']);
|
||||
}
|
||||
$client_url = "clients/" . $client->client_id;
|
||||
$result = ApiCalls::CurlGet($client_url);
|
||||
|
||||
$result_arr = json_decode($result, true);
|
||||
$logged_in = $result_arr;
|
||||
// dd($logged_in);
|
||||
|
||||
$request->session()->regenerate(true);
|
||||
$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.name', $logged_in['name']);
|
||||
$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.status', $logged_in['status']);
|
||||
$request->session()->put('current_user.createdAt', $logged_in['createdAt']);
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Utilities\ApiCalls;
|
||||
use Session;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use GuzzleHttp\Client;
|
||||
use DateTime;
|
||||
|
||||
class ClientsTrafficController extends Controller
|
||||
{
|
||||
@@ -38,17 +39,36 @@ class ClientsTrafficController extends Controller
|
||||
);
|
||||
}
|
||||
public function index(){
|
||||
//dump(session('current_user'));
|
||||
|
||||
$client_id = session('current_user.org_id');
|
||||
$clients_url = "messages/client/$client_id?page=0&size=20&sort=createdAt,desc";
|
||||
$clients_url = "messages/client/$client_id?page=0&size=1000&sort=createdAt,desc";
|
||||
$result = ApiCalls::CurlGet($clients_url);
|
||||
$result_arr = json_decode($result);
|
||||
// dump($result_arr);
|
||||
|
||||
$current_date = new DateTime();
|
||||
|
||||
$start_date = $current_date->modify('-10 days');
|
||||
$start_date = $start_date->format('Y-m-d');
|
||||
$end_date = date('Y-m-d');
|
||||
$daily_smsunits_url = "ucm/reports/daily-sms-units?from=$start_date&to=$end_date";
|
||||
$result = ApiCalls::CurlGet($daily_smsunits_url);
|
||||
$sms_units_arr = json_decode($result, true);
|
||||
|
||||
$orgId = session('current_user.org_id');
|
||||
$sms_balance_url = "clients/$orgId/balance";
|
||||
$result = ApiCalls::CurlGet($sms_balance_url);
|
||||
|
||||
$balance_arr = json_decode($result, true);
|
||||
|
||||
// dump($sms_units_arr);
|
||||
|
||||
$data = [
|
||||
'page_title' => 'SMS Traffic',
|
||||
'traffic_arr' => $result_arr
|
||||
'traffic_arr' => $result_arr,
|
||||
'sms_units_arr' => $sms_units_arr,
|
||||
'balance_arr' => $balance_arr
|
||||
];
|
||||
return view('client-traffic.index-test', $data);
|
||||
return view('client-traffic.index-main', $data);
|
||||
}
|
||||
public function indexTabulator(Request $request){
|
||||
$client = new Client();
|
||||
@@ -155,6 +175,22 @@ class ClientsTrafficController extends Controller
|
||||
}
|
||||
Session::flash('success_message', 'Message successfully submitted for delivery!');
|
||||
return redirect("send-sms");
|
||||
}
|
||||
public function dailySmsUnits(Request $request){
|
||||
$this->validate($request, [
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date',
|
||||
]);
|
||||
$start_date = $request->start_date;
|
||||
$end_date = $request->end_date;
|
||||
$daily_smsunits_url = "ucm/reports/daily-sms-units?from=$start_date&to=$end_date";
|
||||
$result = ApiCalls::CurlGet($daily_smsunits_url);
|
||||
\Log::info('SMS Units Response ' . $result);
|
||||
|
||||
$data = json_decode($result, true);
|
||||
return response()->json($data);
|
||||
|
||||
|
||||
}
|
||||
public function newAppStore(Request $request){
|
||||
|
||||
|
||||
24
app/Http/Middleware/RoleMiddleware.php
Normal file
24
app/Http/Middleware/RoleMiddleware.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
class ClientSession extends Model
|
||||
{
|
||||
protected $guarded = [
|
||||
'id'
|
||||
];
|
||||
protected $guarded = ['id'];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use App\Http\Middleware\CheckClientSession;
|
||||
use App\Http\Middleware\RoleMiddleware;
|
||||
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
@@ -15,6 +16,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
$middleware->alias([
|
||||
'checksession' => CheckClientSession::class,
|
||||
'checkrole' => RoleMiddleware::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
|
||||
206
config/permission.php
Normal file
206
config/permission.php
Normal 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',
|
||||
],
|
||||
];
|
||||
@@ -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']);
|
||||
}
|
||||
};
|
||||
35
info.md
Normal file
35
info.md
Normal file
@@ -0,0 +1,35 @@
|
||||
Messaging Notes- meeting with Victor on messaging with focus on the dashboard
|
||||
|
||||
They would like a Finance feature that shows the account balances
|
||||
App to include option for report that shows balances , consumption and balances
|
||||
They do not require CDRs for the report but volumes of the messages sent
|
||||
Messaging report for call centre usage that enables them to extract OTP
|
||||
Message access for last 3-6 hours
|
||||
Sam added that we can provide functionality for the App to be useable by multiple departments
|
||||
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
|
||||
|
||||
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
|
||||
123
public/assets/js/traffic-mgt.js
Normal file
123
public/assets/js/traffic-mgt.js
Normal file
@@ -0,0 +1,123 @@
|
||||
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 = "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",
|
||||
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){
|
||||
var input = document.createElement("input");
|
||||
input.type = "date";
|
||||
|
||||
input.addEventListener("change", function(){
|
||||
console.log(input.value);
|
||||
success(input.value);
|
||||
});
|
||||
|
||||
return input;
|
||||
},
|
||||
headerFilterFunc:function(headerValue, rowValue){
|
||||
if(!headerValue){ return true; } // no filter
|
||||
if(!rowValue){ return false; }
|
||||
|
||||
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"});
|
||||
});
|
||||
@@ -1,217 +1,192 @@
|
||||
$(document).ready(function(){
|
||||
// $('.editUserBtn').click(function(evnt){
|
||||
|
||||
// });
|
||||
//
|
||||
console.log('foo bar');
|
||||
$('#editAllowedApps').select2({
|
||||
// width: "resolve",
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
|
||||
$(document).ready(function() {
|
||||
$.ajaxSetup({
|
||||
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
|
||||
});
|
||||
|
||||
$('#allowedApps').select2({
|
||||
// width: "resolve",
|
||||
dropdownParent: $('#addUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
const sessionModal = new bootstrap.Modal(document.getElementById('sessionModal'));
|
||||
|
||||
let currentPage = 1;
|
||||
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({
|
||||
// width: "resolve",
|
||||
dropdownParent: $('#editUserModal'),
|
||||
placeholder : "Select options, multiple allowed"
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#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){
|
||||
evnt.preventDefault();
|
||||
var selectedUserId = $(this).siblings('.userIdinput').val();
|
||||
$(document).on('click', '.page-link', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', selectedUserId);
|
||||
if ($(this).parent().hasClass('disabled')) {
|
||||
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({
|
||||
url: base_url + '/users/edit/' + selectedUserId,
|
||||
type: 'GET',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
$('#editSuccessArea').text("");
|
||||
$('#editErrorArea').text("Please wait ... loading user details!");
|
||||
type: type,
|
||||
url: url,
|
||||
data: data,
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
sessionModal.hide();
|
||||
showAlert(response.message, 'success');
|
||||
fetchSessions(currentPage);
|
||||
},
|
||||
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']);
|
||||
$('#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);
|
||||
error: function(xhr) {
|
||||
let errors = xhr.responseJSON.errors;
|
||||
if(errors.email) showAlert(errors.email, 'danger');
|
||||
if(errors.role) showAlert(errors.role, 'danger');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.viewUserBtn').click(function(evnt){
|
||||
evnt.preventDefault();
|
||||
var selectedUserId = $(this).siblings('.userIdinput').val();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('user_id', selectedUserId);
|
||||
$(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({
|
||||
url: base_url + '/users/' + selectedUserId,
|
||||
type: 'GET',
|
||||
processData: false,
|
||||
contentType: false,
|
||||
beforeSend: function() {
|
||||
$('#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(",");
|
||||
type: "DELETE",
|
||||
url: base_url + `/client-users/${id}`,
|
||||
success: function(response) {
|
||||
showAlert(response.message, 'success');
|
||||
fetchSessions(currentPage); // Stay on current page
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$("#newUserForm").submit(function(evt){
|
||||
evt.preventDefault();
|
||||
$('#successArea').addClass('d-none');
|
||||
$('#errorsArea').removeClass('d-none');
|
||||
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);
|
||||
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>
|
||||
`);
|
||||
}
|
||||
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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
<!doctype html>
|
||||
<!-- -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -80,7 +81,13 @@
|
||||
@csrf
|
||||
<div class="mb-3">
|
||||
<label for="companyEmail" class="form-label fw-semibold">Email</label>
|
||||
<input type="email" name="email" class="form-control" id="companyEmail" placeholder="client@click-mobile.com" value="" autocomplete="">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0 rounded-start-4">
|
||||
<i class="bi bi-envelope-at"></i>
|
||||
</span>
|
||||
|
||||
<input type="email" name="email" class="form-control" id="companyEmail" placeholder="" value="" autocomplete="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
|
||||
158
resources/views/client-traffic/index-main.blade.php
Normal file
158
resources/views/client-traffic/index-main.blade.php
Normal file
@@ -0,0 +1,158 @@
|
||||
@extends('layouts.master')
|
||||
@section('page-title')
|
||||
{{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<!-- <link href="https://unpkg.com/tabulator-tables@6.4.0/dist/css/tabulator.min.css" rel="stylesheet"> -->
|
||||
<link href="{{ url('public/libs/tabulator-master/dist/css/tabulator_bootstrap5.css') }}" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/css/datepicker.min.css">
|
||||
|
||||
@endsection
|
||||
@section('content')
|
||||
<div id="loadingOverlay"
|
||||
style="display:none; position:fixed; top:0; left:0; width:100%; height:100%;
|
||||
background:rgba(255,255,255,0.8); z-index:9999;
|
||||
align-items:center; justify-content:center;">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<section class="traffic-hero mb-4">
|
||||
<div class="row g-4 align-items-end">
|
||||
<div class="col-lg-8">
|
||||
<div class="text-uppercase small fw-semibold opacity-75 mb-2">SMS Traffic Monitoring</div>
|
||||
<!-- <h1 class="display-6 fw-semibold mb-3">Track every message your team sends.</h1>
|
||||
<p class="fs-5 opacity-75 mb-0">
|
||||
Search sent SMS, review delivery outcomes, inspect failed attempts, and give clients a fast audit trail for every campaign.
|
||||
</p> -->
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="row g-3">
|
||||
<!-- <div class="col-6">
|
||||
<div class="rounded-4 p-3 bg-white bg-opacity-10">
|
||||
<div class="small opacity-75">Campaigns</div>
|
||||
<div class="h3 mb-0">18</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<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="small opacity-75">SMS Account Balance</div>
|
||||
<div class="h3 mb-0" id="mainSmsBalance">{{ number_format($balance_arr['balance']) }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row g-4 mb-4">
|
||||
<div class="col-md-6 col-xl-6">
|
||||
<article class="traffic-card">
|
||||
<div class="d-flex align-items-start justify-content-between mb-3">
|
||||
<!-- <div class="icon-wrap"><i class="bi bi-send-check"></i></div> -->
|
||||
<!-- <span class="small text-success fw-semibold">+12.6%</span> -->
|
||||
</div>
|
||||
<div class="muted-label mb-2">
|
||||
<!-- Messages sent from -->
|
||||
<span id="reportRange">{{ $sms_units_arr['reportDate'] }}</span> </div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input id="startDate" name="start_date" type="text" class="form-control" placeholder="Select Start Date">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input id="endDate" name="end_date" type="text" class="form-control" placeholder="Select End Date">
|
||||
</div>
|
||||
<!-- <div id="loadingSpinner" class="spinner-border text-primary" role="status" style="display:none;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- <div class="h3 mb-2 pt-1" id="smsUnitsValue">SMS Units : {{ $sms_units_arr['smsUnits'] }}</div> -->
|
||||
<!-- <div class="mini-chart"><span style="width: 52%;"></span></div> -->
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-6">
|
||||
<article class="traffic-card">
|
||||
<div class="d-flex align-items-start justify-content-between mb-3">
|
||||
<div class="icon-wrap"><i class="bi bi-check2-circle"></i></div>
|
||||
<!-- <span class="small text-success fw-semibold">98.4%</span> -->
|
||||
</div>
|
||||
<div class="muted-label mb-2">Sent Messages</div>
|
||||
<!-- <div class="h3 mb-2">183,372</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: 57%;"></span></div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section class="row g-4">
|
||||
<div class="col-xl-12">
|
||||
<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">Sent SMS list</h2>
|
||||
<!-- <p class="text-secondary mb-0">Filter messages by date, sender, recipient, or delivery state.</p> -->
|
||||
</div>
|
||||
<!-- <div class="d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-ghost px-4"><i class="bi bi-download me-2"></i>Export CSV</button>
|
||||
<button class="btn btn-click px-4"><i class="bi bi-funnel me-2"></i>Apply filters</button>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<div class="float-end">
|
||||
|
||||
<!-- <label for="search" class="form-label fw-semibold">Search</label> -->
|
||||
<!-- <input id="search" type="text" class="form-control" placeholder="Phone, sender ID, text"> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="traffic-table-card">
|
||||
<h2 class="h4 mb-1">Sent SMS list</h2>
|
||||
<div class="p-1">
|
||||
<button id="download-pdf" class="btn btn-danger"><i class="bi bi-file-pdf-fill me-2"></i> Export PDF</button>
|
||||
<button id="download-xlsx" class="btn btn-success"><i class="bi bi-file-excel-fill me-2"></i> Export Excel</button>
|
||||
</div>
|
||||
<div id="message-table"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="col-xl-2"> -->
|
||||
|
||||
|
||||
<!-- <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">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>
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<!-- <script src="https://unpkg.com/tabulator-tables@6.4.0/dist/js/tabulator.min.js"></script> -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/js/datepicker.min.js"></script>
|
||||
|
||||
<script src="{{ url('public/libs/tabulator-master/dist/js/tabulator.min.js') }}"></script>
|
||||
|
||||
<script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.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 src="{{ url('public/libs/tabulator-master/dist/js/autotable.min.js') }}"></script>
|
||||
<script src="{{ url('public/assets/js/traffic-mgt.js') }}"></script>
|
||||
|
||||
@endsection
|
||||
@@ -1,238 +0,0 @@
|
||||
@extends('layouts.master')
|
||||
@section('page-title')
|
||||
{{ $page_title }}
|
||||
@endsection
|
||||
@section('page-css')
|
||||
<!-- <link href="https://unpkg.com/tabulator-tables@6.4.0/dist/css/tabulator.min.css" rel="stylesheet"> -->
|
||||
<link href="{{ url('public/libs/tabulator-master/dist/css/tabulator_bootstrap5.css') }}" rel="stylesheet">
|
||||
|
||||
@endsection
|
||||
@section('content')
|
||||
<section class="traffic-hero mb-4">
|
||||
<div class="row g-4 align-items-end">
|
||||
<div class="col-lg-8">
|
||||
<div class="text-uppercase small fw-semibold opacity-75 mb-2">SMS Traffic Monitoring</div>
|
||||
<!-- <h1 class="display-6 fw-semibold mb-3">Track every message your team sends.</h1>
|
||||
<p class="fs-5 opacity-75 mb-0">
|
||||
Search sent SMS, review delivery outcomes, inspect failed attempts, and give clients a fast audit trail for every campaign.
|
||||
</p> -->
|
||||
</div>
|
||||
<div class="col-lg-4"></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row g-4">
|
||||
<div class="col-xl-9">
|
||||
<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">Sent SMS list</h2>
|
||||
<!-- <p class="text-secondary mb-0">Filter messages by date, sender, recipient, or delivery state.</p> -->
|
||||
</div>
|
||||
<!-- <div class="d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-ghost px-4"><i class="bi bi-download me-2"></i>Export CSV</button>
|
||||
<button class="btn btn-click px-4"><i class="bi bi-funnel me-2"></i>Apply filters</button>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<div class="float-end">
|
||||
|
||||
|
||||
<label for="search" class="form-label fw-semibold">Search</label>
|
||||
<input id="search" type="text" class="form-control" placeholder="Phone, sender ID, text">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="traffic-table-card">
|
||||
<div class="p-1">
|
||||
<button id="download-pdf" class="btn btn-danger"><i class="bi bi-file-pdf-fill me-2"></i> Export PDF</button>
|
||||
<button id="download-xlsx" class="btn btn-success"><i class="bi bi-file-excel-fill me-2"></i> Export Excel</button>
|
||||
</div>
|
||||
<div id="message-table"></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">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>
|
||||
@endsection
|
||||
@section('page-js')
|
||||
<!-- <script src="https://unpkg.com/tabulator-tables@6.4.0/dist/js/tabulator.min.js"></script> -->
|
||||
<script src="{{ url('public/libs/tabulator-master/dist/js/tabulator.min.js') }}"></script>
|
||||
|
||||
<script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.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 src="{{ url('public/libs/tabulator-master/dist/js/autotable.min.js') }}"></script>
|
||||
<script>
|
||||
|
||||
// var table = new Tabulator("#message-table", {
|
||||
// ajaxURL: base_url + "/client-traffic-tabulator/",
|
||||
// ajaxResponse: function(url, params, response) {
|
||||
// return response.content;
|
||||
// },
|
||||
// columns: [
|
||||
// {title: "Sender ID", field: "from"},
|
||||
// {title: "Msisdn", field: "to"},
|
||||
// {title: "Message", field: "message"},
|
||||
// {title: "Status", field: "status"},
|
||||
// {title: "Delivery Status", field: "deliveryStatus"},
|
||||
// // {title: "Created At", field: "createdAt"},
|
||||
// {title:"Date Created ", field:"createdAt", formatter:"datetime", formatterParams:{
|
||||
// inputFormat: "iso",
|
||||
// outputFormat: "dd-MM-yyyy HH:mm:ss",
|
||||
// invalidPlaceholder: "(invalid date)"
|
||||
// }}
|
||||
// ],
|
||||
// });
|
||||
// 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>
|
||||
|
||||
<script>
|
||||
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/",
|
||||
ajaxParams: {size: 20},
|
||||
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"},
|
||||
{title: "Msisdn", field: "to"},
|
||||
// {title: "Message", field: "message"},
|
||||
{title:"Message", field:"message" ,formatter:"textarea"},
|
||||
// {title: "Status", field: "status"},
|
||||
{title: "Delivery Status", field: "deliveryStatus", formatter: statusDesign},
|
||||
// {title: "Created At", field: "createdAt"},
|
||||
{title:"Date Created ", field:"createdAt", formatter:"datetime", formatterParams:{
|
||||
inputFormat: "iso",
|
||||
outputFormat: "dd-MM-yyyy HH:mm:ss",
|
||||
invalidPlaceholder: "(invalid date)"
|
||||
}}
|
||||
],
|
||||
});
|
||||
|
||||
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>
|
||||
|
||||
<script>
|
||||
// var table = new Tabulator("#message-table", {
|
||||
// ajaxURL: base_url + "/client-traffic-tabulator/",
|
||||
// pagination: "remote",
|
||||
// paginationSize: 20,
|
||||
// paginationDataSent: {
|
||||
// "page": "page",
|
||||
// "size": "size"
|
||||
// },
|
||||
// paginationDataReceived: {
|
||||
// "last_page": "totalPages",
|
||||
// "data": "content",
|
||||
// "current_page": "number",
|
||||
// "total": "totalElements"
|
||||
// },
|
||||
// // Sorting configuration
|
||||
// ajaxSorting: true,
|
||||
// sortMode: "remote",
|
||||
// ajaxRequestFunc: function(url, config, params) {
|
||||
// let query = {
|
||||
// page: params.page - 1,
|
||||
// size: params.size,
|
||||
// };
|
||||
|
||||
// if (params.sorters && params.sorters.length > 0) {
|
||||
// let sorter = params.sorters[0];
|
||||
// query.sort = sorter.field + "," + sorter.dir;
|
||||
// // Laravel/Spring style: ?sort=createdAt,asc
|
||||
// }
|
||||
|
||||
// // Build query string
|
||||
// let qs = Object.keys(query)
|
||||
// .map(k => encodeURIComponent(k) + "=" + encodeURIComponent(query[k]))
|
||||
// .join("&");
|
||||
|
||||
// return url + "?" + qs;
|
||||
// },
|
||||
// ajaxResponse: function(url, params, response) {
|
||||
// return response.content;
|
||||
// },
|
||||
// columns: [
|
||||
// {title: "ID", field: "id", sorter: "number"},
|
||||
// {title: "Client ID", field: "clientId", sorter: "number"},
|
||||
// {title: "From", field: "from", sorter: "string"},
|
||||
// {title: "To", field: "to", sorter: "string"},
|
||||
// {title: "Message", field: "message", sorter: "string"},
|
||||
// {title: "Status", field: "status", sorter: "string"},
|
||||
// {title: "Delivery Status", field: "deliveryStatus", sorter: "string"},
|
||||
// {title: "Created At", field: "createdAt", sorter: "datetime"},
|
||||
// ],
|
||||
// });
|
||||
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
327
resources/views/client-users/index.blade.php
Normal file
327
resources/views/client-users/index.blade.php
Normal 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
|
||||
@@ -5,6 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- <title>Click Client Portal | SMS Traffic</title> -->
|
||||
<title>@yield('page-title') | Client Portal </title>
|
||||
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<link rel="icon" type="image/png" href="{{ url('public/assets/images/click-logo.png') }}">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
@@ -16,7 +18,12 @@
|
||||
|
||||
@yield('page-css')
|
||||
<script type="text/javascript">
|
||||
var base_url = "{!! url('/') !!}";
|
||||
@if(env('APP_ENV') == 'production')
|
||||
var base_url = "https://smsportal.clickmlapps.com";
|
||||
@else
|
||||
var base_url = "{{ url('/') }}";
|
||||
@endif
|
||||
//"{!! url('/') !!}";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -31,19 +38,22 @@
|
||||
<div class="fw-semibold">{{ session('current_user.name') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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"><i class="bi bi-clock-history me-1"></i> Last sync 09:12</span> -->
|
||||
@if($page_title == 'SMS Traffic')
|
||||
<a href="{{ url('send-sms') }}" class="btn btn-click px-4"><i class="bi bi-plus-circle me-2"></i>New SMS</a>
|
||||
@else
|
||||
<span class="badge rounded-pill text-bg-light px-3 py-2"><i class="bi bi-clock-history me-1"></i> </span>
|
||||
@if(session('current_user.role') == 'administrator')
|
||||
<a href="{{ url('client-users') }}" class="btn btn-click px-4"><i class="bi bi-people me-2"></i>Users</a>
|
||||
@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>
|
||||
@endif
|
||||
|
||||
@if($page_title == 'Applications')
|
||||
<a href="{{ url('new-app') }}" class="btn btn-clicksuccess px-4" data-bs-toggle="modal" data-bs-target="#newApplicationModal"><i class="bi bi-plus-square me-2"></i>New App</a>
|
||||
<!-- <a href="{{ url('new-app') }}" class="btn btn-clicksuccess px-4" data-bs-toggle="modal" data-bs-target="#newApplicationModal"><i class="bi bi-plus-square me-2"></i>New App</a> -->
|
||||
@else
|
||||
<a href="{{ url('client-apps') }}" class="btn btn-clicksuccess px-4"><i class="bi bi-list-ul me-2"></i>App List</a>
|
||||
<!-- <a href="{{ url('client-apps') }}" class="btn btn-clicksuccess px-4"><i class="bi bi-list-ul me-2"></i>App List</a> -->
|
||||
@endif
|
||||
<a href="{{ url('client-logout') }} " class="btn btn-ghost px-4">Log out</a>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ Route::post('/client-activation', [App\Http\Controllers\ClientsLoginController::
|
||||
|
||||
#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('client-traffic', [App\Http\Controllers\ClientsTrafficController::class, 'index']);
|
||||
Route::get('client-traffic-tabulator', [App\Http\Controllers\ClientsTrafficController::class, 'indexTabulator']);
|
||||
@@ -60,6 +60,16 @@ Route::post('/client-activation', [App\Http\Controllers\ClientsLoginController::
|
||||
Route::post('send-sms', [App\Http\Controllers\ClientsTrafficController::class, 'store'])->name('client.sendsms');
|
||||
Route::post('client-newapp', [App\Http\Controllers\ClientsTrafficController::class, 'newAppStore'])->name('client.sendsms');
|
||||
|
||||
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');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user