514 lines
23 KiB
JavaScript
514 lines
23 KiB
JavaScript
// 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('<span class="spinner-border spinner-border-sm me-2"></span>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(`
|
|
<div class="alert alert-success d-flex align-items-center" role="alert">
|
|
<i class="bi bi-check-circle-fill me-2"></i>
|
|
<div>${response.message || 'Client added successfully!'}</div>
|
|
</div>
|
|
`);
|
|
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(`
|
|
<div class="alert alert-danger d-flex align-items-center" role="alert">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
<div>Please fix the errors highlighted below.</div>
|
|
</div>
|
|
`);
|
|
$.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('<div class="invalid-feedback d-block fw-medium">' + value[0] + '</div>');
|
|
}
|
|
});
|
|
} else {
|
|
$alertBox.html(`
|
|
<div class="alert alert-danger d-flex align-items-center" role="alert">
|
|
<i class="bi bi-x-circle-fill me-2"></i>
|
|
<div>An unexpected server error occurred. Please try again.</div>
|
|
</div>
|
|
`);
|
|
}
|
|
},
|
|
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('<span class="spinner-border spinner-border-sm me-2"></span>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('<div class="invalid-feedback d-block fw-medium">' + value[0] + '</div>');
|
|
}
|
|
});
|
|
} 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(`
|
|
<tr>
|
|
<td colspan="7" class="text-center py-5">
|
|
<div class="spinner-border text-primary" role="status" style="color: #5c4df0 !important;"></div>
|
|
<div class="mt-2 text-secondary" style="font-size: 0.85rem;">Loading clients...</div>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
|
|
$.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('<tr><td colspan="7" class="text-center text-danger py-4">Failed to load data. Please try again.</td></tr>');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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('<tr><td colspan="7" class="text-center text-secondary py-4">No clients found matching your criteria.</td></tr>');
|
|
return;
|
|
}
|
|
|
|
clients.forEach(client => {
|
|
let initials = client.name.substring(0, 2).toUpperCase();
|
|
|
|
let servicesHtml = '<span class="text-secondary" style="font-size: 0.8rem;">N/A</span>';
|
|
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 =>
|
|
`<span class="badge bg-secondary bg-opacity-10 text-secondary border me-1 mb-1" style="font-size: 0.7rem;">${service}</span>`
|
|
).join('');
|
|
}
|
|
} catch (e) {
|
|
console.error("Could not parse services for client: " + client.name);
|
|
}
|
|
}
|
|
|
|
let statusBadge = client.status === 'Live' || client.status === 'active'
|
|
? '<span class="badge bg-success bg-opacity-10 text-success px-2 py-1"><i class="bi bi-check-circle me-1"></i>Active</span>'
|
|
: '<span class="badge bg-warning bg-opacity-10 text-warning px-2 py-1"><i class="bi bi-clock me-1"></i>' + client.status + '</span>';
|
|
|
|
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 = `
|
|
<button class="btn btn-sm btn-light text-primary btn-edit-client" data-id="${client.id}" title="Edit Client">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
html += `
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-circle bg-primary bg-opacity-10 text-primary me-3">${initials}</div>
|
|
<div>
|
|
<div class="fw-bold text-dark">${client.name}<div>
|
|
<div class="text-secondary" style="font-size: 0.8rem;"><i class="bi bi-geo-alt me-1"></i>${client.country || 'N/A'}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="fw-semibold">${client.contact_person || 'N/A'}</div>
|
|
<div class="text-secondary" style="font-size: 0.8rem;">${client.email || 'N/A'}</div>
|
|
</td>
|
|
<td>
|
|
<span class="text-dark fw-medium" style="font-size: 0.85rem;">
|
|
<i class="bi bi-person-badge text-secondary me-1"></i>${amName}
|
|
</span>
|
|
</td>
|
|
<td style="max-width: 200px; white-space: normal;">
|
|
${servicesHtml}
|
|
</td>
|
|
<td><span class="badge bg-info bg-opacity-10 text-info text-uppercase">${client.pay_mode || 'N/A'}</span></td>
|
|
<td><span class="fw-medium text-primary">${formattedDate}</span></td>
|
|
<td>${statusBadge}</td>
|
|
<td class="text-end">
|
|
<div class="d-flex gap-1 justify-content-end align-items-center">
|
|
<button class="btn btn-sm btn-light text-primary me-1 btn-view-client" data-id="${client.id}" title="View Details">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
${editButtonHtml}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
$('#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 += `<li class="page-item ${prevDisabled}"><a class="page-link page-link-ajax" href="#" data-page="${response.current_page - 1}">Previous</a></li>`;
|
|
|
|
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 += `<li class="page-item ${activeClass}"><a class="page-link page-link-ajax" href="#" ${style} data-page="${i}">${i}</a></li>`;
|
|
}
|
|
|
|
let nextDisabled = response.current_page === response.last_page ? 'disabled' : '';
|
|
paginationHtml += `<li class="page-item ${nextDisabled}"><a class="page-link page-link-ajax" href="#" data-page="${response.current_page + 1}">Next</a></li>`;
|
|
}
|
|
|
|
$('#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('<option value="">Select Country</option>');
|
|
|
|
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();
|
|
}); |