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

@@ -35,10 +35,12 @@
</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>
@@ -79,9 +81,9 @@
</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'] }}</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>
</div>
@@ -139,7 +141,7 @@
</div> -->
<!-- </aside> -->
<!-- </div> -->
</section>
</section>
@endsection
@section('page-js')
<!-- <script src="https://unpkg.com/tabulator-tables@6.4.0/dist/js/tabulator.min.js"></script> -->
@@ -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.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=" https://cdn.jsdelivr.net/npm/luxon@3.7.2/build/global/luxon.min.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>
<script src="{{ url('public/assets/js/traffic-mgt.js') }}"></script>
@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')
<script type="text/javascript">
var base_url = "https://smsportal.clickmlapps.com";
@if(env('APP_ENV') == 'production')
var base_url = "https://smsportal.clickmlapps.com";
@else
var base_url = "{{ url('/') }}";
@endif
//"{!! url('/') !!}";
</script>
</head>
@@ -34,12 +38,15 @@
<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