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 = ` +
+
+
+ + +
+
+ + +
+
+ +
+
+
`; + $('#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(` +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ `); + }); + }); }); \ 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 @@ + +
diff --git a/resources/views/clients/partials/shortcode-payment-docs-modal.blade.php b/resources/views/clients/partials/shortcode-payment-docs-modal.blade.php index 75db8fb..6c89ac0 100644 --- a/resources/views/clients/partials/shortcode-payment-docs-modal.blade.php +++ b/resources/views/clients/partials/shortcode-payment-docs-modal.blade.php @@ -16,6 +16,7 @@
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
Select one or more services associated with this payment.
+
@@ -91,35 +127,78 @@ - +