refactor(tenancy): simplify team table + move actions to edit modal
Some checks failed
Some checks failed
Reverts the expandable sub-row design back to a clean one-row-per-member table. All per-store management now happens inside the edit modal. Table: simple 4-column layout (Member | Stores & Roles | Status | Actions) with view + edit buttons. Store badges show orange for pending stores. Edit modal enhanced with per-store cards showing: - Store name, code, and status badge (Active/Pending) - Role dropdown + Update button (for active stores) - Resend invitation button (for pending stores) - Remove from store button - "Remove from all stores" link at bottom Removed: expandedMembers, flattenedRows, toggleMemberExpand, resendStoreInvitation, resendInvitation (member-level). Added: resendForStore, removeFromStore (work inside edit modal). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,9 +31,6 @@ function merchantTeam() {
|
||||
// Filters
|
||||
storeFilter: '',
|
||||
|
||||
// Expanded member rows
|
||||
expandedMembers: [],
|
||||
|
||||
// Modal states
|
||||
showInviteModal: false,
|
||||
showEditModal: false,
|
||||
@@ -132,44 +129,18 @@ function merchantTeam() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Flatten members + their stores into a single row list for table rendering.
|
||||
* Each row is either {type:'member', member, key} or {type:'store', member, store, key}
|
||||
* Resend invitation for a specific store (used inside edit modal)
|
||||
*/
|
||||
get flattenedRows() {
|
||||
const rows = [];
|
||||
for (const member of this.filteredMembers) {
|
||||
rows.push({ type: 'member', member, key: `m-${member.user_id}` });
|
||||
for (const store of member.stores) {
|
||||
rows.push({ type: 'store', member, store, key: `s-${member.user_id}-${store.store_id}` });
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle expand/collapse for a member's store rows
|
||||
*/
|
||||
toggleMemberExpand(userId) {
|
||||
const idx = this.expandedMembers.indexOf(userId);
|
||||
if (idx > -1) {
|
||||
this.expandedMembers.splice(idx, 1);
|
||||
} else {
|
||||
this.expandedMembers.push(userId);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Resend invitation for a specific store membership
|
||||
*/
|
||||
async resendStoreInvitation(storeId, userId) {
|
||||
async resendForStore(storeId, userId) {
|
||||
this.saving = true;
|
||||
try {
|
||||
await apiClient.post(
|
||||
`/merchants/account/team/stores/${storeId}/members/${userId}/resend`
|
||||
);
|
||||
|
||||
Utils.showToast(I18n.t('tenancy.messages.invitation_resent'), 'success');
|
||||
merchantTeamLog.info('Resent invitation for store:', storeId, 'user:', userId);
|
||||
this.showEditModal = false;
|
||||
this.selectedMember = null;
|
||||
await this.loadTeamData();
|
||||
} catch (error) {
|
||||
merchantTeamLog.error('Failed to resend invitation:', error);
|
||||
@@ -179,6 +150,28 @@ function merchantTeam() {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove member from a specific store (used inside edit modal)
|
||||
*/
|
||||
async removeFromStore(storeId, userId) {
|
||||
this.saving = true;
|
||||
try {
|
||||
await apiClient.delete(
|
||||
`/merchants/account/team/stores/${storeId}/members/${userId}`
|
||||
);
|
||||
Utils.showToast(I18n.t('tenancy.messages.team_member_removed'), 'success');
|
||||
merchantTeamLog.info('Removed member from store:', storeId, 'user:', userId);
|
||||
this.showEditModal = false;
|
||||
this.selectedMember = null;
|
||||
await this.loadTeamData();
|
||||
} catch (error) {
|
||||
merchantTeamLog.error('Failed to remove member:', error);
|
||||
Utils.showToast(error.message || 'Failed to remove member', 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Open invite modal with reset form
|
||||
*/
|
||||
@@ -277,30 +270,6 @@ function merchantTeam() {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Resend invitation to a pending member
|
||||
*/
|
||||
async resendInvitation(member) {
|
||||
if (!member.stores || member.stores.length === 0) return;
|
||||
|
||||
this.saving = true;
|
||||
try {
|
||||
// Resend for the first pending store
|
||||
const pendingStore = member.stores.find(s => s.is_pending) || member.stores[0];
|
||||
await apiClient.post(
|
||||
`/merchants/account/team/stores/${pendingStore.store_id}/members/${member.user_id}/resend`
|
||||
);
|
||||
|
||||
Utils.showToast(I18n.t('tenancy.messages.invitation_resent'), 'success');
|
||||
merchantTeamLog.info('Resent invitation to:', member.email);
|
||||
await this.loadTeamData();
|
||||
} catch (error) {
|
||||
merchantTeamLog.error('Failed to resend invitation:', error);
|
||||
Utils.showToast(error.message || 'Failed to resend invitation', 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update member role for a specific store
|
||||
|
||||
Reference in New Issue
Block a user