| @@ -348,52 +348,75 @@ | ||
| 348 | 348 | } |
| 349 | 349 | }); |
| 350 | 350 | } |
| 351 | 351 | |
| 352 | - // Add the new client to the table | |
| 353 | - var clientName = clientData.business_client_name || (clientData.first_name + ' ' + clientData.last_name); | |
| 354 | - if (clientName.trim() === '') { | |
| 355 | - clientName = clientData.username; | |
| 352 | + // Compose the two name columns separately — the table has | |
| 353 | + // Business Name and Client Name as DISTINCT columns, so we | |
| 354 | + // can't collapse them into one or the cells shift left. | |
| 355 | + var businessName = (clientData.business_client_name || '').trim() || '-'; | |
| 356 | + var contactName = ((clientData.first_name || '') + ' ' + (clientData.last_name || '')).trim(); | |
| 357 | + if (contactName === '') { | |
| 358 | + contactName = clientData.username || ''; | |
| 356 | 359 | } |
| 357 | - | |
| 358 | - // Create new table row | |
| 360 | + | |
| 361 | + // Use the role label the server returned with the create | |
| 362 | + // response. PHP renders the same row on the next refresh | |
| 363 | + // by reading $user->roles[0] (currently 'customer'); using | |
| 364 | + // the server-supplied label here keeps both states in sync | |
| 365 | + // so the badge doesn't flicker from "Client" -> "Customer" | |
| 366 | + // on first page reload. | |
| 367 | + var roleLabel = (response.data && response.data.role_label) | |
| 368 | + ? response.data.role_label | |
| 369 | + : 'Customer'; | |
| 370 | + | |
| 371 | + // Create new table row — MUST match the 8 columns in the | |
| 372 | + // <thead> exactly (id, business name, contact name, email, | |
| 373 | + // phone, username, role, actions). Skipping any of these | |
| 374 | + // makes every later cell shift one column to the left. | |
| 375 | + var esc = function(v){ return $('<div>').text(v == null ? '' : String(v)).html(); }; | |
| 359 | 376 | var newRow = ` |
| 360 | 377 | <tr> |
| 361 | 378 | <td class="px-6 py-4 whitespace-nowrap"> |
| 362 | - <div class="text-sm text-gray-500 font-mono font-semibold">${response.data.client_id}</div> | |
| 379 | + <div class="text-sm text-gray-500 font-mono font-semibold">${esc(response.data.client_id)}</div> | |
| 363 | 380 | </td> |
| 364 | 381 | <td class="px-6 py-4 whitespace-nowrap"> |
| 365 | - <div class="text-sm font-medium text-gray-900">${clientName}</div> | |
| 382 | + <div class="text-sm font-medium text-gray-900">${esc(businessName)}</div> | |
| 366 | 383 | </td> |
| 367 | 384 | <td class="px-6 py-4 whitespace-nowrap"> |
| 368 | - <div class="text-sm text-gray-500">${clientData.email}</div> | |
| 385 | + <div class="text-sm font-medium text-gray-900">${esc(contactName)}</div> | |
| 369 | 386 | </td> |
| 370 | 387 | <td class="px-6 py-4 whitespace-nowrap"> |
| 371 | - <div class="text-sm text-gray-500">${clientData.username}</div> | |
| 388 | + <div class="text-sm text-gray-500">${esc(clientData.email || '')}</div> | |
| 372 | 389 | </td> |
| 373 | 390 | <td class="px-6 py-4 whitespace-nowrap"> |
| 391 | + <div class="text-sm text-gray-500">${esc(clientData.phone || '')}</div> | |
| 392 | + </td> | |
| 393 | + <td class="px-6 py-4 whitespace-nowrap"> | |
| 394 | + <div class="text-sm text-gray-500">${esc(clientData.username || '')}</div> | |
| 395 | + </td> | |
| 396 | + <td class="px-6 py-4 whitespace-nowrap"> | |
| 374 | 397 | <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"> |
| 375 | 398 | <i class="fas fa-user mr-1"></i> |
| 376 | - Client | |
| 399 | + ${esc(roleLabel)} | |
| 377 | 400 | </span> |
| 378 | 401 | </td> |
| 379 | 402 | <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> |
| 380 | 403 | <div class="flex items-center space-x-2"> |
| 381 | - <a href="?page=easy-invoice-client-view&client_id=${response.data.client_id}" | |
| 404 | + <a href="?page=easy-invoice-client-view&client_id=${esc(response.data.client_id)}" | |
| 382 | 405 | class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200" |
| 383 | 406 | title="View Client"> |
| 384 | 407 | <i class="fas fa-eye mr-1"></i> |
| 385 | 408 | View |
| 386 | 409 | </a> |
| 387 | - <a href="?page=easy-invoice-client-edit&client_id=${response.data.client_id}" | |
| 410 | + <a href="?page=easy-invoice-client-edit&client_id=${esc(response.data.client_id)}" | |
| 388 | 411 | class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200" |
| 389 | 412 | title="Edit Client"> |
| 390 | 413 | <i class="fas fa-edit mr-1"></i> |
| 391 | 414 | Edit |
| 392 | 415 | </a> |
| 393 | - <button type="button" | |
| 416 | + <button type="button" | |
| 394 | 417 | class="delete-client inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200" |
| 395 | - data-id="${response.data.client_id}" | |
| 418 | + data-id="${esc(response.data.client_id)}" | |
| 396 | 419 | title="Delete Client"> |
| 397 | 420 | <i class="fas fa-trash mr-1"></i> |
| 398 | 421 | Delete |
| 399 | 422 | </button> |
| @@ -400,12 +423,14 @@ | ||
| 400 | 423 | </div> |
| 401 | 424 | </td> |
| 402 | 425 | </tr> |
| 403 | 426 | `; |
| 427 | + | |
| 428 | + // Remove "No clients found" placeholder row if it exists. | |
| 429 | + // The colspan must match what's emitted by the PHP empty | |
| 430 | + // state and the JS delete handler (currently 8). | |
| 431 | + $(".client-list-table tbody tr td[colspan='8']").closest("tr").remove(); | |
| 404 | 432 | |
| 405 | - // Remove "No clients found" row if it exists | |
| 406 | - $(".client-list-table tbody tr td[colspan='6']").closest("tr").remove(); | |
| 407 | - | |
| 408 | 433 | // Add new row at the top of the table (since we order by ID DESC) |
| 409 | 434 | $(".client-list-table tbody").prepend(newRow); |
| 410 | 435 | |
| 411 | 436 | // Update total clients count |
| @@ -411,15 +436,18 @@ | ||
| 411 | 436 | // Update total clients count |
| 412 | 437 | var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0; |
| 413 | 438 | $("h3:contains('Total Clients')").next().text(currentCount + 1); |
| 414 | 439 | |
| 415 | - // Show server's success message (don't add additional client-side message) | |
| 416 | - if (typeof showToast === 'function' && response.data.message) { | |
| 417 | - showToast(response.data.message, "success"); | |
| 440 | + // Show server's success message. The request sends | |
| 441 | + // suppress_global_toast=true (so the global ajaxSuccess | |
| 442 | + // handler in easy-invoice-toast.js doesn't auto-render a | |
| 443 | + // duplicate toast); we surface the message manually here. | |
| 444 | + if (typeof EasyInvoiceToast !== 'undefined' && response.data.message) { | |
| 445 | + EasyInvoiceToast.success(response.data.message); | |
| 418 | 446 | } |
| 419 | 447 | } else { |
| 420 | - if (typeof showToast === 'function') { | |
| 421 | - showToast(response.data.message || "Error adding client", "error"); | |
| 448 | + if (typeof EasyInvoiceToast !== 'undefined') { | |
| 449 | + EasyInvoiceToast.error((response.data && response.data.message) || "Error adding client"); | |
| 422 | 450 | } |
| 423 | 451 | } |
| 424 | 452 | }, |
| 425 | 453 | error: function(xhr, status, error) { |