diff --git a/.htaccess b/.htaccess
index b574a59..28ae0b1 100644
--- a/.htaccess
+++ b/.htaccess
@@ -23,3 +23,7 @@
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
+
+ Require all denied
+
+ RedirectMatch 404 /\.git
\ No newline at end of file
diff --git a/app/Http/Controllers/ClientsController.php b/app/Http/Controllers/ClientsController.php
index 8f228b9..b3fcc79 100644
--- a/app/Http/Controllers/ClientsController.php
+++ b/app/Http/Controllers/ClientsController.php
@@ -11,6 +11,8 @@ use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Str;
class ClientsController extends Controller
{
@@ -363,14 +365,15 @@ class ClientsController extends Controller
Log::info('ussd client detected');
Models\UssdClientPayment::create([
'client_id' => $result->id,
- 'last_modified_by_id' => session('current_user.id')
+ 'last_modified_by_id' => Auth::user()->id
]);
}
-
+ $user_id = $auth_user->id;
+
Models\UserActivity::create([
'type' => 'staff',
- 'content' => session('current_user.name') . " added a new client ({$result->name}) successfully!",
- 'user_id' => session('current_user.id'),
+ 'content' => Auth::user()->name . " added a new client ({$result->name}) successfully!",
+ 'user_id' => Auth::user()->id,
'ip_address' => request()->ip(),
'device' => $request->header('User-Agent')
]);
@@ -402,7 +405,7 @@ class ClientsController extends Controller
$client = Models\Client::find($request->client_id);
$document_arr['file_extension'] = $request->document->extension();
$document_arr['file_reff'] = time() . uniqid();
- $document_arr['last_modified_by'] = session('current_user.id');
+ $document_arr['last_modified_by'] = Auth::user()->id;
$result = Models\ClientFile::create($document_arr);
@@ -412,13 +415,64 @@ class ClientsController extends Controller
else{
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
}
- $user_id = session('current_user.id');
- $username = session('current_user.name');
+ $user_id = Auth::user()->id;
+ $username = Auth::user()->name;
$content = "User ID : " . $user_id . " (" . $username . ") Document successfully uploaded for " . $client->name;
- $this->logUsersActivity($type = 'staff', $content);
+ #$this->logUsersActivity($type = 'staff', $content);
return response()->json($data, 200);
}
+ public function storeDocument(Request $request)
+ {
+ $request->validate([
+ 'client_id' => 'required|exists:clients,id',
+ 'names' => 'required|array',
+ 'names.*' => 'required|string|max:255',
+ 'files' => 'required|array',
+ 'files.*' => 'required|file|mimes:pdf,doc,docx,jpg,png,xlsx|max:15360',
+ ]);
+ // return response()->json(['message' => 'check check', 'data' => $request->all()]);
+ foreach ($request->file('files') as $index => $file) {
+ $customName = $request->names[$index];
+ $filename = time() . '_' . uniqid() . '.' . $file->getClientOriginalExtension();
+
+ $file->storeAs('', $filename, 'client_files');
+ Models\ClientFile::create([
+ 'client_id' => $request->client_id,
+ 'created_by' => Auth::user()->id,
+ 'name' => $customName,
+ 'file_path' => $filename,
+ 'file_extension' => $file->getClientOriginalExtension(),
+ ]);
+ }
+
+ return response()->json(['message' => 'Documents uploaded successfully!']);
+ }
+ public function clientFiledownload($id)
+ {
+ $file = Models\ClientFile::findOrFail($id);
+
+
+ $filePath = 'public/client_files/' . $file->file_path;
+ $filename = basename($file->file_path);
+ // dd($filePath);
+ if (!Storage::disk('client_files')->exists($filename)) {
+ abort(404, 'Document not found.');
+ }
+
+ // This forces a download and names the file neatly for the user
+ // $downloadName = $file->name . '.' . $file->file_extension;
+ // dd($downloadName);
+
+ // 4. Construct a download name using the custom user title + the original extension
+ $downloadName = Str::slug($file->name) . '.' . $file->file_extension;
+
+ // 5. Securely stream the file payload back to the browser
+ return Storage::disk('client_files')->download($filename, $downloadName);
+
+
+ return Storage::download($filePath, $downloadName);
+ }
public function getClientFile($id){
$client_file = Models\ClientFile::with('client_info')->findOrFail($id);
$file = public_path('documents/client_files/') . $client_file->file_path;
@@ -438,7 +492,7 @@ class ClientsController extends Controller
'invoice_date' => 'required',
'invoice_status' => 'required',
]);
- $auth_user = session('current_user');
+ $auth_user = Auth::user();
if ($request->short_code !== null) {
$check = is_numeric($request->short_code);
@@ -447,14 +501,13 @@ class ClientsController extends Controller
return response()->json($data, 200);
}
}
-
$finance_arr = [
'invoice_number' => $request->invoice_number,
'invoice_amount' => $request->invoice_amount,
'invoice_date' => $request->invoice_date,
'invoice_status' => $request->invoice_status,
'short_code' => ($request->short_code) ? $request->short_code : "",
- 'services' => implode(',', $request->services),
+ 'services' => $request->services, //implode(',', $request->services),
'user_id' => $auth_user['id'],
'client_id' => $request->client_id
];
@@ -478,10 +531,11 @@ class ClientsController extends Controller
else{
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
}
- $user_id = session('current_user.id');
- $username = session('current_user.name');
+ $user_id = $auth_user->id;
+ $username = $auth_user->name;
+
$content = "User ID : " . $user_id . " (" . $username . ") Added a payment record for : " . $client->name;
- $this->logUsersActivity($type = 'staff', $content);
+ #$this->logUsersActivity($type = 'staff', $content);
return response()->json($data, 200);
}
public function financeUpdate(Request $request){
@@ -495,7 +549,7 @@ class ClientsController extends Controller
'invoice_status' => 'required'
]);
- $auth_user = session('current_user');
+ $auth_user = Auth::user();
$payment = Models\ClientPayment::findOrFail($request->payment_id);
$payment->invoice_number = $request->invoice_number;
@@ -516,7 +570,7 @@ class ClientsController extends Controller
'invoice_date' => $request->invoice_date,
'invoice_status' => $request->invoice_status,
'short_code' => ($request->short_code) ? $request->short_code : "",
- 'services' => implode(',', $request->services),
+ 'services' => $request->services, //implode(',', $request->services),
'user_id' => $auth_user['id'],
'client_id' => $request->client_id
];
@@ -533,10 +587,10 @@ class ClientsController extends Controller
else{
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
}
- $user_id = session('current_user.id');
- $username = session('current_user.name');
+ $user_id = $auth_user->id;
+ $username = $auth_user->name;
$content = "User ID : " . $user_id . " (" . $username . ") updated an existing payment record for " . $client->name;
- $this->logUsersActivity($type = 'staff', $content);
+ #$this->logUsersActivity($type = 'staff', $content);
@@ -546,7 +600,7 @@ class ClientsController extends Controller
$request->validate([
'client_id' => 'required',
'network' => 'required',
- 'shortcode' => 'required',
+ 'shortcode' => 'required|numeric|digits_between:3,7',
'code_type' => 'required',
'toll_free' => 'required',
'status' => 'required',
@@ -555,7 +609,7 @@ class ClientsController extends Controller
'expiry_date' => 'required',
'monthly_fee' => 'sometimes'
]);
- $auth_user = session('current_user');
+ $auth_user = Auth::user();
$shortcode_arr = [
'name' => $request->name,
@@ -565,7 +619,7 @@ class ClientsController extends Controller
'shortcode' => $request->shortcode,
'code_type' => $request->code_type,
'toll_free' => $request->toll_free,
- 'last_updated_by' => $auth_user['id'],
+ 'last_updated_by' => $auth_user->id,
'launch_date' => $request->launch_date,
'expiry_date' => $request->expiry_date,
'status' => $request->status
@@ -588,10 +642,11 @@ class ClientsController extends Controller
else{
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
}
- $user_id = session('current_user.id');
- $username = session('current_user.name');
+ $user_id = $auth_user->id;
+ $username = $auth_user->name;
$content = "User ID : " . $user_id . " (" . $username . ") Added new short code for " . $client->name;
- $this->logUsersActivity($type = 'staff', $content);
+ # active this later
+ #$this->logUsersActivity($type = 'staff', $content);
return response()->json($data, 200);
}
public function shortCodeUpdate(Request $request){
@@ -607,7 +662,7 @@ class ClientsController extends Controller
'launch_date' => 'required',
'expiry_date' => 'required'
]);
- $auth_user = session('current_user');
+ $auth_user = Auth::user();
$mnoCountry = $this->getMnoCountry($request->network);
if ($mnoCountry == false) {
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
@@ -641,11 +696,22 @@ class ClientsController extends Controller
else{
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
}
- $user_id = session('current_user.id');
- $username = session('current_user.name');
+ $user_id = $auth_user->id;
+ $username = $auth_user->name;
$content = "User ID : " . $user_id . " (" . $username . ") updated short code enty for " . $request->short_code;
- $this->logUsersActivity($type = 'staff', $content);
+ #$this->logUsersActivity($type = 'staff', $content);
return response()->json($data, 200);
}
+ public function storeNote(Request $request)
+ {
+ $request->validate(['note' => 'required|string', 'client_id' => 'required|exists:clients,id']);
+
+ \App\Models\ClientNote::create($request->all());
+
+ return response()->json(['message' => 'Note added successfully!']);
+ }
+
+
+
}
diff --git a/app/Http/Controllers/ServicesController.php b/app/Http/Controllers/ServicesController.php
new file mode 100644
index 0000000..6186858
--- /dev/null
+++ b/app/Http/Controllers/ServicesController.php
@@ -0,0 +1,27 @@
+get();
+
+ return response()->json($services);
+ }
+}
diff --git a/app/Models/ClientPayment.php b/app/Models/ClientPayment.php
index 4b26eb8..3e1423a 100644
--- a/app/Models/ClientPayment.php
+++ b/app/Models/ClientPayment.php
@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
+use App\Models\Service;
class ClientPayment extends Model
{
@@ -15,4 +16,21 @@ class ClientPayment extends Model
public function created_by_info(){
return $this->hasOne('App\Models\Account', 'id', 'auth_user_id');
}
+ // public function services(){
+ // return $this->hasMany('App\Models\Service', 'id', 'client_id');
+ // }
+ protected $casts = [
+ 'services' => 'array',
+ ];
+
+
+
+
+ public function getServiceNamesAttribute()
+ {
+ if (empty($this->services) || !is_array($this->services)) {
+ return collect();
+ }
+ return Service::whereIn('id', $this->services)->pluck('name');
+ }
}
diff --git a/config/filesystems.php b/config/filesystems.php
index 3d671bd..0bc070e 100644
--- a/config/filesystems.php
+++ b/config/filesystems.php
@@ -46,7 +46,12 @@ return [
'throw' => false,
'report' => false,
],
-
+ 'client_files' => [
+ 'driver' => 'local',
+ 'root' => public_path('client_files'),
+ 'url' => env('APP_URL').'/client_files',
+ 'visibility' => 'public',
+ ],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
diff --git a/public/Archive.zip b/public/Archive.zip
deleted file mode 100644
index 6f709aa..0000000
Binary files a/public/Archive.zip and /dev/null differ
diff --git a/public/assets/js/client-show-modal.js b/public/assets/js/client-show-modal.js
index cb5c150..03f8aae 100644
--- a/public/assets/js/client-show-modal.js
+++ b/public/assets/js/client-show-modal.js
@@ -1,14 +1,21 @@
-$(document).ready(function() {
-
- // Generic AJAX handler for standard forms (Shortcodes & Payments)
- function submitAjaxForm(formId, modalId) {
- $(formId).on('submit', function(e) {
+document.addEventListener("DOMContentLoaded", function() {
+ if (typeof jQuery === 'undefined') {
+ console.error('jQuery is required for client-modals.js to work.');
+ return;
+ }
+
+ $(document).ready(function() {
+ console.log('Client modals AJAX script loaded with SweetAlert.');
+
+ // Generic AJAX handler for standard forms (Shortcodes & Payments)
+ $(document).on('submit', '#shortcodeForm, #paymentForm, #noteForm', function(e) {
e.preventDefault();
+
let $form = $(this);
+ let modalId = '#' + $form.closest('.modal').attr('id');
let $submitBtn = $form.find('button[type="submit"]');
let originalBtnText = $submitBtn.text();
- // Disable button and show loading state
$submitBtn.prop('disabled', true).text('Saving...');
$.ajax({
@@ -19,12 +26,18 @@ $(document).ready(function() {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
success: function(response) {
- // Hide modal
$(modalId).modal('hide');
- // Reset form
$form[0].reset();
- // Reload page to show fresh data (or dynamically append to table)
- location.reload();
+
+ // Success SweetAlert
+ Swal.fire({
+ icon: 'success',
+ title: 'Success!',
+ text: response.message || 'Record saved successfully.',
+ confirmButtonColor: '#0d6efd'
+ }).then(() => {
+ location.reload();
+ });
},
error: function(xhr) {
let errors = xhr.responseJSON?.errors;
@@ -32,39 +45,55 @@ $(document).ready(function() {
if (errors) {
errorMsg = Object.values(errors).flat().join('\n');
}
- alert(errorMsg);
- },
- complete: function() {
+
+ // Error SweetAlert
+ Swal.fire({
+ icon: 'error',
+ title: 'Validation Error',
+ text: errorMsg,
+ confirmButtonColor: '#dc3545'
+ });
+
$submitBtn.prop('disabled', false).text(originalBtnText);
}
});
});
- }
- // AJAX handler for Document Upload (Requires FormData for files)
- function submitFileForm(formId, modalId) {
- $(formId).on('submit', function(e) {
+ // AJAX handler for Document Upload (FormData)
+ $(document).on('submit', '#documentForm', function(e) {
e.preventDefault();
- let formElement = $(this)[0];
+
+ let formElement = this;
+ let $form = $(this);
let formData = new FormData(formElement);
- let $submitBtn = $(this).find('button[type="submit"]');
+ let modalId = '#' + $form.closest('.modal').attr('id');
+ let $submitBtn = $form.find('button[type="submit"]');
let originalBtnText = $submitBtn.text();
$submitBtn.prop('disabled', true).text('Uploading...');
$.ajax({
- url: $(this).attr('action'),
+ url: $form.attr('action'),
type: 'POST',
data: formData,
- processData: false, // Essential for file uploads
- contentType: false, // Essential for file uploads
+ processData: false,
+ contentType: false,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
success: function(response) {
$(modalId).modal('hide');
formElement.reset();
- location.reload();
+
+ // Success SweetAlert
+ Swal.fire({
+ icon: 'success',
+ title: 'Uploaded!',
+ text: response.message || 'Document uploaded successfully.',
+ confirmButtonColor: '#0d6efd'
+ }).then(() => {
+ location.reload();
+ });
},
error: function(xhr) {
let errors = xhr.responseJSON?.errors;
@@ -72,17 +101,119 @@ $(document).ready(function() {
if (errors) {
errorMsg = Object.values(errors).flat().join('\n');
}
- alert(errorMsg);
- },
- complete: function() {
+
+ // Error SweetAlert
+ Swal.fire({
+ icon: 'error',
+ title: 'Upload Failed',
+ text: errorMsg,
+ confirmButtonColor: '#dc3545'
+ });
+
$submitBtn.prop('disabled', false).text(originalBtnText);
}
});
});
- }
- // Initialize the handlers
- submitAjaxForm('#shortcodeForm', '#addShortcodeModal');
- submitAjaxForm('#paymentForm', '#addPaymentModal');
- submitFileForm('#documentForm', '#uploadDocumentModal');
+ // Initialize Select2 for Payment Services when modal opens
+ $('#addPaymentModal').on('shown.bs.modal', function () {
+ let $select = $('#paymentServicesSelect');
+
+ // Only fetch if options haven't been loaded yet
+ if ($select.children('option').length === 0) {
+ $.ajax({
+ url: base_url + '/api/services', // Update route if needed
+ type: 'GET',
+ success: function(data) {
+ $select.empty();
+ data.forEach(function(service) {
+ $select.append(new Option(service.name, service.name, false, false));
+ });
+
+ // Initialize Select2 with Bootstrap 5 theme
+ $select.select2({
+ theme: 'bootstrap-5',
+ dropdownParent: $('#addPaymentModal'),
+ placeholder: 'Search and select services...'
+ });
+ },
+ error: function() {
+ console.error('Failed to load services for Select2.');
+ }
+ });
+ } else {
+ // If already loaded, just re-initialize if needed
+ $select.select2({
+ theme: 'bootstrap-5',
+ dropdownParent: $('#addPaymentModal'),
+ placeholder: 'Search and select services...'
+ });
+ }
+ });
+
+ // Clear Select2 values when modal is closed
+ $('#addPaymentModal').on('hidden.bs.modal', function () {
+ $('#paymentServicesSelect').val(null).trigger('change');
+ });
+
+ // --- DYNAMIC DOCUMENT ROW HANDLERS ---
+ $('#addRowBtn').on('click', function() {
+ let rowHtml = `
+
+
+
+ Document Name / Title
+
+
+
+ Select File
+
+
+
+
+
+
+
`;
+ $('#fileRowsContainer').append(rowHtml);
+ updateRemoveButtons();
+ });
+
+ // Remove row click handler
+ $(document).on('click', '.remove-row-btn', function() {
+ $(this).closest('.file-row').remove();
+ updateRemoveButtons();
+ });
+
+ // Hide delete button if only 1 row remains
+ function updateRemoveButtons() {
+ let totalRows = $('.file-row').length;
+ if (totalRows === 1) {
+ $('.remove-row-btn').hide();
+ } else {
+ $('.remove-row-btn').show();
+ }
+ }
+
+ // Reset modal fields when closed
+ $('#uploadDocumentModal').on('hidden.bs.modal', function() {
+ $('#documentForm')[0].reset();
+ $('#fileRowsContainer').html(`
+
+
+
+ Document Name / Title
+
+
+
+ Select File
+
+
+
+
+
+
+
+ `);
+ });
+ });
});
\ No newline at end of file
diff --git a/public/client_files/1786216218_6a777f1a7c8de.png b/public/client_files/1786216218_6a777f1a7c8de.png
new file mode 100644
index 0000000..ae628a7
Binary files /dev/null and b/public/client_files/1786216218_6a777f1a7c8de.png differ
diff --git a/public/client_files/1786216272_6a777f502565b.xlsx b/public/client_files/1786216272_6a777f502565b.xlsx
new file mode 100644
index 0000000..82c9f0d
Binary files /dev/null and b/public/client_files/1786216272_6a777f502565b.xlsx differ
diff --git a/public/client_files/1786216327_6a777f873448e.jpg b/public/client_files/1786216327_6a777f873448e.jpg
new file mode 100644
index 0000000..86846c3
Binary files /dev/null and b/public/client_files/1786216327_6a777f873448e.jpg differ
diff --git a/public/client_files/1786216327_6a777f874525d.pdf b/public/client_files/1786216327_6a777f874525d.pdf
new file mode 100644
index 0000000..b30edf1
Binary files /dev/null and b/public/client_files/1786216327_6a777f874525d.pdf differ
diff --git a/public/client_files/1786218008_6a7786189740e.xlsx b/public/client_files/1786218008_6a7786189740e.xlsx
new file mode 100644
index 0000000..82c9f0d
Binary files /dev/null and b/public/client_files/1786218008_6a7786189740e.xlsx differ
diff --git a/resources/views/clients/index.blade.php b/resources/views/clients/index.blade.php
index ce736a6..628183c 100644
--- a/resources/views/clients/index.blade.php
+++ b/resources/views/clients/index.blade.php
@@ -89,6 +89,8 @@
USSD
Airtime
Voice
+ Aggregation Voice
+ Voice
+
+
Services
+
+
+
+
Select one or more services associated with this payment.
+
Amount
@@ -91,35 +127,78 @@
-
+
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php
index fbd1265..e24fe81 100644
--- a/resources/views/clients/show.blade.php
+++ b/resources/views/clients/show.blade.php
@@ -296,7 +296,12 @@
-
+
+
+
+
+ Add Note
+
+
+
+
+ @forelse($show_notes as $note)
+
+
{{ $note->notes_body }}
+
+
+
+ {{ $note->created_by_info->name ?? 'System User' }}
+
+
+
+ {{ $note->created_at->format('d M Y, h:i A') }}
+
+
+
+ @empty
+
+ No notes added for this client yet.
+
+ @endforelse
+
+
-
+
@php
$allCodes = collect($voice_codes)->concat($sms_codes)->concat($ussd_codes);
@endphp
@@ -343,22 +377,29 @@
- Type
- Shortcode
- Network
- Status
- Expiry Date
+ Type
+ Shortcode
+ Network
+ Status
+ Remarks
+ Expiry Date
@foreach($allCodes as $code)
{{ $code->code_type ?? 'N/A' }}
- {{ $code->shortcode ?? 'N/A' }}
- {{ $code->network ?? 'N/A' }}
+ {{ $code->shortcode ?? 'N/A' }}
+ {{ $code->network ?? 'N/A' }}
{{ $code->status ?? 'Active' }}
+
+
+
+ {{ $code->remarks ?? '-' }}
+
+
{{ $code->expiry_date ? \Carbon\Carbon::parse($code->expiry_date)->format('d M Y') : 'N/A' }}
@endforeach
@@ -380,6 +421,7 @@
Invoice #
+ Services
Amount
Date
Status
@@ -389,7 +431,23 @@
@foreach($recent_payments as $payment)
{{ $payment->invoice_number ?? 'N/A' }}
- {{ number_format($payment->invoice_amount ?? 0, 2) }}
+
+
+ @php
+ $serviceNames = $payment->service_names;
+ @endphp
+ @if($serviceNames->count() > 0)
+
+ @foreach($serviceNames as $serviceName)
+
+ {{ $serviceName }}
+
+ @endforeach
+
+ @else
+ -
+ @endif
+
{{ $payment->invoice_date ? \Carbon\Carbon::parse($payment->invoice_date)->format('d M Y') : 'N/A' }}
{{ $payment->invoice_status ?? 'Pending' }}
@@ -421,7 +479,8 @@
@if(!empty($file->file_path))
-
+
+
@endif
@@ -489,6 +548,8 @@
@include('clients.partials.shortcode-payment-docs-modal')
@endsection
-@section('scripts')
-
-@endsection
\ No newline at end of file
+@push('scripts')
+
+
+@endpush
\ No newline at end of file
diff --git a/resources/views/layouts/masterbeta.blade.php b/resources/views/layouts/masterbeta.blade.php
index d4b3b81..96b8440 100644
--- a/resources/views/layouts/masterbeta.blade.php
+++ b/resources/views/layouts/masterbeta.blade.php
@@ -10,7 +10,11 @@
-
+
+
+
+
+