feat(loyalty): add staff autocomplete to PIN management

When creating or editing a staff PIN in the store context, the name
field now shows an autocomplete dropdown with the store's team members
(loaded from GET /store/team/members). Selecting a member auto-fills
name and staff_id (email). The dropdown filters as you type.

Only active in store context (where staffApiPrefix is configured).
Merchant and admin PIN views are unaffected — merchant has no
staffApiPrefix, admin is read-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 20:58:10 +01:00
parent 316ec42566
commit 4429674100
3 changed files with 98 additions and 10 deletions

View File

@@ -24,6 +24,7 @@ function loyaltyPinsList(config) {
pins: [],
program: null,
locations: [],
staffMembers: [],
// Filters
filters: {
@@ -57,6 +58,33 @@ function loyaltyPinsList(config) {
store_id: ''
},
// Staff autocomplete state
staffSearch: '',
showStaffDropdown: false,
get filteredStaff() {
if (!this.staffSearch) return this.staffMembers;
const q = this.staffSearch.toLowerCase();
return this.staffMembers.filter(m =>
(m.full_name && m.full_name.toLowerCase().includes(q)) ||
(m.email && m.email.toLowerCase().includes(q))
);
},
selectStaffMember(member) {
this.pinForm.name = member.full_name;
this.pinForm.staff_id = member.email;
this.staffSearch = member.full_name;
this.showStaffDropdown = false;
},
clearStaffSelection() {
this.staffSearch = '';
this.pinForm.name = '';
this.pinForm.staff_id = '';
this.showStaffDropdown = false;
},
// Action state
saving: false,
deleting: false,
@@ -88,6 +116,9 @@ function loyaltyPinsList(config) {
if (config.showStoreFilter) {
parallel.push(this.loadLocations());
}
if (config.showCrud && config.staffApiPrefix) {
parallel.push(this.loadStaffMembers());
}
await Promise.all(parallel);
}
} catch (error) {
@@ -137,6 +168,18 @@ function loyaltyPinsList(config) {
}
},
async loadStaffMembers() {
try {
const response = await apiClient.get(config.staffApiPrefix + '/team/members');
if (response && response.members) {
this.staffMembers = response.members.filter(m => m.is_active);
loyaltyPinsListLog.info('Loaded', this.staffMembers.length, 'staff members');
}
} catch (error) {
loyaltyPinsListLog.warn('Failed to load staff members:', error.message);
}
},
computeStats() {
this.stats.total = this.pins.length;
this.stats.active = this.pins.filter(p => p.is_active && !p.is_locked).length;
@@ -156,6 +199,8 @@ function loyaltyPinsList(config) {
pin: '',
store_id: ''
};
this.staffSearch = '';
this.showStaffDropdown = false;
this.showCreateModal = true;
},
@@ -167,6 +212,8 @@ function loyaltyPinsList(config) {
pin: '',
store_id: pin.store_id || ''
};
this.staffSearch = pin.name || '';
this.showStaffDropdown = false;
this.showEditModal = true;
},