refactor(tenancy): simplify team table + move actions to edit modal
Some checks failed
CI / ruff (push) Successful in 15s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

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:
2026-03-30 21:08:36 +02:00
parent 0c6d8409c7
commit d685341b04
4 changed files with 201 additions and 224 deletions

View File

@@ -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