refactor(loyalty): use search_autocomplete macro for staff PIN lookup
Replace custom inline autocomplete HTML in both create and edit PIN modals with the shared search_autocomplete macro from inputs.html. Refactored JS to use staffSearchResults array populated by searchStaff() (client-side filter) matching the macro's conventions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,35 +60,40 @@ function loyaltyPinsList(config) {
|
||||
|
||||
// Staff autocomplete state
|
||||
staffSearch: '',
|
||||
staffSearchResults: [],
|
||||
showStaffDropdown: false,
|
||||
|
||||
// Track which member was selected (to detect when user edits away)
|
||||
searchingStaff: false,
|
||||
_selectedStaffName: '',
|
||||
|
||||
get filteredStaff() {
|
||||
if (!this.staffSearch) return this.staffMembers;
|
||||
searchStaff() {
|
||||
// Client-side filter of loaded staff members
|
||||
if (!this.staffSearch || this.staffSearch.length < 1) {
|
||||
this.staffSearchResults = [];
|
||||
this.showStaffDropdown = false;
|
||||
return;
|
||||
}
|
||||
const q = this.staffSearch.toLowerCase();
|
||||
return this.staffMembers.filter(m =>
|
||||
this.staffSearchResults = this.staffMembers.filter(m =>
|
||||
(m.full_name && m.full_name.toLowerCase().includes(q)) ||
|
||||
(m.email && m.email.toLowerCase().includes(q))
|
||||
);
|
||||
},
|
||||
this.showStaffDropdown = this.staffSearchResults.length > 0;
|
||||
|
||||
selectStaffMember(member) {
|
||||
this.pinForm.name = member.full_name;
|
||||
this.pinForm.staff_id = member.email;
|
||||
this.staffSearch = member.full_name;
|
||||
this._selectedStaffName = member.full_name;
|
||||
this.showStaffDropdown = false;
|
||||
},
|
||||
|
||||
onStaffSearchInput() {
|
||||
this.pinForm.name = this.staffSearch;
|
||||
// If user modified the text away from the selected member, clear staff_id
|
||||
if (this._selectedStaffName && this.staffSearch !== this._selectedStaffName) {
|
||||
this.pinForm.staff_id = '';
|
||||
this._selectedStaffName = '';
|
||||
}
|
||||
this.pinForm.name = this.staffSearch;
|
||||
},
|
||||
|
||||
selectStaffMember(item) {
|
||||
this.pinForm.name = item.full_name;
|
||||
this.pinForm.staff_id = item.email;
|
||||
this.staffSearch = item.full_name;
|
||||
this._selectedStaffName = item.full_name;
|
||||
this.showStaffDropdown = false;
|
||||
this.staffSearchResults = [];
|
||||
},
|
||||
|
||||
clearStaffSelection() {
|
||||
@@ -97,6 +102,7 @@ function loyaltyPinsList(config) {
|
||||
this.pinForm.staff_id = '';
|
||||
this._selectedStaffName = '';
|
||||
this.showStaffDropdown = false;
|
||||
this.staffSearchResults = [];
|
||||
},
|
||||
|
||||
// Action state
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/tables.html' import table_wrapper, table_header %}
|
||||
{% from 'shared/macros/modals.html' import modal, confirm_modal %}
|
||||
{% from 'shared/macros/inputs.html' import search_autocomplete %}
|
||||
|
||||
<!-- Stats Summary -->
|
||||
<div x-show="!loading" class="mb-6 flex flex-wrap items-center gap-6 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
@@ -132,29 +133,22 @@
|
||||
<form @submit.prevent="createPin()">
|
||||
<div class="space-y-4">
|
||||
<!-- Staff Member Autocomplete -->
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('loyalty.shared.pins.pin_name') }}</label>
|
||||
<input type="text" x-model="staffSearch" required
|
||||
@focus="open = staffSearch.length > 0 && staffMembers.length > 0"
|
||||
@input="open = staffMembers.length > 0; onStaffSearchInput()"
|
||||
autocomplete="off"
|
||||
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300"
|
||||
placeholder="{{ _('loyalty.shared.pins.pin_name') }}">
|
||||
<!-- Dropdown -->
|
||||
<div x-show="open && filteredStaff.length > 0" x-cloak
|
||||
class="absolute z-50 w-full mt-1 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-48 overflow-y-auto">
|
||||
<template x-for="member in filteredStaff" :key="member.id">
|
||||
<button type="button"
|
||||
@click="selectStaffMember(member); open = false"
|
||||
class="w-full px-3 py-2 text-left text-sm hover:bg-purple-50 dark:hover:bg-gray-600 flex items-center justify-between">
|
||||
<span>
|
||||
<span class="font-medium text-gray-800 dark:text-gray-200" x-text="member.full_name"></span>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs ml-2" x-text="member.role_name"></span>
|
||||
</span>
|
||||
<span class="text-xs text-gray-400" x-text="member.email"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
{{ search_autocomplete(
|
||||
search_var='staffSearch',
|
||||
results_var='staffSearchResults',
|
||||
show_dropdown_var='showStaffDropdown',
|
||||
loading_var='searchingStaff',
|
||||
search_action='searchStaff()',
|
||||
select_action='selectStaffMember(item)',
|
||||
display_field='full_name',
|
||||
secondary_field='email',
|
||||
placeholder=_('loyalty.shared.pins.pin_name'),
|
||||
min_chars=1,
|
||||
no_results_text=_('loyalty.store.terminal.customer_not_found'),
|
||||
loading_text=_('loyalty.store.terminal.looking_up')
|
||||
) }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('loyalty.shared.pins.pin_staff_id') }}</label>
|
||||
@@ -201,29 +195,22 @@
|
||||
<form @submit.prevent="updatePin()">
|
||||
<div class="space-y-4">
|
||||
<!-- Staff Member Autocomplete -->
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('loyalty.shared.pins.pin_name') }}</label>
|
||||
<input type="text" x-model="staffSearch" required
|
||||
@focus="open = staffSearch.length > 0 && staffMembers.length > 0"
|
||||
@input="open = staffMembers.length > 0; onStaffSearchInput()"
|
||||
autocomplete="off"
|
||||
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300"
|
||||
placeholder="{{ _('loyalty.shared.pins.pin_name') }}">
|
||||
<!-- Dropdown -->
|
||||
<div x-show="open && filteredStaff.length > 0" x-cloak
|
||||
class="absolute z-50 w-full mt-1 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-48 overflow-y-auto">
|
||||
<template x-for="member in filteredStaff" :key="member.id">
|
||||
<button type="button"
|
||||
@click="selectStaffMember(member); open = false"
|
||||
class="w-full px-3 py-2 text-left text-sm hover:bg-purple-50 dark:hover:bg-gray-600 flex items-center justify-between">
|
||||
<span>
|
||||
<span class="font-medium text-gray-800 dark:text-gray-200" x-text="member.full_name"></span>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs ml-2" x-text="member.role_name"></span>
|
||||
</span>
|
||||
<span class="text-xs text-gray-400" x-text="member.email"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
{{ search_autocomplete(
|
||||
search_var='staffSearch',
|
||||
results_var='staffSearchResults',
|
||||
show_dropdown_var='showStaffDropdown',
|
||||
loading_var='searchingStaff',
|
||||
search_action='searchStaff()',
|
||||
select_action='selectStaffMember(item)',
|
||||
display_field='full_name',
|
||||
secondary_field='email',
|
||||
placeholder=_('loyalty.shared.pins.pin_name'),
|
||||
min_chars=1,
|
||||
no_results_text=_('loyalty.store.terminal.customer_not_found'),
|
||||
loading_text=_('loyalty.store.terminal.looking_up')
|
||||
) }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _('loyalty.shared.pins.pin_staff_id') }}</label>
|
||||
|
||||
Reference in New Issue
Block a user