// public/js/client-index.js $(document).ready(function() { $('#clientService').select2({ placeholder: "-- Select Services --", allowClear: true, dropdownParent: $('#createClientModal') }); // Initialize Currency for Create Modal $('#clientCurrency').select2({ theme: 'bootstrap-5', placeholder: "-- Select Currency --", allowClear: true, // Attach to the immediate column wrapper so it moves with the input dropdownParent: $('#clientCurrency').parent() }); $('#edit_currency').select2({ theme: 'bootstrap-5', placeholder: "-- Select Currency --", allowClear: true, // Attach to the immediate column wrapper so it moves with the input dropdownParent: $('#edit_currency').parent() }); let searchTimer; // Initialize the table on page load fetchClients(); // Event Listeners for Search and Filters $('#searchClient').on('keyup', function() { clearTimeout(searchTimer); searchTimer = setTimeout(() => fetchClients(1), 400); }); $('#filterService, #filterBilling').on('change', function() { fetchClients(1); }); $('#filterService, #filterStatus').on('change', function() { fetchClients(1); }); // Handle Pagination Clicks dynamically $(document).on('click', '.page-link-ajax', function(e) { e.preventDefault(); let page = $(this).data('page'); if (page) fetchClients(page); }); $('#btnOpenClientModal').on('click', function(e) { e.preventDefault(); $('#createClientModal').modal('show'); }); $(document).on('click', '.btn-view-client', function(e) { e.preventDefault(); let clientId = $(this).data('id'); window.location.href = base_url + "/clients/" + clientId; }); // --------------------------------------------------------- // EDIT CLIENT MODAL TRIGGER & POPULATION // --------------------------------------------------------- $(document).on('click', '.btn-edit-client', function(e) { e.preventDefault(); let clientId = $(this).data('id'); let $form = $('#editClientForm'); // Set dynamic action URL for update $form.attr('action', base_url + '/clients/' + clientId); // Fetch Services first, then Client data to populate the edit modal $.ajax({ url: base_url + '/api/services', type: 'GET', success: function(services) { let $servicesSelect = $('#edit_services'); $servicesSelect.empty(); if (services && Array.isArray(services)) { services.forEach(function(s) { $servicesSelect.append(new Option(s.name, s.id)); }); } // Fetch client details JSON $.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_currency').val(client.currency).trigger('change'); $('#edit_account_manager').val(client.auth_user_id).trigger('change'); // $('#edit_country').val(client.country); loadCountries(client.country); // Helper for multi-select Select2 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'); } } // Parse services if stored as JSON string let clientServices = client.services; if (typeof clientServices === 'string') { try { clientServices = JSON.parse(clientServices); } catch(err) {} } setSelect2Values('#edit_services', clientServices); 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); // Show the modal after populating $('#editClientModal select[multiple]').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...' }); } }); $('#editClientModal').modal('show'); }, error: function() { Swal.fire('Error', 'Could not fetch client details.', 'error'); } }); } }); }); $('#createClientModal, #editClientModal').on('hidden.bs.modal', function () { let $form = $(this).find('form'); if ($form.length) { $form[0].reset(); // Reset any select elements or select2 tags back to blank $form.find('select').val(null).trigger('change'); } }); // --------------------------------------------------------- // AJAX FORM SUBMISSION (Create Client) // --------------------------------------------------------- $('#createClientForm').on('submit', function(e) { e.preventDefault(); let $form = $(this); let $submitBtn = $('#btnSubmitClient'); let originalText = $submitBtn.html(); let $alertBox = $('#clientModalAlert'); $submitBtn.html('Saving...').prop('disabled', true); $alertBox.html(''); $form.find('.is-invalid').removeClass('is-invalid'); $form.find('.invalid-feedback').remove(); $.ajax({ url: $form.attr('action'), method: 'POST', data: $form.serialize(), success: function(response) { if(response.success) { $alertBox.html(` `); fetchClients(1); setTimeout(() => { $('#createClientModal').modal('hide'); $form[0].reset(); $alertBox.html(''); }, 2000); } }, error: function(xhr) { if (xhr.status === 401) { window.location.href = base_url + '/login'; } if (xhr.status === 422) { let errors = xhr.responseJSON.errors; $alertBox.html(` `); $.each(errors, function(key, value) { let fieldName = key; if(key === 'services') fieldName = 'services[]'; let $input = $form.find('[name="' + fieldName + '"]'); if ($input.length) { $input.addClass('is-invalid'); $input.parent().append('
' + value[0] + '
'); } }); } else { $alertBox.html(` `); } }, complete: function() { $submitBtn.html(originalText).prop('disabled', false); } }); }); // --------------------------------------------------------- // AJAX FORM SUBMISSION (Edit Client) // --------------------------------------------------------- $('#editClientForm').on('submit', function(e) { e.preventDefault(); let $form = $(this); let $submitBtn = $form.find('button[type="submit"]'); let originalText = $submitBtn.html(); $submitBtn.html('Updating...').prop('disabled', true); // Clear previous validation errors $form.find('.is-invalid').removeClass('is-invalid'); $form.find('.invalid-feedback').remove(); $.ajax({ url: $form.attr('action'), method: 'POST', // @method('PUT') is already included in the serialized data data: $form.serialize(), success: function(response) { $('#editClientModal').modal('hide'); Swal.fire({ icon: 'success', title: 'Updated!', text: response.message || 'Client updated successfully!', showConfirmButton: false, timer: 1500 }); // Refresh the table data fetchClients(1); }, error: function(xhr) { if (xhr.status === 401) { window.location.href = base_url + '/login'; } else if (xhr.status === 422) { let errors = xhr.responseJSON.errors; $.each(errors, function(key, value) { // Map validation keys to actual input names if they are arrays let fieldName = key; let arrayFields = ['services', 'message_types', 'connections', 'support_phones', 'support_emails', 'rate_emails', 'support_skype']; if (arrayFields.includes(key)) { fieldName = key + '[]'; } let $input = $form.find('[name="' + fieldName + '"]'); if ($input.length) { // If Select2, target the container for the red border if ($input.hasClass('select2-hidden-accessible')) { $input.next('.select2-container').addClass('is-invalid'); } else { $input.addClass('is-invalid'); } $input.parent().append('
' + value[0] + '
'); } }); } else { Swal.fire({ icon: 'error', title: 'Server Error', text: 'An unexpected error occurred. Please try again.', confirmButtonColor: '#5c4df0' }); } }, complete: function() { $submitBtn.html(originalText).prop('disabled', false); } }); }); $('#createClientModal').on('hidden.bs.modal', function () { $('#createClientForm')[0].reset(); $('#clientService').val(null).trigger('change'); $('#clientModalAlert').html(''); $('#createClientForm').find('.is-invalid').removeClass('is-invalid'); $('#createClientForm').find('.invalid-feedback').remove(); }); // AJAX Fetch Function function fetchClients(page = 1) { const search = $('#searchClient').val(); const service = $('#filterService').val(); const billing = $('#filterBilling').val(); const status = $('#filterStatus').val(); $('#clientTableBody').html(`
Loading clients...
`); $.ajax({ url: base_url + "/clients/data", type: "GET", data: { page: page, search: search, service: service, billing: billing, status: status }, success: function(response) { renderTable(response.data); renderPagination(response); }, error: function() { $('#clientTableBody').html('Failed to load data. Please try again.'); } }); } // Handle Export Actions $('.btn-export').on('click', function(e) { e.preventDefault(); const search = $('#searchClient').val(); const service = $('#filterService').val(); const billing = $('#filterBilling').val(); const status = $('#filterStatus').val(); const format = $(this).data('format'); const queryParams = $.param({ search: search, service: service, billing: billing, status: status, format: format }); window.location.href = base_url + "/clients/export?" + queryParams; }); // Render Table Rows // Render Table Rows function renderTable(clients) { let html = ''; if (clients.length === 0) { $('#clientTableBody').html('No clients found matching your criteria.'); return; } clients.forEach(client => { let initials = client.name.substring(0, 2).toUpperCase(); let servicesHtml = 'N/A'; if (client.services) { try { let servicesArray = typeof client.services === 'string' ? JSON.parse(client.services) : client.services; if (Array.isArray(servicesArray) && servicesArray.length > 0) { servicesHtml = servicesArray.map(service => `${service}` ).join(''); } } catch (e) { console.error("Could not parse services for client: " + client.name); } } let statusBadge = client.status === 'Live' || client.status === 'active' ? 'Active' : '' + client.status + ''; let formattedDate = 'N/A'; if (client.created_at) { const dateObj = new Date(client.created_at); formattedDate = dateObj.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }); } // Extract Account Manager name safely let amName = 'Unassigned'; if (client.account_manager && client.account_manager.name) { amName = client.account_manager.name; } // CONDITIONAL EDIT BUTTON LOGIC let editButtonHtml = ''; if (typeof currentUserId !== 'undefined' && client.auth_user_id == currentUserId) { editButtonHtml = ` `; } html += `
${initials}
${client.name}
${client.country || 'N/A'}
${client.contact_person || 'N/A'}
${client.email || 'N/A'}
${amName} ${servicesHtml} ${client.pay_mode || 'N/A'} ${formattedDate} ${statusBadge}
${editButtonHtml}
`; }); $('#clientTableBody').html(html); } // Render Pagination Links function renderPagination(response) { $('#paginationInfo').text(`Showing ${response.from || 0} to ${response.to || 0} of ${response.total} entries`); let paginationHtml = ''; if (response.last_page > 1) { let prevDisabled = response.current_page === 1 ? 'disabled' : ''; paginationHtml += `
  • Previous
  • `; for (let i = 1; i <= response.last_page; i++) { let activeClass = response.current_page === i ? 'active' : ''; let style = response.current_page === i ? 'style="background-color: #5c4df0; border-color: #5c4df0;"' : 'class="page-link text-dark"'; paginationHtml += `
  • ${i}
  • `; } let nextDisabled = response.current_page === response.last_page ? 'disabled' : ''; paginationHtml += `
  • Next
  • `; } $('#paginationLinks').html(paginationHtml); } // Helper function to populate country dropdowns dynamically function loadCountries(selectedCountry = '') { $.ajax({ url: base_url + '/api/countries', type: 'GET', success: function(countries) { // Target both create and edit country dropdowns if they exist let $selectors = $('#create_country, #edit_country'); $selectors.each(function() { let $select = $(this); let currentVal = $select.val() || selectedCountry; $select.empty().append(''); if (countries && Array.isArray(countries)) { countries.forEach(function(c) { $select.append(new Option(c.en_short_name, c.en_short_name)); }); } if (currentVal) { $select.val(currentVal); } }); } }); } // Call loadCountries on page load so it's ready for the create modal loadCountries(); });