diff --git a/app/Http/Controllers/ClientsController.php b/app/Http/Controllers/ClientsController.php index 8ba6489..46fdaeb 100644 --- a/app/Http/Controllers/ClientsController.php +++ b/app/Http/Controllers/ClientsController.php @@ -134,6 +134,8 @@ class ClientsController extends Controller // dump($change_account_mgr_permission); $change_account_mgr_permission = $this->hasAnyAccess([$change_account_mgr_permission]) ? 'YES' : 'NO'; // dd($change_account_mgr_permission); + + // dd($showclient); return view('clients.show', [ 'page_title' => 'Client Profile', 'showclient' => $showclient, @@ -390,7 +392,39 @@ class ClientsController extends Controller return redirect('clients'); } + public function getClientJson($id) + { + $client = \App\Models\Client::findOrFail($id); + return response()->json($client); + } + public function update(Request $request, $id) + { + $client = \App\Models\Client::findOrFail($id); + + $client->update([ + 'name' => $request->name, + 'email' => $request->email, + 'phone' => $request->phone, + 'contact_person' => $request->contact_person, + 'company_type' => $request->company_type, + 'contract_type' => $request->contract_type, + 'industry' => $request->industry, + 'status' => $request->status, + 'currency' => $request->currency, + 'country' => $request->country, + // Arrays handled by model casts + 'services' => $request->services, + 'message_types' => $request->message_types, + 'connections' => $request->connections, + 'support_phones' => $request->support_phones, + 'support_emails' => $request->support_emails, + 'rate_emails' => $request->rate_emails, + 'support_skype' => $request->support_skype, + ]); + + return response()->json(['message' => 'Client updated successfully!']); + } public function storeFiles(AddFilesRequest $request){ diff --git a/app/Http/Controllers/NetworkOperatorsControlle.php b/app/Http/Controllers/NetworkOperatorsControlle.php deleted file mode 100644 index 5f29cc7..0000000 --- a/app/Http/Controllers/NetworkOperatorsControlle.php +++ /dev/null @@ -1,16 +0,0 @@ - 'Dashboard' - ]; - return view('network_operators.index', $data); - } -} diff --git a/app/Http/Controllers/NetworkOperatorsController.php b/app/Http/Controllers/NetworkOperatorsController.php new file mode 100644 index 0000000..7d6ee85 --- /dev/null +++ b/app/Http/Controllers/NetworkOperatorsController.php @@ -0,0 +1,115 @@ +filled('search')) { + $search = $request->input('search'); + $query->where('name', 'like', "%{$search}%") + ->orWhere('contact_person_email', 'like', "%{$search}%") + ->orWhere('support_phones', 'like', "%{$search}%"); + } + + if ($request->filled('country')) { + $query->where('country', $request->input('country')); + } + + if ($request->filled('connection_status')) { + $query->where('connection_status', 'like', "%{$request->input('connection_status')}%"); + } + + // Paginate instead of getting all records at once (e.g., 10 per page) + $operators = $query->latest('id')->paginate(10)->withQueryString(); + + // dd($operators); + + $totalActive = NetworkOperator::where('connection_status', 'Active')->count(); + $totalSipLinks = NetworkOperator::where('connection_type', 'like', '%VPN%')->count(); + $totalOperators = NetworkOperator::count(); + + return view('network_operators.index', compact('operators', 'totalActive', 'totalSipLinks', 'totalOperators')); + } + + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:200', + 'country' => 'required|string', + ]); + + NetworkOperator::create([ + 'name' => $request->name, + 'country' => $request->country, + 'connection_status' => $request->connection_status ?? 'Online', + 'contact_person' => $request->contact_person, + 'contact_person_phone' => $request->contact_person_phone, + 'contact_person_email' => $request->contact_person_email, + 'technical_support_person' => $request->technical_support_person, + 'support_skype' => $request->support_skype, + 'mno_account_manager' => $request->mno_account_manager, + 'services' => $request->services, + 'support_emails' => $request->support_emails, + 'finance_emails' => $request->finance_emails, + 'buying_rate' => $request->buying_rate ?? 0, + 'rate_type' => $request->rate_type, + 'payment_terms' => $request->payment_terms, + 'connection_type' => $request->connection_type, + ]); + + return response()->json(['message' => 'MNO Partner created successfully!']); + } + + public function update(Request $request, $id) + { + $operator = NetworkOperator::findOrFail($id); + + $request->validate([ + 'name' => 'required|string|max:200', + 'country' => 'required|string', + ]); + + $operator->update([ + 'name' => $request->name, + 'country' => $request->country, + 'connection_status' => $request->connection_status, + 'contact_person' => $request->contact_person, + 'contact_person_phone' => $request->contact_person_phone, + 'contact_person_email' => $request->contact_person_email, + 'technical_support_person' => $request->technical_support_person, + 'support_skype' => $request->support_skype, + 'mno_account_manager' => $request->mno_account_manager, + 'services' => $request->services, + 'support_emails' => $request->support_emails, + 'finance_emails' => $request->finance_emails, + 'buying_rate' => $request->buying_rate, + 'rate_type' => $request->rate_type, + 'payment_terms' => $request->payment_terms, + 'connection_type' => $request->connection_type, + ]); + + return response()->json(['message' => 'MNO Partner updated successfully!']); + } + + // Fetch single record JSON for editing modal + public function getJson($id) + { + $operator = NetworkOperator::findOrFail($id); + return response()->json($operator); + } + public function show($id) + { + $operator = NetworkOperator::findOrFail($id); + return view('network_operators.show', compact('operator')); + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/ProjectStatusesController.php b/app/Http/Controllers/ProjectStatusesController.php deleted file mode 100644 index 9d33b16..0000000 --- a/app/Http/Controllers/ProjectStatusesController.php +++ /dev/null @@ -1,69 +0,0 @@ -id; - // dd($user_id); - $result = Models\ProjectStatus::with('project') - ->where('assignee_id', $user_id) - ->orderBy('project_id', 'DESC') - ->get(); - $data = [ - 'page_title' => 'Projects Status List', - 'project_statuses' => $result - ]; - return view('project_status.index', $data); - } - public function add_status($id){ - $result = Models\Project::with('statusInfo')->where('id', $id)->firstOrFail(); - $data = [ - 'page_title' => 'Projects Status Update', - 'project' => $result - ]; - // dd($data); - return view('project_status.add_status', $data); - } - public function create() { - - return view('project_status.create'); - } - - public function store(Request $request) { - // Save a new post - $this->validate($request, [ - 'description' => 'required', - 'status' => 'required', - 'project_id' => 'required', - 'assignee_id' => 'required' - ]); - $project_status_arr = $request->except('_token'); - $result = Models\ProjectStatus::create($project_status_arr); - Session::flash('success_message', 'Project status added successfully!'); - - return redirect(url('projects')); - } - - public function show($id) { - // Show a specific post - } - - public function edit($id) { - // Show form to edit a post - } - - public function update(Request $request, $id) { - // Update a specific post - return redirect(url('project-status')); - } - - public function destroy($id) { - // Delete a specific post - } -} diff --git a/app/Http/Controllers/ProjectsController.php b/app/Http/Controllers/ProjectsController.php deleted file mode 100644 index 715b2e0..0000000 --- a/app/Http/Controllers/ProjectsController.php +++ /dev/null @@ -1,86 +0,0 @@ -id; - // dd($user_id); - $result = Models\Project::where('user_id', $user_id)->get(); - $data = [ - 'page_title' => 'Projects List', - 'projects' => $result - ]; - return view('projects.index', $data); - } - public function create() { - // Show form to create a new post - return view('projects.create'); - } - - public function store(Request $request) { - // Save a new post - $this->validate($request, [ - 'name' => 'required', - 'description'=> 'required', - 'dependancy' => 'sometimes', - 'status' => 'required', - ]); - $project_arr = $request->except('_token'); - $result = Models\Project::create($project_arr); - Session::flash('success_message', 'Project created successfully!'); - return redirect(url('projects')); - } - - public function show($id) { - // Show a specific post - $user_id = \Auth::user()->id; - $result = Models\Project::with('statusInfo')->where('id', $id)->firstOrFail(); - $data = [ - 'page_title' => 'Projects Details', - 'project' => $result - ]; - // dump($data); - return view('projects.show', $data); - } - - public function edit($id) { - // Show form to edit a post - $user_id = \Auth::user()->id; - $result = Models\Project::with('statusInfo')->where('id', $id)->firstOrFail(); - $data = [ - 'page_title' => 'Projects Update', - 'project' => $result - ]; - return view('projects.edit', $data); - } - - public function update(Request $request, $id) { - // Update a specific post - $this->validate($request, [ - 'name' => 'required', - 'description'=> 'required', - 'dependancy' => 'sometimes', - 'status' => 'required', - ]); - - $project = Models\Project::findOrFail($id); - - $project->name = $request->name; - $project->description = $request->description; - $project->dependancy = $request->dependancy; - $project->status = $request->status; - $result = $project->save(); - Session::flash('success_message', 'Project details updated successfully!'); - return redirect(url('projects')); - } - - public function destroy($id) { - // Delete a specific post - } -} diff --git a/app/Models/Client.php b/app/Models/Client.php index 3be8606..1b818a8 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -51,4 +51,14 @@ class Client extends Model } return $service_name_arr; } + + protected $casts = [ + 'connections' => 'array', + 'message_types' => 'array', + 'services' => 'array', + 'support_emails' => 'array', + 'rate_emails' => 'array', + 'support_skype' => 'array', + 'support_phones' => 'array', + ]; } diff --git a/app/Models/NetworkOps.php b/app/Models/NetworkOperator.php similarity index 78% rename from app/Models/NetworkOps.php rename to app/Models/NetworkOperator.php index 7ac1e5a..71901e8 100755 --- a/app/Models/NetworkOps.php +++ b/app/Models/NetworkOperator.php @@ -5,11 +5,17 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Casts\Attribute; -class NetworkOps extends Model +class NetworkOperator extends Model { protected $guarded = array('id'); public $table = "network_operators"; - + + protected $casts = [ + 'services' => 'array', + 'support_emails' => 'array', + 'finance_emails' => 'array', + 'bind_specifics' => 'array', + ]; public function account_manager_info(){ return $this->hasOne('App\Models\StaffMember', 'id', 'account_manager_id'); } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index fd52d92..9993f98 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\URL; +use Illuminate\Pagination\Paginator; class AppServiceProvider extends ServiceProvider { @@ -23,5 +24,6 @@ class AppServiceProvider extends ServiceProvider if (config('app.env') === 'production') { URL::forceScheme('https'); } + Paginator::useBootstrapFive(); } } diff --git a/public/assets/js/client-show-modal.js b/public/assets/js/client-show-modal.js index c81c614..490457f 100644 --- a/public/assets/js/client-show-modal.js +++ b/public/assets/js/client-show-modal.js @@ -249,5 +249,134 @@ document.addEventListener("DOMContentLoaded", function() { `); }); + + // 1. Populate Edit Client Modal when opened + $(document).on('click', '[data-bs-target="#editClientModal"]', function() { + let clientId = $(this).data('client-id'); + let $form = $('#editClientForm'); + + $form.attr('action', base_url + '/clients/' + clientId); + + // Fetch Services first, then Client data + $.ajax({ + url: base_url + '/api/services', + type: 'GET', + success: function(services) { + let $servicesSelect = $('#edit_services'); + $servicesSelect.empty(); + services.forEach(function(s) { + $servicesSelect.append(new Option(s.name, s.id)); + }); + + // Now fetch client details + $.ajax({ + url: base_url + '/clients/' + clientId + '/json', + type: 'GET', + success: function(client) { + $('#edit_name').val(client.name); + $('#edit_email').val(client.email); + $('#edit_phone').val(client.phone); + $('#edit_contact_person').val(client.contact_person); + $('#edit_company_type').val(client.company_type); + $('#edit_contract_type').val(client.contract_type); + $('#edit_industry').val(client.industry); + $('#edit_status').val(client.status); + $('#edit_currency').val(client.currency); + $('#edit_country').val(client.country); + + // Helper function to set values for Select2 multi-select tags fields + function setSelect2Values(selector, values) { + let $el = $(selector); + $el.val(null).trigger('change'); + if (values && Array.isArray(values)) { + values.forEach(function(val) { + if ($el.find("option[value='" + val + "']").length === 0) { + $el.append(new Option(val, val, true, true)); + } + }); + $el.val(values).trigger('change'); + } + } + + setSelect2Values('#edit_services', client.services); + setSelect2Values('#edit_message_types', client.message_types); + setSelect2Values('#edit_connections', client.connections); + setSelect2Values('#edit_support_phones', client.support_phones); + setSelect2Values('#edit_support_emails', client.support_emails); + setSelect2Values('#edit_rate_emails', client.rate_emails); + setSelect2Values('#edit_support_skype', client.support_skype); + } + }); + } + }); + }); + + // Initialize Select2 with tags option enabled for all multi-select fields inside edit modal + $('#editClientModal').on('shown.bs.modal', function () { + $('#editClientModal .select2-tags').each(function() { + if (!$(this).hasClass("select2-hidden-accessible")) { + $(this).select2({ + theme: 'bootstrap-5', + dropdownParent: $('#editClientModal'), + tags: true, // Allows typing custom entries for emails, phones, etc. + tokenSeparators: [',', ' '], + placeholder: 'Select or type and hit enter...' + }); + } + }); + }); + + // Clean up Select2 when modal closes + $('#editClientModal').on('hidden.bs.modal', function () { + $('#editClientModal .select2-tags').val(null).trigger('change'); + }); + + // Submit Edit Client Form via AJAX + $(document).on('submit', '#editClientForm', function(e) { + e.preventDefault(); // Hard stop on native form post + + let $form = $(this); + let $submitBtn = $form.find('button[type="submit"]'); + let originalBtnText = $submitBtn.text(); + + $submitBtn.prop('disabled', true).text('Updating...'); + + $.ajax({ + url: $form.attr('action'), + type: 'POST', // Spoofed as PUT via hidden input + data: $form.serialize(), + headers: { + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + }, + success: function(response) { + $('#editClientModal').modal('hide'); + + Swal.fire({ + icon: 'success', + title: 'Updated!', + text: response.message || 'Client updated successfully.', + confirmButtonColor: '#0d6efd' + }).then(() => { + location.reload(); + }); + }, + error: function(xhr) { + let errors = xhr.responseJSON?.errors; + let errorMsg = 'Something went wrong. Please check your inputs.'; + if (errors) { + errorMsg = Object.values(errors).flat().join('\n'); + } + + Swal.fire({ + icon: 'error', + title: 'Validation Error', + text: errorMsg, + confirmButtonColor: '#dc3545' + }); + + $submitBtn.prop('disabled', false).text(originalBtnText); + } + }); + }); }); }); \ No newline at end of file diff --git a/public/assets/js/mno_index.js b/public/assets/js/mno_index.js new file mode 100644 index 0000000..ef1e643 --- /dev/null +++ b/public/assets/js/mno_index.js @@ -0,0 +1,183 @@ +$(document).ready(function() { + const mnoModal = new bootstrap.Modal(document.getElementById('mnoModal')); + const $form = $('#mnoForm'); + + + function loadServicesAndOpen(callback) { + $.ajax({ + url: base_url + '/api/services', + type: 'GET', + success: function(services) { + let $servicesSelect = $('#mnoServices'); + $servicesSelect.empty(); + services.forEach(function(s) { + $servicesSelect.append(new Option(s.name, s.id)); + }); + if (typeof callback === 'function') callback(); + } + }); + } + + function setSelect2Values(selector, values) { + let $el = $(selector); + $el.val(null).trigger('change'); + if (values && Array.isArray(values)) { + values.forEach(function(val) { + if ($el.find("option[value='" + val + "']").length === 0) { + // If it's a free-form tag not in the base options, append it + $el.append(new Option(val, val, true, true)); + } + }); + $el.val(values).trigger('change'); + } + } + + // --- OPEN MODAL FOR CREATE --- + $('#btnOpenMnoModal').on('click', function(e) { + e.preventDefault(); // Prevent any default anchor/button jump + + $form[0].reset(); + $('#mnoId').val(''); + $('#methodField').html(''); + $form.attr('action', "{{ route('mnos.store') }}"); + $('#mnoModalTitle').text('Add New MNO'); + $('#btnSubmitMno').html('Save Gateway Profile'); + + // Clear all multi-selects first + $('#mnoServices, #mnoSupportEmails, #mnoFinanceEmails').val(null).trigger('change'); + + // Fetch services and then open the modal directly + $.ajax({ + url: base_url + '/api/services', + type: 'GET', + success: function(services) { + let $servicesSelect = $('#mnoServices'); + $servicesSelect.empty(); + services.forEach(function(s) { + $servicesSelect.append(new Option(s.name, s.id)); + }); + $servicesSelect.trigger('change'); + + // Open modal safely once data is ready + mnoModal.show(); + }, + error: function() { + // Fallback: open modal even if service fetch fails + mnoModal.show(); + } + }); + }); + + // --- OPEN MODAL FOR EDIT --- + $(document).on('click', '.btn-edit-mno', function() { + const mnoId = $(this).data('id'); + + $form[0].reset(); + $('#methodField').html(''); + $form.attr('action', base_url + '/mnos/' + mnoId); + $('#mnoModalTitle').text('Modify MNO Profile'); + $('#btnSubmitMno').html('Apply Changes'); + + loadServicesAndOpen(function() { + $.ajax({ + url: base_url + '/mnos/' + mnoId + '/json', + type: 'GET', + success: function(mno) { + $('#mnoId').val(mno.id); + $('#mnoName').val(mno.name); + $('#mnoCountry').val(mno.country); + $('#mnoConnectionStatus').val(mno.connection_status); + $('#mnoContactPerson').val(mno.contact_person); + $('#mnoContactPhone').val(mno.contact_person_phone); + $('#mnoContactEmail').val(mno.contact_person_email); + $('#mnoTechSupport').val(mno.technical_support_person); + $('#mnoSupportSkype').val(mno.support_skype); + $('#mnoAccountManager').val(mno.mno_account_manager); + $('#mnoBuyingRate').val(mno.buying_rate); + $('#mnoRateType').val(mno.rate_type); + $('#mnoPaymentTerms').val(mno.payment_terms); + $('#mnoConnectionType').val(mno.connection_type); + + // Populate Select2 fields with existing records + setSelect2Values('#mnoServices', mno.services); + setSelect2Values('#mnoSupportEmails', mno.support_emails); + setSelect2Values('#mnoFinanceEmails', mno.finance_emails); + + mnoModal.show(); + }, + error: function() { + Swal.fire('Error', 'Could not fetch MNO details.', 'error'); + } + }); + }); + }); + + // --- INITIALIZE SELECT2 TAGS ON MODAL SHOWN --- + $('#mnoModal').on('shown.bs.modal', function () { + // Initialize Database-driven services multi-select (no custom tags allowed) + if (!$('#mnoServices').hasClass("select2-hidden-accessible")) { + $('#mnoServices').select2({ + theme: 'bootstrap-5', + dropdownParent: $('#mnoModal'), + placeholder: 'Select services...' + }); + } + + // Initialize Email fields with custom tagging enabled (allows typing custom emails) + $('#mnoModal .select2-tags').each(function() { + if (!$(this).hasClass("select2-hidden-accessible")) { + $(this).select2({ + theme: 'bootstrap-5', + dropdownParent: $('#mnoModal'), + tags: true, + tokenSeparators: [',', ' '], + placeholder: 'Type email and press enter...' + }); + } + }); + }); + + // --- AJAX FORM SUBMISSION --- + $form.on('submit', function(e) { + e.preventDefault(); + const $btn = $('#btnSubmitMno'); + const originalText = $btn.html(); + + $btn.html(' Saving...'); + $btn.prop('disabled', true); + + $.ajax({ + url: $form.attr('action'), + type: 'POST', + data: $form.serialize(), + headers: { + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + }, + success: function(response) { + mnoModal.hide(); + Swal.fire({ + icon: 'success', + title: 'Success!', + text: response.message || 'Saved successfully.', + confirmButtonColor: '#5c4df0' + }).then(() => { + location.reload(); + }); + }, + error: function(xhr) { + let errors = xhr.responseJSON?.errors; + let errorMsg = 'Validation failed. Please check inputs.'; + if (errors) { + errorMsg = Object.values(errors).flat().join('\n'); + } + Swal.fire({ + icon: 'error', + title: 'Error', + text: errorMsg, + confirmButtonColor: '#dc3545' + }); + $btn.html(originalText).prop('disabled', false); + } + }); + }); +}); diff --git a/resources/views/clients/partials/edit-modal.blade.php b/resources/views/clients/partials/edit-modal.blade.php new file mode 100644 index 0000000..8df6364 --- /dev/null +++ b/resources/views/clients/partials/edit-modal.blade.php @@ -0,0 +1,103 @@ +
\ No newline at end of file diff --git a/resources/views/clients/show.blade.php b/resources/views/clients/show.blade.php index a56d799..fe84152 100644 --- a/resources/views/clients/show.blade.php +++ b/resources/views/clients/show.blade.php @@ -38,9 +38,8 @@Manage telecommunications carrier connections, signaling links, and gateways.
+Manage MNO details, connections and others
+ {{ $operator->country }} • + + {{ $operator->connection_status ?? 'Offline' }} + +
+