feat(loyalty): add customer autocomplete to terminal search

Terminal search now shows live autocomplete suggestions as the user
types (debounced 300ms, min 2 chars). Dropdown shows matching customers
with avatar, name, email, card number, and points balance. Uses the
existing GET /store/loyalty/cards?search= endpoint (limit=5).

Selecting a result loads the full card details via the lookup endpoint.
Enter key still works for exact lookup. No new dependencies — uses
native Alpine.js dropdown, no Tom Select needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 21:21:36 +01:00
parent b679c9687d
commit 040cbd1962
2 changed files with 90 additions and 4 deletions

View File

@@ -23,6 +23,9 @@ function storeLoyaltyTerminal() {
searchQuery: '',
lookingUp: false,
selectedCard: null,
searchResults: [],
showSearchDropdown: false,
_searchTimeout: null,
// Transaction inputs
earnAmount: null,
@@ -146,6 +149,58 @@ function storeLoyaltyTerminal() {
}
},
// Debounced search for autocomplete suggestions
debouncedSearchCustomers() {
if (this._searchTimeout) clearTimeout(this._searchTimeout);
if (!this.searchQuery || this.searchQuery.length < 2) {
this.searchResults = [];
this.showSearchDropdown = false;
return;
}
this._searchTimeout = setTimeout(() => this.searchCustomers(), 300);
},
async searchCustomers() {
try {
const params = new URLSearchParams({
search: this.searchQuery,
limit: '5',
is_active: 'true'
});
const response = await apiClient.get(`/store/loyalty/cards?${params}`);
if (response && response.cards) {
this.searchResults = response.cards;
this.showSearchDropdown = this.searchResults.length > 0;
}
} catch (error) {
loyaltyTerminalLog.warn('Search failed:', error.message);
this.searchResults = [];
this.showSearchDropdown = false;
}
},
// Select a customer from autocomplete dropdown
async selectCustomer(card) {
this.showSearchDropdown = false;
this.searchResults = [];
this.lookingUp = true;
try {
// Use the lookup endpoint to get full card details
const response = await apiClient.get(`/store/loyalty/cards/lookup?q=${encodeURIComponent(card.card_number)}`);
if (response) {
this.selectedCard = response;
this.searchQuery = '';
loyaltyTerminalLog.info('Customer selected:', this.selectedCard.customer_name);
}
} catch (error) {
Utils.showToast(I18n.t('loyalty.store.terminal.error_lookup', {message: error.message}), 'error');
loyaltyTerminalLog.error('Lookup failed:', error);
} finally {
this.lookingUp = false;
}
},
// Clear selected customer
clearCustomer() {
this.selectedCard = null;