feat(tenancy): add delete button on table + add-to-store in edit modal
All checks were successful
All checks were successful
Table actions now show view + edit + delete (trash icon) for non-owner members. Delete opens the existing remove-from-all-stores modal. Edit modal enhanced with "Add to another store" section: - Shows a dashed-border card with store dropdown + role dropdown + add button - Only appears when the member is not yet in all merchant stores - Uses the existing invite API to add the member to the selected store i18n: 2 new keys (add_to_store, select_store) in 4 locales. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,8 @@
|
|||||||
"personal_info": "Persönliche Informationen",
|
"personal_info": "Persönliche Informationen",
|
||||||
"resend_invitation": "Einladung erneut senden",
|
"resend_invitation": "Einladung erneut senden",
|
||||||
"save_profile": "Profil speichern",
|
"save_profile": "Profil speichern",
|
||||||
|
"add_to_store": "Zu einem anderen Shop hinzufügen",
|
||||||
|
"select_store": "Shop auswählen...",
|
||||||
"multiple_roles": "Mehrere Rollen",
|
"multiple_roles": "Mehrere Rollen",
|
||||||
"view_member": "Mitglied anzeigen",
|
"view_member": "Mitglied anzeigen",
|
||||||
"account_information": "Kontoinformationen",
|
"account_information": "Kontoinformationen",
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
"personal_info": "Personal Information",
|
"personal_info": "Personal Information",
|
||||||
"resend_invitation": "Resend Invitation",
|
"resend_invitation": "Resend Invitation",
|
||||||
"save_profile": "Save Profile",
|
"save_profile": "Save Profile",
|
||||||
|
"add_to_store": "Add to another store",
|
||||||
|
"select_store": "Select store...",
|
||||||
"multiple_roles": "Multiple roles",
|
"multiple_roles": "Multiple roles",
|
||||||
"view_member": "View Member",
|
"view_member": "View Member",
|
||||||
"account_information": "Account Information",
|
"account_information": "Account Information",
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
"personal_info": "Informations personnelles",
|
"personal_info": "Informations personnelles",
|
||||||
"resend_invitation": "Renvoyer l'invitation",
|
"resend_invitation": "Renvoyer l'invitation",
|
||||||
"save_profile": "Enregistrer le profil",
|
"save_profile": "Enregistrer le profil",
|
||||||
|
"add_to_store": "Ajouter à un autre magasin",
|
||||||
|
"select_store": "Sélectionner un magasin...",
|
||||||
"multiple_roles": "Rôles multiples",
|
"multiple_roles": "Rôles multiples",
|
||||||
"view_member": "Voir le membre",
|
"view_member": "Voir le membre",
|
||||||
"account_information": "Informations du compte",
|
"account_information": "Informations du compte",
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
"personal_info": "Perséinlech Informatiounen",
|
"personal_info": "Perséinlech Informatiounen",
|
||||||
"resend_invitation": "Aluedung nei schécken",
|
"resend_invitation": "Aluedung nei schécken",
|
||||||
"save_profile": "Profil späicheren",
|
"save_profile": "Profil späicheren",
|
||||||
|
"add_to_store": "Zu engem anere Shop derbäisetzen",
|
||||||
|
"select_store": "Shop auswielen...",
|
||||||
"multiple_roles": "Méi Rollen",
|
"multiple_roles": "Méi Rollen",
|
||||||
"view_member": "Member kucken",
|
"view_member": "Member kucken",
|
||||||
"account_information": "Konto Informatiounen",
|
"account_information": "Konto Informatiounen",
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ function merchantTeam() {
|
|||||||
showViewModal: false,
|
showViewModal: false,
|
||||||
selectedMember: null,
|
selectedMember: null,
|
||||||
|
|
||||||
|
// Add to store form (inside edit modal)
|
||||||
|
addStoreForm: {
|
||||||
|
store_id: '',
|
||||||
|
role_name: 'staff',
|
||||||
|
},
|
||||||
|
|
||||||
// Invite form
|
// Invite form
|
||||||
inviteForm: {
|
inviteForm: {
|
||||||
email: '',
|
email: '',
|
||||||
@@ -186,6 +192,45 @@ function merchantTeam() {
|
|||||||
this.showInviteModal = true;
|
this.showInviteModal = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stores the member is NOT yet in (for "Add to store" in edit modal)
|
||||||
|
*/
|
||||||
|
getAvailableStores(member) {
|
||||||
|
if (!member || !member.stores) return this.stores;
|
||||||
|
const memberStoreIds = member.stores.map(s => s.store_id);
|
||||||
|
return this.stores.filter(s => !memberStoreIds.includes(s.id));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add member to another store (invites them via existing invite API)
|
||||||
|
*/
|
||||||
|
async addMemberToStore(member) {
|
||||||
|
if (!this.addStoreForm.store_id) return;
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
try {
|
||||||
|
await apiClient.post('/merchants/account/team/invite', {
|
||||||
|
email: member.email,
|
||||||
|
store_ids: [parseInt(this.addStoreForm.store_id)],
|
||||||
|
role_name: this.addStoreForm.role_name,
|
||||||
|
});
|
||||||
|
|
||||||
|
Utils.showToast(I18n.t('tenancy.messages.invitation_sent_successfully'), 'success');
|
||||||
|
merchantTeamLog.info('Added member to store:', this.addStoreForm.store_id);
|
||||||
|
|
||||||
|
this.addStoreForm.store_id = '';
|
||||||
|
this.addStoreForm.role_name = 'staff';
|
||||||
|
this.showEditModal = false;
|
||||||
|
this.selectedMember = null;
|
||||||
|
await this.loadTeamData();
|
||||||
|
} catch (error) {
|
||||||
|
merchantTeamLog.error('Failed to add member to store:', error);
|
||||||
|
Utils.showToast(error.message || 'Failed to add member to store', 'error');
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle store in invite form store_ids
|
* Toggle store in invite form store_ids
|
||||||
*/
|
*/
|
||||||
@@ -242,6 +287,7 @@ function merchantTeam() {
|
|||||||
*/
|
*/
|
||||||
openEditModal(member) {
|
openEditModal(member) {
|
||||||
this.selectedMember = JSON.parse(JSON.stringify(member));
|
this.selectedMember = JSON.parse(JSON.stringify(member));
|
||||||
|
this.addStoreForm = { store_id: '', role_name: 'staff' };
|
||||||
this.showEditModal = true;
|
this.showEditModal = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -137,11 +137,18 @@
|
|||||||
<span x-html="$icon('eye', 'w-4 h-4')"></span>
|
<span x-html="$icon('eye', 'w-4 h-4')"></span>
|
||||||
</button>
|
</button>
|
||||||
<template x-if="!member.is_owner">
|
<template x-if="!member.is_owner">
|
||||||
<button @click="openEditModal(member)"
|
<div class="flex items-center gap-2">
|
||||||
class="p-1.5 text-gray-500 hover:text-purple-600 dark:text-gray-400 dark:hover:text-purple-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
<button @click="openEditModal(member)"
|
||||||
:title="$t('tenancy.team.edit_member')">
|
class="p-1.5 text-gray-500 hover:text-purple-600 dark:text-gray-400 dark:hover:text-purple-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||||
<span x-html="$icon('pencil', 'w-4 h-4')"></span>
|
:title="$t('tenancy.team.edit_member')">
|
||||||
</button>
|
<span x-html="$icon('pencil', 'w-4 h-4')"></span>
|
||||||
|
</button>
|
||||||
|
<button @click="openRemoveModal(member)"
|
||||||
|
class="p-1.5 text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||||
|
:title="$t('tenancy.team.remove_member')">
|
||||||
|
<span x-html="$icon('trash', 'w-4 h-4')"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="member.is_owner">
|
<template x-if="member.is_owner">
|
||||||
<span class="inline-flex items-center px-2 py-1 text-xs text-purple-600 dark:text-purple-400">
|
<span class="inline-flex items-center px-2 py-1 text-xs text-purple-600 dark:text-purple-400">
|
||||||
@@ -347,6 +354,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- Add to another store -->
|
||||||
|
<template x-if="getAvailableStores(selectedMember).length > 0">
|
||||||
|
<div class="p-3 border border-dashed border-gray-300 dark:border-gray-600 rounded-lg">
|
||||||
|
<h5 class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">{{ _('tenancy.team.add_to_store') }}</h5>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<select x-model="addStoreForm.store_id"
|
||||||
|
class="flex-1 px-2 py-1 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none">
|
||||||
|
<option value="">{{ _('tenancy.team.select_store') }}</option>
|
||||||
|
<template x-for="s in getAvailableStores(selectedMember)" :key="s.id">
|
||||||
|
<option :value="s.id" x-text="s.name"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
<select x-model="addStoreForm.role_name"
|
||||||
|
class="px-2 py-1 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none">
|
||||||
|
<template x-for="role in roleOptions" :key="role.value">
|
||||||
|
<option :value="role.value" x-text="role.label"></option>
|
||||||
|
</template>
|
||||||
|
</select>
|
||||||
|
<button @click="addMemberToStore(selectedMember)"
|
||||||
|
:disabled="saving || !addStoreForm.store_id"
|
||||||
|
class="px-3 py-1 text-xs font-medium text-white bg-purple-600 rounded-md hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
||||||
|
<span x-show="saving" x-html="$icon('spinner', 'w-3 h-3')"></span>
|
||||||
|
<span x-show="!saving" x-html="$icon('plus', 'w-3 h-3')"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Remove from all stores link -->
|
<!-- Remove from all stores link -->
|
||||||
<div x-show="selectedMember?.stores?.length > 1" class="text-center pt-2">
|
<div x-show="selectedMember?.stores?.length > 1" class="text-center pt-2">
|
||||||
<button @click="showEditModal = false; openRemoveModal(selectedMember)"
|
<button @click="showEditModal = false; openRemoveModal(selectedMember)"
|
||||||
|
|||||||
Reference in New Issue
Block a user