From 38d1352e9a086860ecb00b57d020cef75e424a32 Mon Sep 17 00:00:00 2001 From: Malin Date: Wed, 1 Apr 2026 17:18:01 +0200 Subject: [PATCH] fix: customer search & linking fully implemented MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ajax_search_wc_customers() – searches user_login, user_email, display_name AND first_name/last_name meta so any query returns hits - Add ajax_unlink_customer() to remove an existing Odoo link - Replace placeholder Customers tab with two-panel UI: Step 1 search WC customers → Step 2 search Odoo partners independently (WC email and Odoo email do not need to match) - Results table shows current link status; inline Link/Unlink actions update the row in-place without page reload - Admin JS fully wired: both search inputs respond to Enter key and button - Add two-panel layout CSS and results table styles Co-Authored-By: Claude Sonnet 4.6 --- assets/css/woodoo-admin.css | 44 +++++ assets/js/woodoo-admin.js | 328 +++++++++++++++++++++++++------- includes/class-woodoo-admin.php | 127 ++++++++++++- 3 files changed, 426 insertions(+), 73 deletions(-) diff --git a/assets/css/woodoo-admin.css b/assets/css/woodoo-admin.css index edef085..cd694e6 100644 --- a/assets/css/woodoo-admin.css +++ b/assets/css/woodoo-admin.css @@ -24,3 +24,47 @@ } .woodoo-badge--green { background: #d1fae5; color: #065f46; } .woodoo-badge--red { background: #fee2e2; color: #991b1b; } +.woodoo-badge--grey { background: #f3f4f6; color: #6b7280; } + +/* ── Two-panel customer linker ────────────────────────────────────────── */ +.woodoo-linker-panel { + display: flex; + gap: 24px; + align-items: flex-start; + margin-top: 12px; +} +.woodoo-linker-col { + flex: 1; + min-width: 0; + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 6px; + padding: 16px; +} +.woodoo-linker-col h3 { + margin: 0 0 10px; + font-size: 1rem; + color: #2271b1; +} +.woodoo-search-row { + display: flex; + gap: 6px; + margin-bottom: 12px; +} +.woodoo-search-row .regular-text { flex: 1; margin: 0; } +.woodoo-results-list { margin-top: 4px; } +.woodoo-no-results { color: #6b7280; font-style: italic; margin: 8px 0 0; } +.woodoo-linking-for { + font-size: .875rem; + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 4px; + padding: 6px 10px; + margin: 0 0 10px; +} + +/* ── Results table ────────────────────────────────────────────────────── */ +.woodoo-link-table { font-size: .8125rem; } +.woodoo-link-table th { padding: 6px 8px; font-weight: 600; background: #f3f4f6; } +.woodoo-link-table td { padding: 6px 8px; vertical-align: middle; } +.woodoo-link-table tr:hover td { background: #fafafa; } diff --git a/assets/js/woodoo-admin.js b/assets/js/woodoo-admin.js index 8299ae3..a504a3f 100644 --- a/assets/js/woodoo-admin.js +++ b/assets/js/woodoo-admin.js @@ -1,16 +1,17 @@ /* WooDoo Admin JS */ jQuery( function ( $ ) { - const cfg = window.WooDooAdmin || {}; + const cfg = window.WooDooAdmin || {}; + const i18n = cfg.i18n || {}; + const ajaxUrl = cfg.ajax_url; + const nonce = cfg.nonce; // ── Tab navigation ────────────────────────────────────────────────── $( '.woodoo-settings .nav-tab' ).on( 'click', function ( e ) { e.preventDefault(); const target = $( this ).attr( 'href' ); - $( '.woodoo-settings .nav-tab' ).removeClass( 'nav-tab-active' ); $( this ).addClass( 'nav-tab-active' ); - $( '.woodoo-tab' ).hide(); $( target ).show(); } ); @@ -20,77 +21,273 @@ jQuery( function ( $ ) { const $btn = $( this ); const $result = $( '#woodoo-test-result' ); - $btn.prop( 'disabled', true ).text( cfg.i18n.testing ); + $btn.prop( 'disabled', true ).text( i18n.testing ); $result.html( '' ); - $.post( cfg.ajax_url, { - action : 'woodoo_test_connection', - nonce : cfg.nonce, + ajax( 'woodoo_test_connection', {} ) + .done( function ( res ) { + if ( res.success ) { + const d = res.data; + const cls = d.success ? 'green' : 'red'; + $result.html( badge( cls, d.message ) ); + } else { + $result.html( badge( 'red', res.data || i18n.error ) ); + } + } ) + .fail( function () { $result.html( badge( 'red', i18n.error ) ); } ) + .always( function () { + $btn.prop( 'disabled', false ).text( 'Test Connection' ); + } ); + } ); + + // ══════════════════════════════════════════════════════════════════════ + // CUSTOMER LINKING TAB + // ══════════════════════════════════════════════════════════════════════ + + let selectedWcUser = null; // { id, display_name, email } + + // ── Step 1: search WooCommerce customers ───────────────────────────── + function searchWcCustomers() { + const q = $( '#woo-customer-search' ).val().trim(); + if ( q.length < 2 ) return; + + const $results = $( '#woo-customer-results' ); + $results.html( '' + esc( i18n.searching ) + '' ); + $( '#woodoo-odoo-col' ).hide(); + selectedWcUser = null; + + ajax( 'woodoo_search_wc_customers', { query: q } ) + .done( function ( res ) { + if ( ! res.success || ! res.data.length ) { + $results.html( '

' + esc( i18n.no_wc_customers ) + '

' ); + return; + } + + let html = '' + + '' + + ''; + + res.data.forEach( function ( u ) { + const linked = u.partner_id + ? '' + esc( u.partner_name || '#' + u.partner_id ) + '' + : '' + esc( i18n.not_linked ) + ''; + + html += + '' + + '' + + '' + + '' + + ''; + } ); + + html += ''; + $results.html( html ); + } ) + .fail( function () { $results.html( '

' + esc( i18n.error ) + '

' ); } ); + } + + $( '#woo-customer-search-btn' ).on( 'click', searchWcCustomers ); + $( '#woo-customer-search' ).on( 'keypress', function ( e ) { + if ( e.which === 13 ) { e.preventDefault(); searchWcCustomers(); } + } ); + + // ── Select a WC customer → open Odoo partner search panel ─────────── + $( document ).on( 'click', '.woodoo-select-wc-user', function () { + const $btn = $( this ); + selectedWcUser = { + id : $btn.data( 'id' ), + name : $btn.data( 'name' ), + email : $btn.data( 'email' ), + }; + + // Highlight selected row + $( '#woo-customer-results tr' ).css( 'background', '' ); + $btn.closest( 'tr' ).css( 'background', '#eff6ff' ); + + // Show step 2 panel + $( '#woodoo-linking-for' ).html( + 'Linking: ' + esc( selectedWcUser.name ) + + ' (' + esc( selectedWcUser.email ) + ')' + ); + $( '#woodoo-odoo-col' ).show(); + $( '#woodoo-odoo-partner-search' ).val( '' ).focus(); + $( '#woodoo-odoo-partner-results' ).html( '' ); + } ); + + // ── Step 2: search Odoo partners ───────────────────────────────────── + function searchOdooPartners() { + if ( ! selectedWcUser ) return; + + const q = $( '#woodoo-odoo-partner-search' ).val().trim(); + if ( q.length < 2 ) return; + + const $results = $( '#woodoo-odoo-partner-results' ); + $results.html( '' + esc( i18n.searching ) + '' ); + + ajax( 'woodoo_search_partners', { query: q } ) + .done( function ( res ) { + if ( ! res.success || ! res.data.length ) { + $results.html( '

' + esc( i18n.no_odoo_partners ) + '

' ); + return; + } + + let html = '' + + '' + + ''; + + res.data.forEach( function ( p ) { + html += + '' + + '' + + '' + + '' + + '' + + ''; + } ); + + html += ''; + $results.html( html ); + } ) + .fail( function () { $results.html( '

' + esc( i18n.error ) + '

' ); } ); + } + + $( '#woodoo-odoo-partner-search-btn' ).on( 'click', searchOdooPartners ); + $( '#woodoo-odoo-partner-search' ).on( 'keypress', function ( e ) { + if ( e.which === 13 ) { e.preventDefault(); searchOdooPartners(); } + } ); + + // ── Confirm link: save WC user ↔ Odoo partner ──────────────────────── + $( document ).on( 'click', '.woodoo-confirm-link', function () { + if ( ! selectedWcUser ) return; + + const $btn = $( this ); + const partnerId = $btn.data( 'partner-id' ); + const partnerName = $btn.data( 'partner-name' ); + + $btn.prop( 'disabled', true ).text( 'Saving…' ); + + ajax( 'woodoo_link_customer', { + user_id : selectedWcUser.id, + partner_id : partnerId, } ) .done( function ( res ) { if ( res.success ) { - const d = res.data; - const cls = d.success ? 'success' : 'error'; - $result.html( - '' + - escHtml( d.message ) + '' + // Update the partner status cell in the WC customers table + const $row = $( '#woo-customer-results tr[data-user-id="' + selectedWcUser.id + '"]' ); + $row.find( '.woodoo-partner-status' ).html( + badge( 'green', partnerName ) ); + $row.find( '.woodoo-link-actions' ).html( + '' + + ' ' + ); + $row.css( 'background', '#ecfdf5' ); + + // Success feedback in step 2 + $( '#woodoo-odoo-partner-results' ).prepend( + '

✓ ' + + esc( i18n.link_done ) + ': ' + esc( selectedWcUser.name ) + + ' → ' + esc( partnerName ) + '

' + ); + $btn.text( 'Linked!' ); } else { - $result.html( '' + escHtml( res.data || cfg.i18n.error ) + '' ); + $btn.prop( 'disabled', false ).text( 'Link' ); + alert( res.data || i18n.error ); } } ) .fail( function () { - $result.html( '' + escHtml( cfg.i18n.error ) + '' ); - } ) - .always( function () { - $btn.prop( 'disabled', false ).text( 'Test Connection' ); + $btn.prop( 'disabled', false ).text( 'Link' ); + alert( i18n.error ); } ); } ); - // ── Partner search on user profile ────────────────────────────────── + // ── Unlink ──────────────────────────────────────────────────────────── + $( document ).on( 'click', '.woodoo-unlink-user', function () { + if ( ! confirm( i18n.unlink_confirm ) ) return; + + const $btn = $( this ); + const userId = $btn.data( 'id' ); + $btn.prop( 'disabled', true ); + + ajax( 'woodoo_unlink_customer', { user_id: userId } ) + .done( function ( res ) { + if ( res.success ) { + const $row = $( '#woo-customer-results tr[data-user-id="' + userId + '"]' ); + $row.find( '.woodoo-partner-status' ).html( badge( 'grey', i18n.not_linked ) ); + $row.find( '.woodoo-link-actions button.woodoo-unlink-user' ).remove(); + $row.css( 'background', '' ); + if ( selectedWcUser && selectedWcUser.id == userId ) { + $( '#woodoo-odoo-col' ).hide(); + selectedWcUser = null; + } + } else { + $btn.prop( 'disabled', false ); + alert( res.data || i18n.error ); + } + } ) + .fail( function () { $btn.prop( 'disabled', false ); } ); + } ); + + // ══════════════════════════════════════════════════════════════════════ + // USER PROFILE PAGE – partner search + // ══════════════════════════════════════════════════════════════════════ + $( '#woodoo-partner-search-btn' ).on( 'click', function () { const $input = $( '#woodoo-partner-search-input' ); const $results = $( '#woodoo-partner-search-results' ); - const query = $input.val().trim(); + const q = $input.val().trim(); const userId = $input.data( 'user-id' ); - if ( ! query ) return; + if ( ! q ) return; - $results.html( '' + escHtml( cfg.i18n.searching ) + '' ); + $results.html( '' + esc( i18n.searching ) + '' ); - $.post( cfg.ajax_url, { - action : 'woodoo_search_partners', - nonce : cfg.nonce, - query : query, - } ) - .done( function ( res ) { - if ( ! res.success || ! res.data.length ) { - $results.html( 'No partners found.' ); - return; - } + ajax( 'woodoo_search_partners', { query: q } ) + .done( function ( res ) { + if ( ! res.success || ! res.data.length ) { + $results.html( '' + esc( i18n.no_odoo_partners ) + '' ); + return; + } - let html = ''; - $results.html( html ); - } ) - .fail( function () { - $results.html( '' + escHtml( cfg.i18n.error ) + '' ); - } ); + let html = ''; + $results.html( html ); + } ) + .fail( function () { $results.html( '' + esc( i18n.error ) + '' ); } ); } ); - // ── Pick partner from search results ──────────────────────────────── $( document ).on( 'click', '.woodoo-pick-partner', function () { const $btn = $( this ); const partnerId = $btn.data( 'id' ); @@ -100,31 +297,34 @@ jQuery( function ( $ ) { $( '#woodoo_odoo_partner_id' ).val( partnerId ); $( '#woodoo-partner-name-display' ).text( name ).show(); $( '#woodoo-partner-search-results' ).html( - '' + escHtml( cfg.i18n.link_done ) + ' → ' + escHtml( name ) + ' (#' + partnerId + ')' + '' + esc( i18n.link_done ) + + ' → ' + esc( name ) + ' (#' + partnerId + ')' ); - // Persist immediately if we have the AJAX handler if ( userId ) { - $.post( cfg.ajax_url, { - action : 'woodoo_link_customer', - nonce : cfg.nonce, - user_id : userId, - partner_id : partnerId, - } ); + ajax( 'woodoo_link_customer', { user_id: userId, partner_id: partnerId } ); } } ); - // ── Utility ────────────────────────────────────────────────────────── - function escHtml( str ) { + // ── Utilities ───────────────────────────────────────────────────────── + function ajax( action, data ) { + return $.post( ajaxUrl, Object.assign( { action, nonce }, data ) ); + } + + function badge( color, text ) { + return '' + esc( text ) + ''; + } + + function esc( str ) { return String( str ) .replace( /&/g, '&' ) - .replace( //g, '>' ) + .replace( //g, '>' ) .replace( /"/g, '"' ); } function escAttr( str ) { - return escHtml( str ).replace( /'/g, ''' ); + return esc( str ).replace( /'/g, ''' ); } } ); diff --git a/includes/class-woodoo-admin.php b/includes/class-woodoo-admin.php index 29830b1..2202675 100644 --- a/includes/class-woodoo-admin.php +++ b/includes/class-woodoo-admin.php @@ -21,7 +21,9 @@ class WooDoo_Admin { // AJAX handlers (admin only) add_action( 'wp_ajax_woodoo_test_connection', [ __CLASS__, 'ajax_test_connection' ] ); add_action( 'wp_ajax_woodoo_search_partners', [ __CLASS__, 'ajax_search_partners' ] ); + add_action( 'wp_ajax_woodoo_search_wc_customers',[ __CLASS__, 'ajax_search_wc_customers' ] ); add_action( 'wp_ajax_woodoo_link_customer', [ __CLASS__, 'ajax_link_customer' ] ); + add_action( 'wp_ajax_woodoo_unlink_customer', [ __CLASS__, 'ajax_unlink_customer' ] ); // WC customer list column add_filter( 'manage_users_columns', [ __CLASS__, 'add_users_column' ] ); @@ -91,10 +93,17 @@ class WooDoo_Admin { 'nonce' => wp_create_nonce( 'woodoo_admin' ), 'ajax_url' => admin_url( 'admin-ajax.php' ), 'i18n' => [ - 'testing' => __( 'Testing…', 'woodoo' ), - 'searching' => __( 'Searching…', 'woodoo' ), - 'link_done' => __( 'Linked!', 'woodoo' ), - 'error' => __( 'Error. Check console.', 'woodoo' ), + 'testing' => __( 'Testing…', 'woodoo' ), + 'searching' => __( 'Searching…', 'woodoo' ), + 'no_wc_customers' => __( 'No customers found.', 'woodoo' ), + 'no_odoo_partners' => __( 'No Odoo partners found.', 'woodoo' ), + 'link_done' => __( 'Linked!', 'woodoo' ), + 'unlink_confirm' => __( 'Remove the Odoo link for this customer?', 'woodoo' ), + 'unlinked' => __( 'Unlinked', 'woodoo' ), + 'error' => __( 'Error. Check console.', 'woodoo' ), + 'search_odoo' => __( 'Search Odoo partners…', 'woodoo' ), + 'linked_to' => __( 'Linked to', 'woodoo' ), + 'not_linked' => __( 'Not linked', 'woodoo' ), ], ] ); } @@ -230,12 +239,35 @@ class WooDoo_Admin {