PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.2
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / assets / js / client-manager.js

client-manager.js in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.1.2, at assets/js/client-manager.js

609 lines 30.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Easy Invoice - Client Management
3 * Handles all client-related functionality including selection, adding new clients,
4 * and managing client data.
5 */
6
7 (function($) {
8 $(document).ready(function() {
9 // Store client data in a JavaScript object
10 var clientsData = {};
11
12 // Load clients data from the global object if available
13 function initializeClientData() {
14 if (typeof window.easyInvoiceClients !== 'undefined') {
15 clientsData = window.easyInvoiceClients;
16 }
17
18 // Make clientsData available globally
19 window.easyInvoiceClients = clientsData;
20 }
21
22 // Initialize client data
23 initializeClientData();
24
25 // Handle client selection change
26 function setupClientSelection() {
27 var isInitialLoad = true; // Flag to prevent AJAX calls on initial load
28
29 $("#select-client").on("change", function() {
30 var clientId = $(this).val();
31
32 // Skip AJAX call if this is the initial load or if we're initializing client
33 if (isInitialLoad || (typeof window.isInitializingClient !== 'undefined' && window.isInitializingClient)) {
34 isInitialLoad = false;
35 return;
36 }
37
38 if (clientId) {
39 // Load client data via AJAX
40 loadClientData(clientId);
41 } else {
42 // Clear client display
43 clearClientDisplay();
44 }
45 });
46
47 // Auto-select client if client-id field has a value (for existing invoices)
48 var existingClientId = $("#client-id").val();
49 if (existingClientId && existingClientId !== '') {
50 // Set the dropdown value without triggering change event
51 $("#select-client").val(existingClientId);
52
53 // Check if we have client data from PHP first
54 if (typeof easyInvoice !== 'undefined' && easyInvoice.clientData && easyInvoice.clientData.id == existingClientId) {
55 loadClientDataFromPHP(easyInvoice.clientData);
56 }
57 }
58
59 // Reset the flag after a short delay to allow for user interactions
60 setTimeout(function() {
61 isInitialLoad = false;
62 }, 1000);
63 }
64
65 // Load client data from PHP (no AJAX needed)
66 function loadClientDataFromPHP(clientData) {
67 // Set client ID
68 $("#client-id").val(clientData.id);
69
70 // Update display fields
71 $("#display-client-name").text(clientData.business_client_name || (clientData.first_name + ' ' + clientData.last_name) || 'N/A');
72 $("#display-client-email").text(clientData.email || 'N/A');
73 $("#display-client-phone").text(clientData.phone || 'N/A');
74 $("#display-client-company").text(clientData.business_client_name || 'N/A');
75 $("#display-client-address").text(clientData.address || 'N/A');
76 $("#display-client-website").text(clientData.website || 'N/A');
77
78 // Show client info display and hide no-client message
79 $("#client-info-display").removeClass("hidden");
80 $("#no-client-message").addClass("hidden");
81
82 // Update the new selected client display
83 var clientName = clientData.business_client_name || (clientData.first_name + ' ' + clientData.last_name) || 'N/A';
84 $("#selected-client-name").text(clientName);
85 $("#selected-client-email").text(clientData.email || 'N/A');
86 $("#selected-client-display").removeClass("hidden");
87
88 // Update the edit client link
89 var editUrl = 'admin.php?page=easy-invoice-client-edit&client_id=' + clientData.id;
90 $('#edit-selected-client').attr('href', editUrl).removeClass('hidden');
91
92 // Store client data for later use
93 clientsData[clientData.id] = {
94 name: clientData.business_client_name || (clientData.first_name + ' ' + clientData.last_name),
95 email: clientData.email,
96 phone: clientData.phone,
97 address: clientData.address,
98 website: clientData.website,
99 business_name: clientData.business_client_name,
100 first_name: clientData.first_name,
101 last_name: clientData.last_name
102 };
103 }
104
105 // Load client data via AJAX
106 function loadClientData(clientId) {
107 $.ajax({
108 url: easyInvoice.ajaxUrl,
109 type: 'POST',
110 data: {
111 action: 'easy_invoice_get_client',
112 client_id: clientId,
113 nonce: easyInvoice.nonce
114 },
115 success: function(response) {
116 if (response.success) {
117 var client = response.data;
118
119 // Set client ID
120 $("#client-id").val(clientId);
121
122 // Update display fields
123 $("#display-client-name").text(client.business_client_name || (client.first_name + ' ' + client.last_name) || 'N/A');
124 $("#display-client-email").text(client.email || 'N/A');
125 $("#display-client-phone").text(client.phone || 'N/A');
126 $("#display-client-company").text(client.business_client_name || 'N/A');
127 $("#display-client-address").text(client.address || 'N/A');
128 $("#display-client-website").text(client.website || 'N/A');
129
130 // Show client info display and hide no-client message
131 $("#client-info-display").removeClass("hidden");
132 $("#no-client-message").addClass("hidden");
133
134 // Update the new selected client display
135 var clientName = client.business_client_name || (client.first_name + ' ' + client.last_name) || 'N/A';
136 $("#selected-client-name").text(clientName);
137 $("#selected-client-email").text(client.email || 'N/A');
138 $("#selected-client-display").removeClass("hidden");
139
140 // Update the edit client link
141 var editUrl = 'admin.php?page=easy-invoice-client-edit&client_id=' + clientId;
142 $('#edit-selected-client').attr('href', editUrl).removeClass('hidden');
143
144 // Store client data for later use
145 clientsData[clientId] = {
146 name: client.business_client_name || (client.first_name + ' ' + client.last_name),
147 email: client.email,
148 phone: client.phone,
149 address: client.address,
150 website: client.website,
151 business_name: client.business_client_name,
152 first_name: client.first_name,
153 last_name: client.last_name
154 };
155 } else {
156 clearClientDisplay();
157 }
158 },
159 error: function(xhr, status, error) {
160 clearClientDisplay();
161 }
162 });
163 }
164
165 // Clear client display
166 function clearClientDisplay() {
167 $("#client-id").val("");
168 $("#display-client-name").text("-");
169 $("#display-client-email").text("-");
170 $("#display-client-phone").text("-");
171 $("#display-client-company").text("-");
172 $("#display-client-address").text("-");
173 $("#display-client-website").text("-");
174
175 $("#client-info-display").addClass("hidden");
176 $("#no-client-message").removeClass("hidden");
177 $("#selected-client-display").addClass("hidden");
178 $("#edit-selected-client").addClass("hidden");
179 }
180
181 // Clean up existing handlers to prevent duplicates
182 function cleanUpExistingHandlers() {
183 $("#add-new-client-btn").off("click");
184 $(document).off("click", "#add-new-client-btn");
185 $(".close-modal").off("click");
186 $(document).off("click", ".close-modal");
187 $(document).off("click", "#add-save-client-btn");
188 $(document).off("click", "#add-cancel-add-client");
189 }
190
191 // Setup client modal functionality
192 function setupClientModal() {
193 // Clean up existing handlers first
194 cleanUpExistingHandlers();
195
196 // Simple function to toggle modal visibility
197 function toggleClientModal(show) {
198 if (show) {
199 $("#add_client_modal").removeClass("hidden");
200 } else {
201 $("#add_client_modal").addClass("hidden");
202
203 // Reset form when closing
204 $("#add-client-form")[0].reset();
205 }
206 }
207
208 // Simple button click handler
209 $(document).on("click", "#add-new-client-btn", function(e) {
210 e.preventDefault();
211 e.stopPropagation();
212
213 // Simple modal show
214 $("#add_client_modal").removeClass("hidden");
215
216 return false;
217 });
218
219 // Use event delegation for close buttons
220 $(document).on("click", ".close-modal", function(e) {
221 e.preventDefault();
222 e.stopPropagation();
223 toggleClientModal(false);
224 return false;
225 });
226
227 // Handle cancel button
228 $(document).on("click", "#add-cancel-add-client", function(e) {
229 e.preventDefault();
230 e.stopPropagation();
231 toggleClientModal(false);
232 return false;
233 });
234
235 // Close when clicking on the background
236 $(document).on("click", "#add_client_modal", function(e) {
237 if (e.target === this) {
238 toggleClientModal(false);
239 }
240 });
241
242 // Handle client form submission via button click
243 $(document).on("click", "#add-save-client-btn", function(e) {
244 e.preventDefault();
245 e.stopPropagation(); // Prevent event bubbling to parent form
246
247 // Check if we can find the form fields directly (since form tag might not be rendering)
248 var $businessNameField = $("#add-client-business-name");
249 var $emailField = $("#add-client-email");
250 var $usernameField = $("#add-client-username");
251 var $passwordField = $("#add-client-password");
252
253 if ($businessNameField.length === 0 || $emailField.length === 0 || $usernameField.length === 0 || $passwordField.length === 0) {
254 if (typeof EasyInvoiceToast !== 'undefined') {
255 EasyInvoiceToast.error("Client form fields not found! Please refresh the page and try again.");
256 } else {
257 console.error("Client form fields not found! Please refresh the page and try again.");
258 }
259 return;
260 }
261
262 // Check if easyInvoice object exists
263 if (typeof easyInvoice === 'undefined') {
264 if (typeof EasyInvoiceToast !== 'undefined') {
265 EasyInvoiceToast.error("Error: easyInvoice object is not defined. Please refresh the page.");
266 } else {
267 console.error("Error: easyInvoice object is not defined. Please refresh the page.");
268 }
269 return;
270 }
271
272 // Check if required fields are filled
273 var businessName = $businessNameField.val();
274 var email = $emailField.val();
275 var username = $usernameField.val();
276 var password = $passwordField.val();
277
278 // Validate required fields
279 if (!businessName || !email || !username ) {
280 if (typeof EasyInvoiceToast !== 'undefined') {
281 EasyInvoiceToast.error("Please fill in all required fields (Business Name, Email, Username, Password)");
282 } else {
283 console.error("Please fill in all required fields (Business Name, Email, Username, Password)");
284 }
285 return;
286 }
287
288 // Get form data using new field names - ensure no conflicts with main form
289 var clientData = {
290 business_client_name: businessName,
291 email: email,
292 username: username,
293 password: password,
294 address: $("#add-client-address").val(),
295 phone: $("#add-client-phone").val(),
296 first_name: $("#add-client-first-name").val(),
297 last_name: $("#add-client-last-name").val(),
298 website: $("#add-client-website").val(),
299 action: "easy_invoice_add_client",
300 nonce: easyInvoice.nonce,
301 suppress_global_toast: true, // Prevent global toasts
302 is_client_form: true // Flag to identify this is client form data
303 };
304
305 // Show loading state
306 var submitBtn = $(this);
307 var originalText = submitBtn.text();
308 submitBtn.prop("disabled", true).html("<i class=\"fas fa-spinner fa-spin mr-2\"></i> Saving...");
309
310 // Send AJAX request
311 $.ajax({
312 url: easyInvoice.ajaxUrl,
313 type: "POST",
314 data: clientData,
315 timeout: 10000, // 10 second timeout
316 success: function(response) {
317 // Always reset the button and clear the form
318 submitBtn.prop("disabled", false).html(originalText);
319 $("#add-client-business-name, #add-client-email, #add-client-username, #add-client-password, #add-client-first-name, #add-client-last-name, #add-client-address, #add-client-phone, #add-client-website").val("");
320
321 if (response.success) {
322 // Close modal
323 $("#add_client_modal").addClass("hidden");
324
325 // If we're in an invoice or quote builder, select the new client
326 if (typeof window.selectClientFromOption === 'function') {
327 // Create client data object for selection
328 var newClientData = {
329 name: clientData.business_client_name || (clientData.first_name + ' ' + clientData.last_name),
330 email: clientData.email,
331 company: clientData.business_client_name,
332 phone: clientData.phone,
333 website: clientData.website,
334 address: clientData.address
335 };
336
337 // Select the new client in the main form
338 window.selectClientFromOption({
339 getAttribute: function(attr) {
340 if (attr === 'data-client-id') return response.data.client_id;
341 if (attr === 'data-client-name') return newClientData.name;
342 if (attr === 'data-client-email') return newClientData.email;
343 if (attr === 'data-client-company') return newClientData.company;
344 if (attr === 'data-client-phone') return newClientData.phone;
345 if (attr === 'data-client-website') return newClientData.website;
346 if (attr === 'data-client-address') return newClientData.address;
347 return '';
348 }
349 });
350 }
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;
356 }
357
358 // Create new table row
359 var newRow = `
360 <tr>
361 <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>
363 </td>
364 <td class="px-6 py-4 whitespace-nowrap">
365 <div class="text-sm font-medium text-gray-900">${clientName}</div>
366 </td>
367 <td class="px-6 py-4 whitespace-nowrap">
368 <div class="text-sm text-gray-500">${clientData.email}</div>
369 </td>
370 <td class="px-6 py-4 whitespace-nowrap">
371 <div class="text-sm text-gray-500">${clientData.username}</div>
372 </td>
373 <td class="px-6 py-4 whitespace-nowrap">
374 <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 <i class="fas fa-user mr-1"></i>
376 Client
377 </span>
378 </td>
379 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
380 <div class="flex items-center space-x-2">
381 <a href="?page=easy-invoice-client-view&client_id=${response.data.client_id}"
382 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 title="View Client">
384 <i class="fas fa-eye mr-1"></i>
385 View
386 </a>
387 <a href="?page=easy-invoice-client-edit&client_id=${response.data.client_id}"
388 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 title="Edit Client">
390 <i class="fas fa-edit mr-1"></i>
391 Edit
392 </a>
393 <button type="button"
394 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}"
396 title="Delete Client">
397 <i class="fas fa-trash mr-1"></i>
398 Delete
399 </button>
400 </div>
401 </td>
402 </tr>
403 `;
404
405 // Remove "No clients found" row if it exists
406 $(".client-list-table tbody tr td[colspan='6']").closest("tr").remove();
407
408 // Add new row at the top of the table (since we order by ID DESC)
409 $(".client-list-table tbody").prepend(newRow);
410
411 // Update total clients count
412 var currentCount = parseInt($("h3:contains('Total Clients')").next().text()) || 0;
413 $("h3:contains('Total Clients')").next().text(currentCount + 1);
414
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");
418 }
419 } else {
420 if (typeof showToast === 'function') {
421 showToast(response.data.message || "Error adding client", "error");
422 }
423 }
424 },
425 error: function(xhr, status, error) {
426 submitBtn.prop("disabled", false).html(originalText);
427 if (typeof EasyInvoiceToast !== 'undefined') {
428 EasyInvoiceToast.error("Error connecting to server. Please try again.");
429 } else if (typeof showToast === 'function') {
430 showToast("Error connecting to server. Please try again.", "error");
431 } else {
432 console.error("Error connecting to server. Please try again.");
433 }
434 },
435 timeout: function() {
436 submitBtn.prop("disabled", false).html(originalText);
437 if (typeof EasyInvoiceToast !== 'undefined') {
438 EasyInvoiceToast.error("Request timed out. Please try again.");
439 } else if (typeof showToast === 'function') {
440 showToast("Request timed out. Please try again.", "error");
441 } else {
442 console.error("Request timed out. Please try again.");
443 }
444 }
445 });
446 });
447 }
448
449 // Setup password field functionality
450 function setupPasswordFields() {
451 // Show/hide password functionality for add client modal
452 $(document).on('click', '#show-password', function() {
453 const passwordInput = $('#client-password');
454 const icon = $(this).find('i');
455 if (passwordInput.attr('type') === 'password') {
456 passwordInput.attr('type', 'text');
457 icon.removeClass('fa-eye').addClass('fa-eye-slash');
458 } else {
459 passwordInput.attr('type', 'password');
460 icon.removeClass('fa-eye-slash').addClass('fa-eye');
461 }
462 });
463
464 // Show/hide password functionality for edit client page
465 $(document).on('click', '#edit-show-password', function() {
466 const passwordInput = $('#edit-client-password');
467 const icon = $(this).find('i');
468 if (passwordInput.attr('type') === 'password') {
469 passwordInput.attr('type', 'text');
470 icon.removeClass('fa-eye').addClass('fa-eye-slash');
471 } else {
472 passwordInput.attr('type', 'password');
473 icon.removeClass('fa-eye-slash').addClass('fa-eye');
474 }
475 });
476
477 // Generate password functionality for add client modal
478 $(document).on('click', '#add-generate-password, #edit-generate-password', function() {
479 const button = $(this);
480 const isEditMode = button.attr('id') === 'edit-generate-password';
481 const passwordInput = isEditMode ? $('#edit-client-password') : $('#add-client-password');
482 const showPasswordBtn = isEditMode ? $('#edit-show-password') : $('#add-show-password');
483
484 button.prop('disabled', true);
485
486 $.ajax({
487 url: easyInvoice.ajaxUrl,
488 type: 'POST',
489 data: {
490 action: 'easy_invoice_generate_password',
491 nonce: easyInvoice.nonce,
492 suppress_global_toast: true // Prevent global success toast
493 },
494 success: function(response) {
495 if (response.success) {
496 passwordInput.val(response.data.password);
497 passwordInput.attr('type', 'text');
498 showPasswordBtn.find('i').removeClass('fa-eye').addClass('fa-eye-slash');
499 } else {
500
501 }
502 },
503 error: function() {
504 if (typeof EasyInvoiceToast !== 'undefined') {
505 EasyInvoiceToast.error('Error connecting to server');
506 } else if (typeof showToast === 'function') {
507 showToast('Error connecting to server', 'error');
508 } else {
509 console.error('Error connecting to server');
510 }
511 },
512 complete: function() {
513 button.prop('disabled', false);
514 }
515 });
516 });
517
518
519 }
520
521 // Setup client edit functionality
522 function setupClientEdit() {
523 // Disable HTML5 validation for password field in edit mode
524 if ($("#edit-client-form").length) {
525 $("#edit-client-password").removeAttr("required");
526 }
527
528 // Handle client edit form submission
529 $(document).on("submit", "#edit-client-form", function(e) {
530 e.preventDefault();
531
532 let isSubmitting = false;
533 if (isSubmitting) return;
534
535 isSubmitting = true;
536
537 const clientData = {
538 client_id: $("#edit-client-id").val(),
539 business_client_name: $("#edit-client-business-name").val(),
540 email: $("#edit-client-email").val(),
541 username: $("#edit-client-username").val(),
542 password: $("#edit-client-password").val(),
543 address: $("#edit-client-address").val(),
544 phone: $("#edit-client-phone").val(),
545 first_name: $("#edit-client-first-name").val(),
546 last_name: $("#edit-client-last-name").val(),
547 website: $("#edit-client-website").val(),
548 action: "easy_invoice_update_client",
549 nonce: easyInvoice.nonce
550 };
551
552 const submitBtn = $("#edit-save-client-btn");
553 const originalText = submitBtn.text();
554 submitBtn.prop("disabled", true).html("<i class=\"fas fa-spinner fa-spin mr-2\"></i> Saving...");
555
556 $.ajax({
557 url: easyInvoice.ajaxUrl,
558 type: "POST",
559 data: clientData,
560 success: function(response) {
561 // Re-enable the button regardless of success/failure
562 submitBtn.prop("disabled", false).text(originalText);
563 isSubmitting = false;
564 },
565 error: function() {
566 if (typeof EasyInvoiceToast !== 'undefined') {
567 EasyInvoiceToast.error("Error connecting to server");
568 } else if (typeof showToast === 'function') {
569 showToast("Error connecting to server", "error");
570 } else {
571 console.error("Error connecting to server");
572 }
573 submitBtn.prop("disabled", false).text(originalText);
574 isSubmitting = false;
575 }
576 });
577 });
578 }
579
580 // Initialize all client functionality
581 function initializeClientManager() {
582 // Prevent multiple initializations
583 if (window.clientManagerInitialized) {
584 return;
585 }
586
587 setupClientSelection();
588 setupClientModal();
589 setupPasswordFields();
590 setupClientEdit();
591
592 // Mark as initialized
593 window.clientManagerInitialized = true;
594 }
595
596 // Initialize the client manager
597 initializeClientManager();
598
599 // Expose client manager functions globally
600 window.clientManager = {
601 initializeClientData: initializeClientData,
602 refreshClientList: function(newClientData) {
603 clientsData = newClientData || clientsData;
604 window.easyInvoiceClients = clientsData;
605 },
606 setupModal: setupClientModal
607 };
608 });
609 })(jQuery);