From ab41e104455e28aa1c9ec3448a008f6ba8e62ee7 Mon Sep 17 00:00:00 2001 From: Kwesi Banson Jnr Date: Wed, 19 Aug 2026 11:55:44 +0000 Subject: [PATCH] worked on short code and sender ID modules --- app/Http/Controllers/SenderidsController.php | 133 +++- app/Http/Controllers/ShortCodesController.php | 110 +++- app/Models/Country.php | 2 +- app/Models/ShortCode.php | 6 +- public/assets/js/sender-ids.js | 197 ++++++ resources/views/senderids/index.blade.php | 402 ++++++------ .../views/senderids/partials/create.blade.php | 74 +++ .../views/shortcodes/index.blade copy.php | 359 +++++++++++ resources/views/shortcodes/index.blade.php | 570 ++++++++++-------- routes/web.php | 10 +- 10 files changed, 1397 insertions(+), 466 deletions(-) create mode 100644 public/assets/js/sender-ids.js create mode 100644 resources/views/senderids/partials/create.blade.php create mode 100644 resources/views/shortcodes/index.blade copy.php diff --git a/app/Http/Controllers/SenderidsController.php b/app/Http/Controllers/SenderidsController.php index 1b1e738..9d82557 100644 --- a/app/Http/Controllers/SenderidsController.php +++ b/app/Http/Controllers/SenderidsController.php @@ -3,14 +3,141 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; - +use App\Models; class SenderidsController extends Controller { - public function index() + + + + public function index(Request $request) { + // Fetch KPI Metrics (Keep your existing counts) + // $totalCount = \App\Models\SenderId::count(); + // $approvedCount = \App\Models\SenderId::where('status', 'APPROVED')->count(); + // $pendingCount = \App\Models\SenderId::where('status', 'PENDING')->count(); + // $rejectedCount = \App\Models\SenderId::where('status', 'REJECTED')->count(); + + $totalCount = \App\Models\SenderId::count(); + $approvedCount = \App\Models\SenderId::where('status', 'LIKE', 'Approved%')->count(); + $pendingCount = \App\Models\SenderId::where('status', 'LIKE', 'Applied%')->count(); + $rejectedCount = \App\Models\SenderId::where('status', 'Rejected')->count(); + // Fetch dynamic lists for the modal dropdowns + $clients = Models\Client::orderBy('name', 'asc')->get(); + $networkOperators = Models\NetworkOperator::orderBy('name', 'asc')->get(); + + // Start the query + $query = \App\Models\SenderId::query(); + + // 1. Search Filter (Sender ID, MNO, or Supplier) + if ($request->filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('senderid', 'like', "%{$search}%") + ->orWhere('mno_name', 'like', "%{$search}%") + ->orWhere('supplier_name', 'like', "%{$search}%"); + }); + } + + // 2. Direct MNO Filter + if ($request->filled('direct_mno')) { + $query->where('direct_mno', $request->direct_mno); + } + + // 3. Status Filter + if ($request->filled('status')) { + $query->where('status', 'LIKE', '%' . $request->status . '%'); + } + + // Execute query with pagination + $senderIds = $query->orderBy('created_at', 'desc')->paginate(10); + + // Crucial: Append the current request query strings to pagination links + $senderIds->appends($request->query()); + $data = [ - 'page_title' => 'Dashboard' + 'page_title' => 'Sender IDs', + 'totalCount' => $totalCount, + 'approvedCount' => $approvedCount, + 'pendingCount' => $pendingCount, + 'rejectedCount' => $rejectedCount, + 'senderIds' => $senderIds, + 'clients' => $clients, // <-- Pass Clients + 'networkOperators' => $networkOperators, // <-- Pass MNOs ]; + return view('senderids.index', $data); } + + public function store(Request $request) + { + $validated = $request->validate([ + 'senderid' => 'required|string|max:20', + 'direct_mno' => 'required|in:YES,NO', + + // Conditional Validation Rules + 'mno_name' => 'required_if:direct_mno,YES|nullable|string|max:192', + 'supplier_name' => 'required_if:direct_mno,NO|nullable|string|max:192', + + 'bind_name' => 'nullable|string|max:50', + 'type' => 'nullable|string|max:25', + 'status' => 'nullable|string|max:154', + 'remarks' => 'nullable|string|max:250', + ], [ + 'mno_name.required_if' => 'The MNO Name is required when routing directly to an MNO.', + 'supplier_name.required_if' => 'The Supplier Name is required when not routing directly to an MNO.', + ]); + + // Attach creator ID and save + $validated['created_by'] = auth()->id(); + + // Create the Sender ID + $senderId = \App\Models\SenderId::create($validated); + + return response()->json(['success' => true, 'message' => 'Sender ID added successfully!']); + } + public function update(Request $request, $id) + { + $senderId = Models\SenderId::findOrFail($id); + + $validated = $request->validate([ + 'senderid' => 'required|string|max:20', + 'direct_mno' => 'required|in:YES,NO', + + // Conditional validation rules for update as well + 'mno_name' => 'required_if:direct_mno,YES|nullable|string|max:192', + 'supplier_name' => 'required_if:direct_mno,NO|nullable|string|max:192', + + 'bind_name' => 'nullable|string|max:50', + 'type' => 'nullable|string|max:25', + 'status' => 'nullable|string|max:154', + 'remarks' => 'nullable|string|max:250', + ], [ + 'mno_name.required_if' => 'The MNO Name is required when routing directly to an MNO.', + 'supplier_name.required_if' => 'The Supplier Name is required when not routing directly to an MNO.', + ]); + + // Clear out the opposite field depending on the Direct MNO choice to keep data clean + if ($validated['direct_mno'] === 'YES') { + $validated['supplier_name'] = null; + } else { + $validated['mno_name'] = null; + } + + $validated['last_modified_by'] = auth()->id(); + + $senderId->update($validated); + + return response()->json([ + 'success' => true, + 'message' => 'Sender ID updated successfully!' + ]); + } + + public function destroy($id) + { + $senderId = SenderId::findOrFail($id); + $senderId->delete(); + + return redirect()->route('sender-ids.index')->with('success', 'Sender ID deleted successfully.'); + } } diff --git a/app/Http/Controllers/ShortCodesController.php b/app/Http/Controllers/ShortCodesController.php index 84b8033..922f139 100644 --- a/app/Http/Controllers/ShortCodesController.php +++ b/app/Http/Controllers/ShortCodesController.php @@ -2,15 +2,119 @@ namespace App\Http\Controllers; +use App\Models\ShortCode; +use App\Models\Client; +use App\Models\NetworkOperator; +use App\Models\Country; use Illuminate\Http\Request; class ShortCodesController extends Controller { - public function index() + public function index(Request $request) { + // KPI Metrics + $totalCount = ShortCode::count(); + $smsCount = ShortCode::where('code_type', 'sms')->count(); + $ussdCount = ShortCode::where('code_type', 'ussd')->count(); + $voiceCount = ShortCode::where('code_type', 'voice')->count(); + $countries = Country::orderBy('en_short_name', 'asc')->get(); + // dd($countries); + + // Dropdown options + $clients = Client::orderBy('name', 'asc')->get(); + $networkOperators = NetworkOperator::orderBy('name', 'asc')->get(); + + $query = ShortCode::query(); + + // Search Filter + if ($request->filled('search')) { + $search = $request->search; + $query->where(function($q) use ($search) { + $q->where('shortcode', 'like', "%{$search}%") + ->orWhere('name', 'like', "%{$search}%") + ->orWhere('toll_free', 'like', "%{$search}%") + ->orWhere('country', 'like', "%{$search}%") + ->orWhere('network', 'like', "%{$search}%") + ->orWhere('code_type', 'like', "%{$search}%"); + }); + } + + // Status Filter + if ($request->filled('status')) { + $query->where('status', $request->status); + } + + // Execute query with pagination (Fixed variable name from $senderIds to $shortcodes) + $shortcodes = $query->orderBy('created_at', 'desc')->paginate(10); + $shortcodes->appends($request->query()); + $data = [ - 'page_title' => 'Dashboard' + 'page_title' => 'Short Codes Management', + 'totalCount' => $totalCount, + 'smsCount' => $smsCount, + 'ussdCount' => $ussdCount, + 'voiceCount' => $voiceCount, + 'shortcodes' => $shortcodes, + 'clients' => $clients, + 'countries' => $countries, + 'networkOperators' => $networkOperators, ]; + return view('shortcodes.index', $data); } -} + + public function store(Request $request) + { + $validated = $request->validate([ + 'client_id' => 'nullable|integer', + 'name' => 'nullable|string|max:192', + 'shortcode' => 'required|string|max:20', + 'code_type' => 'nullable|string|max:15', + 'toll_free' => 'nullable|string|max:10', + 'monthly_fee' => 'nullable|numeric', + 'country' => 'nullable|string|max:45', + 'network' => 'nullable|string|max:45', + 'status' => 'nullable|string|max:15', + 'launch_date' => 'nullable|date', + 'expiry_date' => 'nullable|date', + 'remarks' => 'nullable|string|max:250', + ]); + + ShortCode::create($validated); + + return response()->json(['success' => true, 'message' => 'Short code created successfully!']); + } + + public function update(Request $request, $id) + { + $shortcode = ShortCode::findOrFail($id); + + $validated = $request->validate([ + 'client_id' => 'nullable|integer', + 'name' => 'nullable|string|max:192', + 'shortcode' => 'required|string|max:20', + 'code_type' => 'nullable|string|max:15', + 'toll_free' => 'nullable|string|max:10', + 'monthly_fee' => 'nullable|numeric', + 'country' => 'nullable|string|max:45', + 'network' => 'nullable|string|max:45', + 'status' => 'nullable|string|max:15', + 'launch_date' => 'nullable|date', + 'expiry_date' => 'nullable|date', + 'remarks' => 'nullable|string|max:250', + ]); + + $validated['last_updated_by'] = auth()->id(); + $shortcode->update($validated); + + return response()->json(['success' => true, 'message' => 'Short code updated successfully!']); + } + + public function destroy($id) + { + $shortcode = ShortCode::findOrFail($id); + $shortcode->delete(); + + return redirect()->route('shortcodes.index')->with('success', 'Short code deleted successfully.'); + } +} \ No newline at end of file diff --git a/app/Models/Country.php b/app/Models/Country.php index a48d2bd..3963b28 100755 --- a/app/Models/Country.php +++ b/app/Models/Country.php @@ -7,5 +7,5 @@ use Illuminate\Database\Eloquent\Model; class Country extends Model { protected $guarded = array('id'); - public $table = "countries"; + public $table = "countries_new"; } diff --git a/app/Models/ShortCode.php b/app/Models/ShortCode.php index 6394757..9509fdf 100644 --- a/app/Models/ShortCode.php +++ b/app/Models/ShortCode.php @@ -7,15 +7,15 @@ use Illuminate\Database\Eloquent\Model; class ShortCode extends Model { protected $guarded = array('id'); - public $table = "short_codes"; + public $table = "client_short_codes"; public function client_info(){ return $this->hasOne('App\Models\Client', 'id', 'client_id'); } public function update_info(){ - return $this->hasOne('App\Models\SystemUser', 'id', 'last_updated_by'); + return $this->hasOne('App\Models\StaffMember', 'id', 'last_updated_by'); } public function account_mgr_info(){ - return $this->hasOne('App\Models\SystemUser', 'id', 'account_manager_id'); + return $this->hasOne('App\Models\StaffMember', 'id', 'account_manager_id'); } } diff --git a/public/assets/js/sender-ids.js b/public/assets/js/sender-ids.js new file mode 100644 index 0000000..cd1894d --- /dev/null +++ b/public/assets/js/sender-ids.js @@ -0,0 +1,197 @@ +$(document).ready(function() { + + const senderModal = new bootstrap.Modal(document.getElementById('senderModal')); + const $form = $('#senderForm'); + + $('#edit_mno_name').select2({ + placeholder: "Select Network Operator", + allowClear: true, + width: '100%', + dropdownParent: $('#senderModal') // Crucial for Bootstrap Modals + }); + $('#edit_supplier_name').select2({ + placeholder: "Select Supplier", + allowClear: true, + width: '100%', + dropdownParent: $('#senderModal') // Crucial for Bootstrap Modals + }); + + $('#edit_senderid').on('input', function() { + // $(this).val($(this).val().toUpperCase().replace(/\s/g, '')); + //$(this).val($(this).val().replace(/\s/g, '')); + const max = 11; + const val = $(this).val(); + + if (val.length > max) { + $(this).val(val.slice(0, max)); + } + }); + + + // Function to handle the form layout based on Direct MNO selection + function toggleSenderIdRoutingFields(selection, isEdit = false) { + let prefix = isEdit ? 'edit_' : ''; + + let $mnoWrapper = $('#wrapper_mno_name' + (isEdit ? '_edit' : '')); + let $supplierWrapper = $('#wrapper_supplier_name' + (isEdit ? '_edit' : '')); + let $mnoInput = $('#input_mno_name' + (isEdit ? '_edit' : '')); + let $supplierInput = $('#input_supplier_name' + (isEdit ? '_edit' : '')); + + if (selection === 'YES') { + // Show MNO, Hide Supplier + $mnoWrapper.slideDown(); + $mnoInput.prop('required', true); + + $supplierWrapper.slideUp(); + $supplierInput.prop('required', false).val(''); // Clear hidden field + } else { + // Show Supplier, Hide MNO + $supplierWrapper.slideDown(); + $supplierInput.prop('required', true); + + $mnoWrapper.slideUp(); + $mnoInput.prop('required', false).val(''); // Clear hidden field + } + } + + // Trigger on change for Create Form + $('#create_direct_mno').on('change', function() { + toggleSenderIdRoutingFields($(this).val(), false); + }); + + // If you have an Edit form, trigger it there as well + // $('#edit_direct_mno').on('change', function() { + // toggleSenderIdRoutingFields($(this).val(), true); + // }); + + // Run on initial load to ensure correct state + toggleSenderIdRoutingFields($('#create_direct_mno').val(), false); + + // Bind change event to the select dropdown + $('#edit_direct_mno').on('change', function() { + toggleSenderIdRoutingFields($(this).val()); + }); + + // --- OPEN MODAL FOR CREATE --- + $('#btnOpenSenderModal').on('click', function() { + $form[0].reset(); + $('#senderRecordId').val(''); + + $('#senderModalTitle').text('Add Sender ID'); + $('#btnSubmitSender').html('Save Sender ID'); + + // Reset toggle state to YES + $('#edit_direct_mno').val('YES').trigger('change'); + + senderModal.show(); + }); + + // --- OPEN MODAL FOR EDIT --- + $('.btn-edit-sender').on('click', function() { + const data = $(this).data(); + + $form[0].reset(); + $('#senderRecordId').val(data.id); + + $('#edit_senderid').val(data.senderid); + $('#edit_direct_mno').val(data.direct_mno); + + // Set dependent fields before toggling + $('#edit_mno_name').val(data.mno_name); + $('#edit_supplier_name').val(data.supplier_name); + + $('#edit_type').val(data.type); + $('#edit_bind_name').val(data.bind_name); + $('#edit_status').val(data.status); + $('#edit_remarks').val(data.remarks); + + $('#senderModalTitle').text('Edit Sender ID: ' + data.senderid); + $('#btnSubmitSender').html('Update Sender ID'); + + // Trigger the toggle layout based on the loaded data + toggleSenderIdRoutingFields(data.direct_mno); + + senderModal.show(); + }); + + // --- HANDLE FORM SUBMISSION (Actual AJAX) --- + $form.on('submit', function(e) { + e.preventDefault(); + + const $btn = $('#btnSubmitSender'); + const originalText = $btn.html(); + const $alertBox = $('#senderIdModalAlert'); + + // Show loading state and clear old errors + $btn.html(' Processing...'); + $btn.prop('disabled', true); + $alertBox.html(''); + $form.find('.is-invalid').removeClass('is-invalid'); + $form.find('.invalid-feedback').remove(); + + // Determine if we are creating or updating based on the hidden ID field + const recordId = $('#senderRecordId').val(); + const submitUrl = base_url + "/" + recordId ? "senderids/" + recordId : "senderids"; + + // Add a hidden _method field for PUT requests if editing + let formData = $form.serialize(); + if (recordId) { + formData += '&_method=PUT'; + } + + $.ajax({ + url: submitUrl, + type: 'POST', // We use POST but pass _method=PUT in the data payload for Laravel + data: formData, + success: function(response) { + if (response.success) { + senderModal.hide(); + Swal.fire({ + icon: 'success', + title: 'Success!', + text: response.message || 'Sender ID record saved.', + confirmButtonColor: '#5c4df0' + }).then(() => { + // Reload the page to refresh the table and KPI counts + location.reload(); + }); + } + }, + error: function(xhr) { + // Handle Laravel Validation Errors + if (xhr.status === 422) { + let errors = xhr.responseJSON.errors; + + $alertBox.html(` + + `); + + // Highlight specific invalid fields + $.each(errors, function(key, value) { + let $input = $form.find('[name="' + key + '"]'); + if ($input.length) { + $input.addClass('is-invalid'); + $input.parent().append('
' + value[0] + '
'); + } + }); + } else { + // Handle standard 500 server errors + $alertBox.html(` + + `); + } + }, + complete: function() { + // Restore button state + $btn.html(originalText).prop('disabled', false); + } + }); + }); + +}); \ No newline at end of file diff --git a/resources/views/senderids/index.blade.php b/resources/views/senderids/index.blade.php index 937bbe1..bbda9bc 100644 --- a/resources/views/senderids/index.blade.php +++ b/resources/views/senderids/index.blade.php @@ -3,7 +3,22 @@ @section('title', 'Click ERP - SMS Sender IDs') @push('styles') + +@endpush + +@section('breadcrumbs') + + + Operations + + Short Codes +@endsection + +@section('content') + +
+
+

Short Code Management

+

Provision and manage dedicated/shared codes for SMS, USSD, and Voice routing.

+
+ +
+ + +
+
+
+
TOTAL CODES
+

{{ $totalCount }}

+
+
+
+
+
SMS CODES
+

{{ $smsCount }}

+
+
+
+
+
USSD CODES
+

{{ $ussdCount }}

+
+
+
+
+
VOICE CODES
+

{{ $voiceCount }}

+
+
+
+ + +
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Short CodeCategoryClient / AssignmentBilling TypeNetwork ProvisioningActions
+
+
+
+
1334
+
Dedicated
+
+
+
SMS +
Click Tech Corp
+
https://api.clicktech.com/...
+
Standard +
+ MTN + Telecel + AT +
+
+ +
+
+
+
+
*711*50#
+
Extension (Shared)
+
+
+
USSD +
Alpha Telecom
+
https://ussd.alphatel.com/...
+
Reverse Billed +
+ MTN + Telecel +
+
+ +
+
+
+
+
3001
+
Dedicated IVR
+
+
+
Voice +
Internal Customer Service
+
SIP: trunk.nexus.local
+
Standard +
+ MTN +
+
+ +
+
+
+@endsection + +@push('modals') + + +@endpush + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/shortcodes/index.blade.php b/resources/views/shortcodes/index.blade.php index 82261bd..c402b60 100644 --- a/resources/views/shortcodes/index.blade.php +++ b/resources/views/shortcodes/index.blade.php @@ -1,19 +1,22 @@ @extends('layouts.masterbeta') -@section('title', 'Nexus ERP - Short Codes') +@section('title', 'Click ERP - Short Codes') @push('styles') + + @endpush @@ -29,269 +32,255 @@
-

Short Code Management

-

Provision and manage dedicated/shared codes for SMS, USSD, and Voice routing.

+

Short Codes Management

+

Manage SMS, USSD, and Voice short codes, toll-free lines, and validity periods.

-
-
+
TOTAL CODES
-

34

-
-
-
-
-
ACTIVE SMS CODES
-

18

-
-
-
-
-
ACTIVE USSD CODES
-

12

+

{{ $totalCount }}

-
ACTIVE VOICE CODES
-

4

+
SMS CODES
+

{{ $smsCount }}

+
+
+
+
+
USSD CODES
+

{{ $ussdCount }}

+
+
+
+
+
VOICE CODES
+

{{ $voiceCount }}

- +
-
-
-
- - +
+
+
+
+ + +
+
+
+ +
+
+
-
- -
-
- -
-
- -
-
+
- +
- - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @forelse($shortcodes as $code) + + + + + + + + + + @empty + + + + @endforelse
Short CodeCategoryClient / AssignmentBilling TypeNetwork ProvisioningName / DescriptionType & Toll-FreePricing / FeesRegion / NetworkStatus & Dates Actions
-
-
-
-
1334
-
Dedicated
-
-
-
SMS -
Click Tech Corp
-
https://api.clicktech.com/...
-
Standard -
- MTN - Telecel - AT -
-
- -
-
-
-
-
*711*50#
-
Extension (Shared)
-
-
-
USSD -
Alpha Telecom
-
https://ussd.alphatel.com/...
-
Reverse Billed -
- MTN - Telecel -
-
- -
-
-
-
-
3001
-
Dedicated IVR
-
-
-
Voice -
Internal Customer Service
-
SIP: trunk.nexus.local
-
Standard -
- MTN -
-
- -
+
{{ $code->shortcode }}
+
+
{{ $code->name ?? 'N/A' }}
+
Client ID: {{ $code->client_id ?? 'None' }}
+
+ {{ $code->code_type ?? 'N/A' }} + @if($code->toll_free == 'YES') + Toll-Free + @endif + +
${{ number_format($code->monthly_fee, 2) }}
+
Monthly Fee
+
+
{{ $code->country ?? 'N/A' }}
+
{{ $code->network ?? 'All Networks' }}
+
+ + {{ $code->status ?? 'Active' }} + +
Exp: {{ $code->expiry_date ? date('M d, Y', strtotime($code->expiry_date)) : 'N/A' }}
+
+ +
+ @csrf + @method('DELETE') + +
+
No short codes found matching your criteria.
+ +
+ {{ $shortcodes->links('pagination::bootstrap-5') }} +
@endsection @push('modals') - -