added messages list, new client form, logic for client Apps plus others

This commit is contained in:
Kwesi Banson Jnr
2026-03-22 22:29:28 +00:00
parent c68c007945
commit 4ab0fda326
858 changed files with 242393 additions and 337 deletions

View File

@@ -0,0 +1,87 @@
@extends('layouts.master')
@section('page-title')
{{ $page_title }}
@endsection
@section('page-css')
@section('content')
<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">App List</h2>
<!-- <p class="text-secondary mb-0">Filter messages by date, sender, recipient, or delivery state.</p> -->
</div>
</div>
</div>
<div class="traffic-table-card">
<div class="table-responsive">
<table class="table align-middle mb-0">
<thead>
<tr>
<th scope="col">App Name</th>
<th scope="col">Type</th>
<th scope="col">Api Key</th>
<th scope="col">Date Created</th>
</tr>
</thead>
<tbody>
@if(count($apps_arr->content) > 0)
@foreach($apps_arr->content as $row)
<tr>
<td class="fw-semibold">{{ $row->name}}</td>
<td class="recipient-cell">
<div class="fw-semibold">{{ $row->type}}</div>
</td>
<td><i class="bi bi-eye" id="viewApiKeyIcon"></i></td>
<td>
{{ \Carbon\Carbon::parse($row->createdAt)->format('Y-m-d H:i:s') }}
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="6" class="text-center">No records</td>
</tr>
@endif
</tbody>
</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">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>
@endsection
@section('page-js')
<script>
</script>
@endsection

View File

@@ -0,0 +1,225 @@
@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">
@endsetion
@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>
// 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)"
}}
],
});
</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

View File

@@ -7,7 +7,7 @@
<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">Outbound SMS Monitoring</div>
<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.
@@ -85,16 +85,16 @@
<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>
<!-- <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">
<!-- <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>
<div class="row g-3">
<div class="col-md-6 col-lg-3">
<!-- <div class="col-md-6 col-lg-3">
<label for="range" class="form-label fw-semibold">Date range</label>
<select id="range" class="form-select">
<option>Today</option>
@@ -118,10 +118,14 @@
<option>Pending</option>
<option>Failed</option>
</select>
</div>
<div class="col-md-6 col-lg-3">
<label for="search" class="form-label fw-semibold">Search</label>
<input id="search" type="text" class="form-control" placeholder="Phone, message ID, text">
</div> -->
<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>
@@ -140,7 +144,7 @@
</tr>
</thead>
<tbody>
@if($traffic_arr)
@if(count($traffic_arr->content) > 0)
@foreach($traffic_arr->content as $row)
<tr>
<td class="fw-semibold">{{ $row->refId}}</td>
@@ -156,6 +160,10 @@
<td><span class="status-badge status-delivered">{{ $row->deliveryStatus }}</span></td>
</tr>
@endforeach
@else
<tr>
<td colspan="6" class="text-center">No records</td>
</tr>
@endif
</tbody>
</table>
@@ -174,13 +182,6 @@
<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>

View File

@@ -0,0 +1,38 @@
<div class="modal fade" id="newApplicationModal" tabindex="-1" aria-labelledby="newApplicationModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="newApplicationModalLabel">New Application</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="clientNewAppForm" action="{{ url('client-newapp') }}" method="POST">
@csrf
<input type="hidden" name="orgId" value="{{ session('current_user.org_id') }}">
<div class="mb-3">
<label for="applicationName" class="col-form-label">Application Name</label>
<input type="text" class="form-control" placeholder="e.g. My SMS App" name="application_name" id="applicationName" required>
</div>
<div class="mb-3">
<label for="applicationName" class="col-form-label">Application Type</label>
<select class="form-control" id="applicationType" name="application_type" required>
<option value="" selected disabled>-- Select --</option>
<option value="INTERNATIONAL">International</option>
<option value="LOCAL">Local</option>
</select>
</div>
<div class="mb-3">
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<!-- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> -->
</div>
</div>
</div>
</div>

View File

@@ -4,7 +4,6 @@
@endsection
@section('page-css')
@section('content')
<section class="traffic-hero mb-4">
<div class="row g-4 align-items-end">
<div class="col-lg-8">
@@ -42,13 +41,15 @@
<h2 class="h3 mb-1">Compose message</h2>
<p class="text-secondary mb-0">Set campaign details, recipients, and delivery rules before sending.</p>
</div>
@include('commons.notifications')
<!-- <div class="d-flex flex-wrap gap-2">
<span class="option-chip active"><i class="bi bi-lightning-charge"></i>Send now</span>
<span class="option-chip"><i class="bi bi-calendar-event"></i>Schedule</span>
</div> -->
</div>
<form>
<form action="send-sms" method="POST" enctype="multipart/form-data">
@csrf
<!-- <div class="row g-3 mb-4">
<div class="col-md-6">
<label for="campaignName" class="form-label fw-semibold">Campaign name</label>
@@ -71,14 +72,20 @@
</select>
</div>
</div> -->
<div class="mb-4">
<div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-2 mb-2">
<label class="form-label fw-semibold mb-0">Sender ID</label>
<span class="muted-label">Not more than 11 characters</span>
</div>
<input type="text" name="sender_id" class="form-control">
</div>
<div class="mb-4">
<div class="d-flex align-items-center justify-content-between mb-2">
<label for="messageText" class="form-label fw-semibold mb-0">Message content</label>
<span class="muted-label">54 / 160 characters</span>
<span class="muted-label" id="messageCounter">54 / 160 characters</span>
</div>
<textarea id="messageText" class="form-control py-3" rows="5">Dear customer, grow your savings with our new fixed plan. Visit any branch today.</textarea>
<div class="char-progress mt-3"><span></span></div>
<textarea id="messageText" name="message" class="form-control py-3" rows="5" placeholder="message body goes here"></textarea>
<!-- <div class="char-progress mt-3"><span></span></div> -->
</div>
<div class="mb-4">
@@ -87,15 +94,24 @@
<span class="muted-label">Paste numbers(comma separated) or upload CSV</span>
</div>
<div class="recipient-box">
<span class="recipient-tag"><i class="bi bi-telephone"></i>+233245559001</span>
<!-- <input type="textarea" rows="8" name="pasted"> -->
<textarea name="pasted" class="form-control" rows="6"></textarea>
<!-- <span class="recipient-tag"><i class="bi bi-telephone"></i>+233245559001</span>
<span class="recipient-tag"><i class="bi bi-telephone"></i>+233502287744</span>
<span class="recipient-tag"><i class="bi bi-telephone"></i>+265884412280</span>
<span class="recipient-tag"><i class="bi bi-people"></i>Retail Savers Segment</span>
<span class="recipient-tag"><i class="bi bi-people"></i>Retail Savers Segment</span> -->
</div>
<div class="mb-3 recipient-box mt-2">
<label for="formFile" class="form-label">Upload your file</label>
<input type="file" id="formFile" name="recipients_file" class="d-none">
<div id="dropZone" class="rounded border border-primary p-4 text-center">
Click to select a file or drag and drop here
</div>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<!-- <div class="row g-3 mb-4">
<div class="col-md-12">
<label for="deliveryWindow" class="form-label fw-semibold">Delivery window</label>
<select id="deliveryWindow" class="form-select">
<option>Immediately</option>
@@ -104,19 +120,12 @@
<option>15:00 - 18:00</option>
</select>
</div>
<div class="col-md-6">
<label for="failover" class="form-label fw-semibold">Retry policy</label>
<select id="failover" class="form-select">
<option>Retry once after 5 minutes</option>
<option>Retry twice after 10 minutes</option>
<option>No retries</option>
</select>
</div>
</div>
</div> -->
<div class="d-flex flex-column flex-lg-row gap-3">
<button type="button" class="btn btn-click flex-grow-1"><i class="bi bi-send me-2"></i>Send SMS campaign</button>
<button type="button" class="btn btn-ghost flex-grow-1"><i class="bi bi-floppy me-2"></i>Save draft</button>
<button type="submit" class="btn btn-click flex-grow-1"><i class="bi bi-send me-2"></i>Send SMS campaign</button>
<!-- <button type="button" class="btn btn-ghost flex-grow-1"><i class="bi bi-floppy me-2"></i>Save draft</button> -->
</div>
</form>
</div>
@@ -126,9 +135,30 @@
</section>
</div>
@endsection
@endsection
@section('page-js')
<script>
const dropZone = document.getElementById('dropZone');
const formFile = document.getElementById('formFile');
dropZone.addEventListener('click', () => {
formFile.click(); // Trigger the hidden input click
});
formFile.addEventListener('change', () => {
if (formFile.files.length > 0) {
dropZone.textContent = formFile.files[0].name; // Update the drop zone text
} else {
dropZone.textContent = 'Click to select a file or drag and drop here';
}
});
$(document).ready(function(){
$('#messageText').keyup(function(){
characterCount = $(this).val().length;
messagePages = Math.ceil(characterCount/160);
$('#messageCounter').html(characterCount + " character " + messagePages + " messages");
});
});
</script>