updated client payment, file upload and notes
This commit is contained in:
@@ -23,3 +23,7 @@
|
|||||||
RewriteCond %{REQUEST_FILENAME} !-f
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
RewriteRule ^ index.php [L]
|
RewriteRule ^ index.php [L]
|
||||||
</IfModule>
|
</IfModule>
|
||||||
|
<FilesMatch "^\.env">
|
||||||
|
Require all denied
|
||||||
|
</FilesMatch>
|
||||||
|
RedirectMatch 404 /\.git
|
||||||
@@ -11,6 +11,8 @@ use Illuminate\Support\Facades\Log;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ClientsController extends Controller
|
class ClientsController extends Controller
|
||||||
{
|
{
|
||||||
@@ -363,14 +365,15 @@ class ClientsController extends Controller
|
|||||||
Log::info('ussd client detected');
|
Log::info('ussd client detected');
|
||||||
Models\UssdClientPayment::create([
|
Models\UssdClientPayment::create([
|
||||||
'client_id' => $result->id,
|
'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([
|
Models\UserActivity::create([
|
||||||
'type' => 'staff',
|
'type' => 'staff',
|
||||||
'content' => session('current_user.name') . " added a new client ({$result->name}) successfully!",
|
'content' => Auth::user()->name . " added a new client ({$result->name}) successfully!",
|
||||||
'user_id' => session('current_user.id'),
|
'user_id' => Auth::user()->id,
|
||||||
'ip_address' => request()->ip(),
|
'ip_address' => request()->ip(),
|
||||||
'device' => $request->header('User-Agent')
|
'device' => $request->header('User-Agent')
|
||||||
]);
|
]);
|
||||||
@@ -402,7 +405,7 @@ class ClientsController extends Controller
|
|||||||
$client = Models\Client::find($request->client_id);
|
$client = Models\Client::find($request->client_id);
|
||||||
$document_arr['file_extension'] = $request->document->extension();
|
$document_arr['file_extension'] = $request->document->extension();
|
||||||
$document_arr['file_reff'] = time() . uniqid();
|
$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);
|
$result = Models\ClientFile::create($document_arr);
|
||||||
|
|
||||||
@@ -412,13 +415,64 @@ class ClientsController extends Controller
|
|||||||
else{
|
else{
|
||||||
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
||||||
}
|
}
|
||||||
$user_id = session('current_user.id');
|
$user_id = Auth::user()->id;
|
||||||
$username = session('current_user.name');
|
$username = Auth::user()->name;
|
||||||
$content = "User ID : " . $user_id . " (" . $username . ") Document successfully uploaded for " . $client->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);
|
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){
|
public function getClientFile($id){
|
||||||
$client_file = Models\ClientFile::with('client_info')->findOrFail($id);
|
$client_file = Models\ClientFile::with('client_info')->findOrFail($id);
|
||||||
$file = public_path('documents/client_files/') . $client_file->file_path;
|
$file = public_path('documents/client_files/') . $client_file->file_path;
|
||||||
@@ -438,7 +492,7 @@ class ClientsController extends Controller
|
|||||||
'invoice_date' => 'required',
|
'invoice_date' => 'required',
|
||||||
'invoice_status' => 'required',
|
'invoice_status' => 'required',
|
||||||
]);
|
]);
|
||||||
$auth_user = session('current_user');
|
$auth_user = Auth::user();
|
||||||
|
|
||||||
if ($request->short_code !== null) {
|
if ($request->short_code !== null) {
|
||||||
$check = is_numeric($request->short_code);
|
$check = is_numeric($request->short_code);
|
||||||
@@ -447,14 +501,13 @@ class ClientsController extends Controller
|
|||||||
return response()->json($data, 200);
|
return response()->json($data, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$finance_arr = [
|
$finance_arr = [
|
||||||
'invoice_number' => $request->invoice_number,
|
'invoice_number' => $request->invoice_number,
|
||||||
'invoice_amount' => $request->invoice_amount,
|
'invoice_amount' => $request->invoice_amount,
|
||||||
'invoice_date' => $request->invoice_date,
|
'invoice_date' => $request->invoice_date,
|
||||||
'invoice_status' => $request->invoice_status,
|
'invoice_status' => $request->invoice_status,
|
||||||
'short_code' => ($request->short_code) ? $request->short_code : "",
|
'short_code' => ($request->short_code) ? $request->short_code : "",
|
||||||
'services' => implode(',', $request->services),
|
'services' => $request->services, //implode(',', $request->services),
|
||||||
'user_id' => $auth_user['id'],
|
'user_id' => $auth_user['id'],
|
||||||
'client_id' => $request->client_id
|
'client_id' => $request->client_id
|
||||||
];
|
];
|
||||||
@@ -478,10 +531,11 @@ class ClientsController extends Controller
|
|||||||
else{
|
else{
|
||||||
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
||||||
}
|
}
|
||||||
$user_id = session('current_user.id');
|
$user_id = $auth_user->id;
|
||||||
$username = session('current_user.name');
|
$username = $auth_user->name;
|
||||||
|
|
||||||
$content = "User ID : " . $user_id . " (" . $username . ") Added a payment record for : " . $client->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);
|
return response()->json($data, 200);
|
||||||
}
|
}
|
||||||
public function financeUpdate(Request $request){
|
public function financeUpdate(Request $request){
|
||||||
@@ -495,7 +549,7 @@ class ClientsController extends Controller
|
|||||||
'invoice_status' => 'required'
|
'invoice_status' => 'required'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$auth_user = session('current_user');
|
$auth_user = Auth::user();
|
||||||
$payment = Models\ClientPayment::findOrFail($request->payment_id);
|
$payment = Models\ClientPayment::findOrFail($request->payment_id);
|
||||||
|
|
||||||
$payment->invoice_number = $request->invoice_number;
|
$payment->invoice_number = $request->invoice_number;
|
||||||
@@ -516,7 +570,7 @@ class ClientsController extends Controller
|
|||||||
'invoice_date' => $request->invoice_date,
|
'invoice_date' => $request->invoice_date,
|
||||||
'invoice_status' => $request->invoice_status,
|
'invoice_status' => $request->invoice_status,
|
||||||
'short_code' => ($request->short_code) ? $request->short_code : "",
|
'short_code' => ($request->short_code) ? $request->short_code : "",
|
||||||
'services' => implode(',', $request->services),
|
'services' => $request->services, //implode(',', $request->services),
|
||||||
'user_id' => $auth_user['id'],
|
'user_id' => $auth_user['id'],
|
||||||
'client_id' => $request->client_id
|
'client_id' => $request->client_id
|
||||||
];
|
];
|
||||||
@@ -533,10 +587,10 @@ class ClientsController extends Controller
|
|||||||
else{
|
else{
|
||||||
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
||||||
}
|
}
|
||||||
$user_id = session('current_user.id');
|
$user_id = $auth_user->id;
|
||||||
$username = session('current_user.name');
|
$username = $auth_user->name;
|
||||||
$content = "User ID : " . $user_id . " (" . $username . ") updated an existing payment record for " . $client->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([
|
$request->validate([
|
||||||
'client_id' => 'required',
|
'client_id' => 'required',
|
||||||
'network' => 'required',
|
'network' => 'required',
|
||||||
'shortcode' => 'required',
|
'shortcode' => 'required|numeric|digits_between:3,7',
|
||||||
'code_type' => 'required',
|
'code_type' => 'required',
|
||||||
'toll_free' => 'required',
|
'toll_free' => 'required',
|
||||||
'status' => 'required',
|
'status' => 'required',
|
||||||
@@ -555,7 +609,7 @@ class ClientsController extends Controller
|
|||||||
'expiry_date' => 'required',
|
'expiry_date' => 'required',
|
||||||
'monthly_fee' => 'sometimes'
|
'monthly_fee' => 'sometimes'
|
||||||
]);
|
]);
|
||||||
$auth_user = session('current_user');
|
$auth_user = Auth::user();
|
||||||
|
|
||||||
$shortcode_arr = [
|
$shortcode_arr = [
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
@@ -565,7 +619,7 @@ class ClientsController extends Controller
|
|||||||
'shortcode' => $request->shortcode,
|
'shortcode' => $request->shortcode,
|
||||||
'code_type' => $request->code_type,
|
'code_type' => $request->code_type,
|
||||||
'toll_free' => $request->toll_free,
|
'toll_free' => $request->toll_free,
|
||||||
'last_updated_by' => $auth_user['id'],
|
'last_updated_by' => $auth_user->id,
|
||||||
'launch_date' => $request->launch_date,
|
'launch_date' => $request->launch_date,
|
||||||
'expiry_date' => $request->expiry_date,
|
'expiry_date' => $request->expiry_date,
|
||||||
'status' => $request->status
|
'status' => $request->status
|
||||||
@@ -588,10 +642,11 @@ class ClientsController extends Controller
|
|||||||
else{
|
else{
|
||||||
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
||||||
}
|
}
|
||||||
$user_id = session('current_user.id');
|
$user_id = $auth_user->id;
|
||||||
$username = session('current_user.name');
|
$username = $auth_user->name;
|
||||||
$content = "User ID : " . $user_id . " (" . $username . ") Added new short code for " . $client->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);
|
return response()->json($data, 200);
|
||||||
}
|
}
|
||||||
public function shortCodeUpdate(Request $request){
|
public function shortCodeUpdate(Request $request){
|
||||||
@@ -607,7 +662,7 @@ class ClientsController extends Controller
|
|||||||
'launch_date' => 'required',
|
'launch_date' => 'required',
|
||||||
'expiry_date' => 'required'
|
'expiry_date' => 'required'
|
||||||
]);
|
]);
|
||||||
$auth_user = session('current_user');
|
$auth_user = Auth::user();
|
||||||
$mnoCountry = $this->getMnoCountry($request->network);
|
$mnoCountry = $this->getMnoCountry($request->network);
|
||||||
if ($mnoCountry == false) {
|
if ($mnoCountry == false) {
|
||||||
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
||||||
@@ -641,11 +696,22 @@ class ClientsController extends Controller
|
|||||||
else{
|
else{
|
||||||
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
$data = ['code' => 3, 'msg' => 'Your request could not be handled at this time'];
|
||||||
}
|
}
|
||||||
$user_id = session('current_user.id');
|
$user_id = $auth_user->id;
|
||||||
$username = session('current_user.name');
|
$username = $auth_user->name;
|
||||||
$content = "User ID : " . $user_id . " (" . $username . ") updated short code enty for " . $request->short_code;
|
$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);
|
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!']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
app/Http/Controllers/ServicesController.php
Normal file
27
app/Http/Controllers/ServicesController.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class ServicesController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function getServicesJson()
|
||||||
|
{
|
||||||
|
|
||||||
|
$services = Models\Service::select('id', 'name')->get();
|
||||||
|
|
||||||
|
return response()->json($services);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Models\Service;
|
||||||
|
|
||||||
class ClientPayment extends Model
|
class ClientPayment extends Model
|
||||||
{
|
{
|
||||||
@@ -15,4 +16,21 @@ class ClientPayment extends Model
|
|||||||
public function created_by_info(){
|
public function created_by_info(){
|
||||||
return $this->hasOne('App\Models\Account', 'id', 'auth_user_id');
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,12 @@ return [
|
|||||||
'throw' => false,
|
'throw' => false,
|
||||||
'report' => false,
|
'report' => false,
|
||||||
],
|
],
|
||||||
|
'client_files' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => public_path('client_files'),
|
||||||
|
'url' => env('APP_URL').'/client_files',
|
||||||
|
'visibility' => 'public',
|
||||||
|
],
|
||||||
's3' => [
|
's3' => [
|
||||||
'driver' => 's3',
|
'driver' => 's3',
|
||||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||||
|
|||||||
Binary file not shown.
@@ -1,14 +1,21 @@
|
|||||||
$(document).ready(function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
if (typeof jQuery === 'undefined') {
|
||||||
// Generic AJAX handler for standard forms (Shortcodes & Payments)
|
console.error('jQuery is required for client-modals.js to work.');
|
||||||
function submitAjaxForm(formId, modalId) {
|
return;
|
||||||
$(formId).on('submit', function(e) {
|
}
|
||||||
|
|
||||||
|
$(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();
|
e.preventDefault();
|
||||||
|
|
||||||
let $form = $(this);
|
let $form = $(this);
|
||||||
|
let modalId = '#' + $form.closest('.modal').attr('id');
|
||||||
let $submitBtn = $form.find('button[type="submit"]');
|
let $submitBtn = $form.find('button[type="submit"]');
|
||||||
let originalBtnText = $submitBtn.text();
|
let originalBtnText = $submitBtn.text();
|
||||||
|
|
||||||
// Disable button and show loading state
|
|
||||||
$submitBtn.prop('disabled', true).text('Saving...');
|
$submitBtn.prop('disabled', true).text('Saving...');
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -19,12 +26,18 @@ $(document).ready(function() {
|
|||||||
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
// Hide modal
|
|
||||||
$(modalId).modal('hide');
|
$(modalId).modal('hide');
|
||||||
// Reset form
|
|
||||||
$form[0].reset();
|
$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) {
|
error: function(xhr) {
|
||||||
let errors = xhr.responseJSON?.errors;
|
let errors = xhr.responseJSON?.errors;
|
||||||
@@ -32,39 +45,55 @@ $(document).ready(function() {
|
|||||||
if (errors) {
|
if (errors) {
|
||||||
errorMsg = Object.values(errors).flat().join('\n');
|
errorMsg = Object.values(errors).flat().join('\n');
|
||||||
}
|
}
|
||||||
alert(errorMsg);
|
|
||||||
},
|
// Error SweetAlert
|
||||||
complete: function() {
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Validation Error',
|
||||||
|
text: errorMsg,
|
||||||
|
confirmButtonColor: '#dc3545'
|
||||||
|
});
|
||||||
|
|
||||||
$submitBtn.prop('disabled', false).text(originalBtnText);
|
$submitBtn.prop('disabled', false).text(originalBtnText);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// AJAX handler for Document Upload (Requires FormData for files)
|
// AJAX handler for Document Upload (FormData)
|
||||||
function submitFileForm(formId, modalId) {
|
$(document).on('submit', '#documentForm', function(e) {
|
||||||
$(formId).on('submit', function(e) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let formElement = $(this)[0];
|
|
||||||
|
let formElement = this;
|
||||||
|
let $form = $(this);
|
||||||
let formData = new FormData(formElement);
|
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();
|
let originalBtnText = $submitBtn.text();
|
||||||
|
|
||||||
$submitBtn.prop('disabled', true).text('Uploading...');
|
$submitBtn.prop('disabled', true).text('Uploading...');
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: $(this).attr('action'),
|
url: $form.attr('action'),
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: formData,
|
data: formData,
|
||||||
processData: false, // Essential for file uploads
|
processData: false,
|
||||||
contentType: false, // Essential for file uploads
|
contentType: false,
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
$(modalId).modal('hide');
|
$(modalId).modal('hide');
|
||||||
formElement.reset();
|
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) {
|
error: function(xhr) {
|
||||||
let errors = xhr.responseJSON?.errors;
|
let errors = xhr.responseJSON?.errors;
|
||||||
@@ -72,17 +101,119 @@ $(document).ready(function() {
|
|||||||
if (errors) {
|
if (errors) {
|
||||||
errorMsg = Object.values(errors).flat().join('\n');
|
errorMsg = Object.values(errors).flat().join('\n');
|
||||||
}
|
}
|
||||||
alert(errorMsg);
|
|
||||||
},
|
// Error SweetAlert
|
||||||
complete: function() {
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Upload Failed',
|
||||||
|
text: errorMsg,
|
||||||
|
confirmButtonColor: '#dc3545'
|
||||||
|
});
|
||||||
|
|
||||||
$submitBtn.prop('disabled', false).text(originalBtnText);
|
$submitBtn.prop('disabled', false).text(originalBtnText);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the handlers
|
// Initialize Select2 for Payment Services when modal opens
|
||||||
submitAjaxForm('#shortcodeForm', '#addShortcodeModal');
|
$('#addPaymentModal').on('shown.bs.modal', function () {
|
||||||
submitAjaxForm('#paymentForm', '#addPaymentModal');
|
let $select = $('#paymentServicesSelect');
|
||||||
submitFileForm('#documentForm', '#uploadDocumentModal');
|
|
||||||
|
// 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 = `
|
||||||
|
<div class="file-row card border bg-light p-3 mb-3 position-relative">
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label text-muted small fw-bold">Document Name / Title</label>
|
||||||
|
<input type="text" name="names[]" class="form-control" placeholder="e.g. Tax Clearance" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label text-muted small fw-bold">Select File</label>
|
||||||
|
<input type="file" name="files[]" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 text-end d-flex align-items-end">
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm remove-row-btn mt-4"><i class="bi bi-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
$('#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(`
|
||||||
|
<div class="file-row card border bg-light p-3 mb-3 position-relative">
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label text-muted small fw-bold">Document Name / Title</label>
|
||||||
|
<input type="text" name="names[]" class="form-control" placeholder="e.g. Signed Service Agreement" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label text-muted small fw-bold">Select File</label>
|
||||||
|
<input type="file" name="files[]" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 text-end d-flex align-items-end">
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm remove-row-btn mt-4" style="display: none;"><i class="bi bi-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
BIN
public/client_files/1786216218_6a777f1a7c8de.png
Normal file
BIN
public/client_files/1786216218_6a777f1a7c8de.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
public/client_files/1786216272_6a777f502565b.xlsx
Normal file
BIN
public/client_files/1786216272_6a777f502565b.xlsx
Normal file
Binary file not shown.
BIN
public/client_files/1786216327_6a777f873448e.jpg
Normal file
BIN
public/client_files/1786216327_6a777f873448e.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
public/client_files/1786216327_6a777f874525d.pdf
Normal file
BIN
public/client_files/1786216327_6a777f874525d.pdf
Normal file
Binary file not shown.
BIN
public/client_files/1786218008_6a7786189740e.xlsx
Normal file
BIN
public/client_files/1786218008_6a7786189740e.xlsx
Normal file
Binary file not shown.
@@ -89,6 +89,8 @@
|
|||||||
<option value="ussd">USSD</option>
|
<option value="ussd">USSD</option>
|
||||||
<option value="airtime">Airtime</option>
|
<option value="airtime">Airtime</option>
|
||||||
<option value="voice">Voice</option>
|
<option value="voice">Voice</option>
|
||||||
|
<option value="aggregatevoice">Aggregation Voice</option>
|
||||||
|
<option value="voice">Voice</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted small fw-bold">Code Type</label>
|
<label class="form-label text-muted small fw-bold">Code Type</label>
|
||||||
<select name="code_type" class="form-select" required>
|
<select name="code_type" class="form-select" required>
|
||||||
|
<option value="" selected disabled>-- Select --</option>
|
||||||
<option value="sms">SMS</option>
|
<option value="sms">SMS</option>
|
||||||
<option value="ussd">USSD</option>
|
<option value="ussd">USSD</option>
|
||||||
<option value="voice">Voice</option>
|
<option value="voice">Voice</option>
|
||||||
@@ -23,16 +24,44 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted small fw-bold">Short Code / Number</label>
|
<label class="form-label text-muted small fw-bold">Short Code / Number</label>
|
||||||
<input type="text" name="shortcode" class="form-control" placeholder="e.g. 1234 or *123#" required>
|
<input type="number" name="shortcode" class="form-control" placeholder="e.g. 5678" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted small fw-bold">Network</label>
|
<label class="form-label text-muted small fw-bold">Network</label>
|
||||||
<input type="text" name="network" class="form-control" placeholder="e.g. MTN, Vodafone, AirtelTigo">
|
<input type="text" name="network" class="form-control" placeholder="e.g. MTN, Vodafone, AirtelTigo">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">Monthly Fee</label>
|
||||||
|
<input type="text" name="monthly_fee" class="form-control" placeholder="">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">Toll Free</label>
|
||||||
|
<select name="toll_free" class="form-select" required>
|
||||||
|
<option value="" selected disabled>-- Select --</option>
|
||||||
|
<option value="yes">YES</option>
|
||||||
|
<option value="no">NO</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">Status</label>
|
||||||
|
<select name="status" class="form-select" required>
|
||||||
|
<option value="" selected disabled>-- Select --</option>
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="inactive">Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">Launch Date</label>
|
||||||
|
<input type="date" name="launch_date" class="form-control">
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted small fw-bold">Expiry Date</label>
|
<label class="form-label text-muted small fw-bold">Expiry Date</label>
|
||||||
<input type="date" name="expiry_date" class="form-control">
|
<input type="date" name="expiry_date" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">Remarks</label>
|
||||||
|
<textarea name="remarks" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer bg-light">
|
<div class="modal-footer bg-light">
|
||||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||||||
@@ -63,6 +92,13 @@
|
|||||||
<label class="form-label text-muted small fw-bold">Invoice / Reference Number</label>
|
<label class="form-label text-muted small fw-bold">Invoice / Reference Number</label>
|
||||||
<input type="text" name="invoice_number" class="form-control" placeholder="e.g. INV-2026-001" required>
|
<input type="text" name="invoice_number" class="form-control" placeholder="e.g. INV-2026-001" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small fw-bold">Services</label>
|
||||||
|
<select name="services[]" id="paymentServicesSelect" class="form-select" multiple="multiple" required>
|
||||||
|
<!-- Options will be loaded dynamically via AJAX -->
|
||||||
|
</select>
|
||||||
|
<div class="form-text text-muted">Select one or more services associated with this payment.</div>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted small fw-bold">Amount</label>
|
<label class="form-label text-muted small fw-bold">Amount</label>
|
||||||
<input type="number" step="0.01" name="invoice_amount" class="form-control" placeholder="0.00" required>
|
<input type="number" step="0.01" name="invoice_amount" class="form-control" placeholder="0.00" required>
|
||||||
@@ -91,35 +127,78 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- ========================================== -->
|
<!-- ========================================== -->
|
||||||
<!-- 3. UPLOAD DOCUMENT MODAL -->
|
<!-- 3. UPLOAD DOCUMENT MODAL (MULTI-FILE) -->
|
||||||
<!-- ========================================== -->
|
<!-- ========================================== -->
|
||||||
<div class="modal fade" id="uploadDocumentModal" tabindex="-1" aria-labelledby="uploadDocumentModalLabel" aria-hidden="true">
|
<div class="modal fade" id="uploadDocumentModal" tabindex="-1" aria-labelledby="uploadDocumentModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<form id="documentForm" action="{{ route('client-files.store') }}" method="POST" enctype="multipart/form-data">
|
<form id="documentForm" action="{{ route('client-files.store') }}" method="POST" enctype="multipart/form-data">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="client_id" value="{{ $showclient->id }}">
|
<input type="hidden" name="client_id" value="{{ $showclient->id }}">
|
||||||
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title fw-bold" id="uploadDocumentModalLabel"><i class="bi bi-file-earmark-arrow-up text-primary me-2"></i>Upload Client Document</h5>
|
<h5 class="modal-title fw-bold" id="uploadDocumentModalLabel"><i class="bi bi-file-earmark-arrow-up text-primary me-2"></i>Upload Client Documents</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label text-muted small fw-bold">Document Name / Title</label>
|
<div id="fileRowsContainer">
|
||||||
<input type="text" name="name" class="form-control" placeholder="e.g. Signed Service Agreement" required>
|
<!-- Initial Row -->
|
||||||
</div>
|
<div class="file-row card border bg-light p-3 mb-3 position-relative">
|
||||||
<div class="mb-3">
|
<div class="row g-2 align-items-center">
|
||||||
<label class="form-label text-muted small fw-bold">Select File</label>
|
<div class="col-md-5">
|
||||||
<input type="file" name="file" class="form-control" required>
|
<label class="form-label text-muted small fw-bold">Document Name / Title</label>
|
||||||
<div class="form-text text-muted">Accepted formats: PDF, DOC, DOCX, JPG, PNG (Max size depends on server config).</div>
|
<input type="text" name="names[]" class="form-control" placeholder="e.g. Signed Service Agreement" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label text-muted small fw-bold">Select File</label>
|
||||||
|
<input type="file" name="files[]" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 text-end d-flex align-items-end">
|
||||||
|
<!-- Delete button hidden for the first row -->
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm remove-row-btn mt-4" style="display: none;"><i class="bi bi-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add More Button -->
|
||||||
|
<button type="button" id="addRowBtn" class="btn btn-sm btn-outline-secondary fw-bold mt-1">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i> Add Another Document
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="form-text text-muted mt-2">Accepted formats: PDF, DOC, DOCX, JPG, PNG. You can upload multiple files at once.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer bg-light">
|
<div class="modal-footer bg-light">
|
||||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||||||
<button type="submit" class="btn btn-primary btn-sm px-4">Upload Document</button>
|
<button type="submit" class="btn btn-primary btn-sm px-4">Upload All Documents</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Client Note Modal -->
|
||||||
|
<div class="modal fade" id="addNoteModal" tabindex="-1" aria-labelledby="addNoteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form id="noteForm" action="{{ route('client-notes.store') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="client_id" value="{{ $showclient->id }}">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title fw-bold">Add Note</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<textarea name="note" class="form-control" rows="4" placeholder="Enter notes here..." required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm">Save Note</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -296,7 +296,12 @@
|
|||||||
<!-- Tab Links -->
|
<!-- Tab Links -->
|
||||||
<ul class="nav nav-tabs card-header-tabs m-0" id="clientRecordTabs" role="tablist">
|
<ul class="nav nav-tabs card-header-tabs m-0" id="clientRecordTabs" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active fw-bold text-dark" id="shortcodes-tab" data-bs-toggle="tab" data-bs-target="#shortcodes-pane" type="button" role="tab">
|
<button class="nav-link active fw-bold" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-pane" type="button">
|
||||||
|
<i class="bi bi-chat-square-text me-1"></i> Notes
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link fw-bold text-dark" id="shortcodes-tab" data-bs-toggle="tab" data-bs-target="#shortcodes-pane" type="button" role="tab">
|
||||||
<i class="bi bi-code-square text-primary me-1"></i> Short Codes
|
<i class="bi bi-code-square text-primary me-1"></i> Short Codes
|
||||||
<span class="badge bg-primary bg-opacity-10 text-primary ms-1">{{ count($voice_codes) + count($sms_codes) + count($ussd_codes) }}</span>
|
<span class="badge bg-primary bg-opacity-10 text-primary ms-1">{{ count($voice_codes) + count($sms_codes) + count($ussd_codes) }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -331,9 +336,38 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body bg-light tab-content" id="clientRecordTabsContent">
|
<div class="card-body bg-light tab-content" id="clientRecordTabsContent">
|
||||||
|
<!-- tab 0 Notes -->
|
||||||
|
<div class="tab-pane fade show active" id="notes-pane" role="tabpanel" tabindex="0">
|
||||||
|
<div class="d-flex justify-content-end mb-3">
|
||||||
|
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addNoteModal">
|
||||||
|
<i class="bi bi-plus-lg"></i> Add Note
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-group">
|
||||||
|
@forelse($show_notes as $note)
|
||||||
|
<div class="list-group-item p-3 mb-2 border rounded shadow-sm">
|
||||||
|
<p class="mb-2 text-dark">{{ $note->notes_body }}</p>
|
||||||
|
<div class="d-flex justify-content-between align-items-center text-muted" style="font-size: 0.8rem;">
|
||||||
|
<span>
|
||||||
|
<i class="bi bi-person-fill me-1 text-primary"></i>
|
||||||
|
<strong class="text-secondary">{{ $note->created_by_info->name ?? 'System User' }}</strong>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<i class="bi bi-clock me-1"></i>
|
||||||
|
{{ $note->created_at->format('d M Y, h:i A') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="text-center py-4 text-muted small">
|
||||||
|
<i class="bi bi-chat-square-text fs-4 d-block mb-1"></i> No notes added for this client yet.
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- TAB 1: SHORT CODES -->
|
<!-- TAB 1: SHORT CODES -->
|
||||||
<div class="tab-pane fade show active" id="shortcodes-pane" role="tabpanel" tabindex="0">
|
<div class="tab-pane fade " id="shortcodes-pane" role="tabpanel" tabindex="0">
|
||||||
@php
|
@php
|
||||||
$allCodes = collect($voice_codes)->concat($sms_codes)->concat($ussd_codes);
|
$allCodes = collect($voice_codes)->concat($sms_codes)->concat($ussd_codes);
|
||||||
@endphp
|
@endphp
|
||||||
@@ -343,22 +377,29 @@
|
|||||||
<table class="table table-hover align-middle mb-0 bg-white border rounded">
|
<table class="table table-hover align-middle mb-0 bg-white border rounded">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr style="font-size: 0.8rem;" class="text-secondary text-uppercase">
|
<tr style="font-size: 0.8rem;" class="text-secondary text-uppercase">
|
||||||
<th>Type</th>
|
<th style="width: 10%;">Type</th>
|
||||||
<th>Shortcode</th>
|
<th style="width: 15%;">Shortcode</th>
|
||||||
<th>Network</th>
|
<th style="width: 20%;">Network</th>
|
||||||
<th>Status</th>
|
<th style="width: 10%;">Status</th>
|
||||||
<th>Expiry Date</th>
|
<th style="width: 30%;">Remarks</th>
|
||||||
|
<th style="width: 15%;">Expiry Date</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach($allCodes as $code)
|
@foreach($allCodes as $code)
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge bg-secondary text-uppercase" style="font-size: 0.7rem;">{{ $code->code_type ?? 'N/A' }}</span></td>
|
<td><span class="badge bg-secondary text-uppercase" style="font-size: 0.7rem;">{{ $code->code_type ?? 'N/A' }}</span></td>
|
||||||
<td class="fw-bold text-dark font-monospace">{{ $code->shortcode ?? 'N/A' }}</td>
|
<td class="fw-bold text-dark font-monospace text-break">{{ $code->shortcode ?? 'N/A' }}</td>
|
||||||
<td>{{ $code->network ?? 'N/A' }}</td>
|
<td class="text-break">{{ $code->network ?? 'N/A' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-success bg-opacity-10 text-success">{{ $code->status ?? 'Active' }}</span>
|
<span class="badge bg-success bg-opacity-10 text-success">{{ $code->status ?? 'Active' }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
<!-- Styled Remarks Column with text-wrap and max-width bounds -->
|
||||||
|
<td>
|
||||||
|
<div class="text-muted text-break" style="font-size: 0.9rem; max-width: 250px; line-height: 1.3;">
|
||||||
|
{{ $code->remarks ?? '-' }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td class="text-muted small">{{ $code->expiry_date ? \Carbon\Carbon::parse($code->expiry_date)->format('d M Y') : 'N/A' }}</td>
|
<td class="text-muted small">{{ $code->expiry_date ? \Carbon\Carbon::parse($code->expiry_date)->format('d M Y') : 'N/A' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
@@ -380,6 +421,7 @@
|
|||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr style="font-size: 0.8rem;" class="text-secondary text-uppercase">
|
<tr style="font-size: 0.8rem;" class="text-secondary text-uppercase">
|
||||||
<th>Invoice #</th>
|
<th>Invoice #</th>
|
||||||
|
<th>Services</th>
|
||||||
<th>Amount</th>
|
<th>Amount</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
@@ -389,7 +431,23 @@
|
|||||||
@foreach($recent_payments as $payment)
|
@foreach($recent_payments as $payment)
|
||||||
<tr>
|
<tr>
|
||||||
<td class="fw-bold text-dark">{{ $payment->invoice_number ?? 'N/A' }}</td>
|
<td class="fw-bold text-dark">{{ $payment->invoice_number ?? 'N/A' }}</td>
|
||||||
<td class="fw-semibold text-success">{{ number_format($payment->invoice_amount ?? 0, 2) }}</td>
|
|
||||||
|
<td>
|
||||||
|
@php
|
||||||
|
$serviceNames = $payment->service_names;
|
||||||
|
@endphp
|
||||||
|
@if($serviceNames->count() > 0)
|
||||||
|
<div class="d-flex flex-wrap gap-1">
|
||||||
|
@foreach($serviceNames as $serviceName)
|
||||||
|
<span class="badge bg-light text-primary border" style="font-size: 0.75rem;">
|
||||||
|
{{ $serviceName }}
|
||||||
|
</span>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<span class="text-muted small">-</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
<td class="text-muted small">{{ $payment->invoice_date ? \Carbon\Carbon::parse($payment->invoice_date)->format('d M Y') : 'N/A' }}</td>
|
<td class="text-muted small">{{ $payment->invoice_date ? \Carbon\Carbon::parse($payment->invoice_date)->format('d M Y') : 'N/A' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-info bg-opacity-10 text-info text-uppercase">{{ $payment->invoice_status ?? 'Pending' }}</span>
|
<span class="badge bg-info bg-opacity-10 text-info text-uppercase">{{ $payment->invoice_status ?? 'Pending' }}</span>
|
||||||
@@ -421,7 +479,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if(!empty($file->file_path))
|
@if(!empty($file->file_path))
|
||||||
<a href="{{ asset('storage/client_files/' . $file->file_path) }}" target="_blank" class="btn btn-sm btn-light text-primary" title="Download File">
|
|
||||||
|
<a href="{{ route('client-files.download', $file->id) }}" class="btn btn-sm btn-light text-primary" target="_blank" title="{{ $file->name }}">
|
||||||
<i class="bi bi-download"></i>
|
<i class="bi bi-download"></i>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@@ -489,6 +548,8 @@
|
|||||||
</div>
|
</div>
|
||||||
@include('clients.partials.shortcode-payment-docs-modal')
|
@include('clients.partials.shortcode-payment-docs-modal')
|
||||||
@endsection
|
@endsection
|
||||||
@section('scripts')
|
@push('scripts')
|
||||||
<script src="{{ asset('js/client-show-modals.js') }}"></script>
|
<script src="{{ asset('public/assets/js/client-show-modal.js') }}"></script>
|
||||||
@endsection
|
<script>
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
@@ -10,7 +10,11 @@
|
|||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||||
|
<!-- Select2 CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body { background-color: #f8f9fc; }
|
body { background-color: #f8f9fc; }
|
||||||
|
|
||||||
@@ -55,7 +59,10 @@
|
|||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- SweetAlert2 CDN -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
<!-- Select2 JS (Place right after jQuery) -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
@@ -68,6 +75,7 @@
|
|||||||
}
|
}
|
||||||
updateClock();
|
updateClock();
|
||||||
setInterval(updateClock, 1000);
|
setInterval(updateClock, 1000);
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ Route::get('test', function () {
|
|||||||
return view('clients.index');
|
return view('clients.index');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//bd8f75cc-0f63-4707-9a2e-361800a0d94c
|
||||||
|
|
||||||
Auth::routes();
|
Auth::routes();
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function () {
|
Route::middleware(['auth'])->group(function () {
|
||||||
@@ -20,9 +22,16 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
// {{ route('shortcodes.store') }}
|
// {{ route('shortcodes.store') }}
|
||||||
// {{ route('client-payments.store') }}
|
// {{ route('client-payments.store') }}
|
||||||
// {{ route('client-files.store') }}
|
// {{ route('client-files.store') }}
|
||||||
Route::get('/clients/shortcodes-store', [App\Http\Controllers\ClientController::class, 'shortcodeStore'])->name('shortcodes.store');
|
// Route::post('/shortcodes', [App\Http\Controllers\ShortCodesController::class, 'store'])->name('shortcodes.store');
|
||||||
Route::get('/clients/payments-store', [App\Http\Controllers\ClientController::class, 'paymentsStore'])->name('client-payments.store');
|
Route::post('/clients/shortcodes-store', [App\Http\Controllers\ClientsController::class, 'shortcodeStore'])->name('shortcodes.store');
|
||||||
Route::get('/clients/files-store', [App\Http\Controllers\ClientController::class, 'storeFiles'])->name('client-files.store');
|
Route::post('/clients/payments-store', [App\Http\Controllers\ClientsController::class, 'paymentsStore'])->name('client-payments.store');
|
||||||
|
Route::post('/clients/files-store', [App\Http\Controllers\ClientsController::class, 'storeDocument'])->name('client-files.store');
|
||||||
|
|
||||||
|
Route::post('/clients/notes-store', [App\Http\Controllers\ClientsController::class, 'storeNote'])->name('client-notes.store');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/client-files/download/{id}', [App\Http\Controllers\ClientsController::class, 'clientFiledownload'])->name('client-files.download');
|
||||||
Route::resource('clients', App\Http\Controllers\ClientsController::class);
|
Route::resource('clients', App\Http\Controllers\ClientsController::class);
|
||||||
|
|
||||||
|
|
||||||
@@ -36,5 +45,9 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
Route::get('/senderids', [App\Http\Controllers\SenderidsController::class, 'index']);
|
Route::get('/senderids', [App\Http\Controllers\SenderidsController::class, 'index']);
|
||||||
Route::get('/shortcodes', [App\Http\Controllers\ShortCodesController::class, 'index']);
|
Route::get('/shortcodes', [App\Http\Controllers\ShortCodesController::class, 'index']);
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/api/services', [App\Http\Controllers\ServicesController::class, 'getServicesJson']);
|
||||||
|
|
||||||
|
|
||||||
Route::post('/logout', [App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
Route::post('/logout', [App\Http\Controllers\AuthController::class, 'logout'])->name('logout');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user