fix(tenancy): align columns and actions in merchant team table
All checks were successful
All checks were successful
- Fixed header/column alignment: Member | Role | Status | Actions - Store count + chevron moved inline with member name (not a separate column) - Role column shows single role, "Owner", or "Multiple roles" on main row - Actions use fixed 4-slot grid (resend | view | edit | remove) ensuring icons always align vertically between main rows and sub-rows - Empty slots render as blank space to maintain alignment i18n: added multiple_roles key in 4 locales. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,7 @@
|
||||
"personal_info": "Persönliche Informationen",
|
||||
"resend_invitation": "Einladung erneut senden",
|
||||
"save_profile": "Profil speichern",
|
||||
"multiple_roles": "Mehrere Rollen",
|
||||
"view_member": "Mitglied anzeigen",
|
||||
"account_information": "Kontoinformationen",
|
||||
"username": "Benutzername",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"personal_info": "Personal Information",
|
||||
"resend_invitation": "Resend Invitation",
|
||||
"save_profile": "Save Profile",
|
||||
"multiple_roles": "Multiple roles",
|
||||
"view_member": "View Member",
|
||||
"account_information": "Account Information",
|
||||
"username": "Username",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"personal_info": "Informations personnelles",
|
||||
"resend_invitation": "Renvoyer l'invitation",
|
||||
"save_profile": "Enregistrer le profil",
|
||||
"multiple_roles": "Rôles multiples",
|
||||
"view_member": "Voir le membre",
|
||||
"account_information": "Informations du compte",
|
||||
"username": "Nom d'utilisateur",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"personal_info": "Perséinlech Informatiounen",
|
||||
"resend_invitation": "Aluedung nei schécken",
|
||||
"save_profile": "Profil späicheren",
|
||||
"multiple_roles": "Méi Rollen",
|
||||
"view_member": "Member kucken",
|
||||
"account_information": "Konto Informatiounen",
|
||||
"username": "Benotzernumm",
|
||||
|
||||
@@ -78,7 +78,14 @@
|
||||
<!-- Members Table -->
|
||||
<div x-show="filteredMembers.length > 0">
|
||||
{% call table_wrapper() %}
|
||||
{{ table_header([_('tenancy.team.member'), _('tenancy.team.stores_and_roles'), _('tenancy.team.status'), _('tenancy.team.actions')]) }}
|
||||
<thead>
|
||||
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
|
||||
<th class="px-4 py-3">{{ _('tenancy.team.member') }}</th>
|
||||
<th class="px-4 py-3 w-36">{{ _('tenancy.team.role') }}</th>
|
||||
<th class="px-4 py-3 w-28">{{ _('tenancy.team.status') }}</th>
|
||||
<th class="px-4 py-3 w-40">{{ _('tenancy.team.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<template x-for="member in filteredMembers" :key="member.user_id">
|
||||
<tbody class="divide-y dark:divide-gray-700">
|
||||
@@ -94,57 +101,73 @@
|
||||
<span class="text-xs font-semibold" x-text="getInitials(member)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex-1">
|
||||
<p class="font-semibold text-gray-800 dark:text-gray-200"
|
||||
x-text="member.full_name"></p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400" x-text="member.email"></p>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 ml-2 text-xs text-gray-400">
|
||||
<span x-html="$icon(expandedMembers.includes(member.user_id) ? 'chevron-up' : 'chevron-down', 'w-4 h-4')"></span>
|
||||
<span x-text="member.stores.length + ' store' + (member.stores.length !== 1 ? 's' : '')"></span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{# Store count summary #}
|
||||
<td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div class="flex items-center gap-1">
|
||||
<span x-html="$icon(expandedMembers.includes(member.user_id) ? 'chevron-up' : 'chevron-down', 'w-4 h-4 text-gray-400')"></span>
|
||||
<span x-text="member.stores.length + ' store' + (member.stores.length !== 1 ? 's' : '')"></span>
|
||||
</div>
|
||||
{# Role (summary) #}
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<template x-if="member.is_owner">
|
||||
<span class="text-xs text-purple-600 dark:text-purple-400 font-medium">Owner</span>
|
||||
</template>
|
||||
<template x-if="!member.is_owner && member.stores.length === 1">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400" x-text="member.stores[0].role_name"></span>
|
||||
</template>
|
||||
<template x-if="!member.is_owner && member.stores.length > 1">
|
||||
<span class="text-xs text-gray-400">{{ _('tenancy.team.multiple_roles') }}</span>
|
||||
</template>
|
||||
</td>
|
||||
|
||||
{# Overall status #}
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<template x-if="member.is_owner">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">
|
||||
<span x-html="$icon('shield-check', 'w-3 h-3 mr-1')"></span>
|
||||
{{ _('tenancy.team.owner') }}
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!member.is_owner && getMemberStatus(member) === 'active'">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||
{{ _('common.active') }}
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!member.is_owner && getMemberStatus(member) === 'pending'">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200">
|
||||
{{ _('common.pending') }}
|
||||
</span>
|
||||
</template>
|
||||
</td>
|
||||
|
||||
{# Member-level actions #}
|
||||
{# Member-level actions: fixed 4-slot grid (resend | view | edit | remove) #}
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<div class="flex items-center gap-2" @click.stop>
|
||||
<div class="grid grid-cols-4 gap-1 w-32" @click.stop>
|
||||
{# Slot 1: resend (empty at member level) #}
|
||||
<span></span>
|
||||
{# Slot 2: view #}
|
||||
<button @click="openViewModal(member)"
|
||||
class="p-1.5 text-gray-500 hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
class="p-1 text-gray-500 hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
|
||||
:title="$t('tenancy.team.view_member')">
|
||||
<span x-html="$icon('eye', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
{# Slot 3: edit #}
|
||||
<template x-if="!member.is_owner">
|
||||
<button @click="openEditModal(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"
|
||||
class="p-1 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 transition-colors"
|
||||
:title="$t('tenancy.team.edit_member')">
|
||||
<span x-html="$icon('pencil', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template x-if="member.is_owner"><span></span></template>
|
||||
{# Slot 4: remove (empty at member level — done per-store) #}
|
||||
<span></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -154,10 +177,10 @@
|
||||
<tr x-show="expandedMembers.includes(member.user_id)"
|
||||
x-transition
|
||||
class="bg-gray-50 dark:bg-gray-900/50 text-gray-600 dark:text-gray-400">
|
||||
{# Indent + Store name #}
|
||||
{# Store name (indented) #}
|
||||
<td class="px-4 py-2 pl-16">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span x-html="$icon('shopping-bag', 'w-4 h-4 text-gray-400')"></span>
|
||||
<span x-html="$icon('shopping-bag', 'w-3.5 h-3.5 text-gray-400')"></span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="store.store_name"></span>
|
||||
<span class="text-xs text-gray-400 font-mono" x-text="store.store_code"></span>
|
||||
</div>
|
||||
@@ -183,24 +206,33 @@
|
||||
</template>
|
||||
</td>
|
||||
|
||||
{# Per-store actions #}
|
||||
{# Per-store actions: same 4-slot grid (resend | view | edit | remove) #}
|
||||
<td class="px-4 py-2 text-sm">
|
||||
<div class="flex items-center gap-2" x-show="!member.is_owner">
|
||||
{# Resend — pending only #}
|
||||
<button x-show="store.is_pending"
|
||||
@click="resendStoreInvitation(store.store_id, member.user_id)"
|
||||
:disabled="saving"
|
||||
class="p-1 text-gray-400 hover:text-green-600 dark:hover:text-green-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
|
||||
:title="$t('tenancy.team.resend_invitation')">
|
||||
<span x-html="$icon('paper-airplane', 'w-3.5 h-3.5')"></span>
|
||||
</button>
|
||||
{# Remove from this store #}
|
||||
<button @click="removeMember(store.store_id, member.user_id)"
|
||||
:disabled="saving"
|
||||
class="p-1 text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
|
||||
:title="$t('tenancy.team.remove_member')">
|
||||
<span x-html="$icon('x-circle', 'w-3.5 h-3.5')"></span>
|
||||
</button>
|
||||
<div class="grid grid-cols-4 gap-1 w-32">
|
||||
{# Slot 1: resend (pending only) #}
|
||||
<template x-if="store.is_pending && !member.is_owner">
|
||||
<button @click="resendStoreInvitation(store.store_id, member.user_id)"
|
||||
:disabled="saving"
|
||||
class="p-1 text-gray-400 hover:text-green-600 dark:hover:text-green-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
|
||||
:title="$t('tenancy.team.resend_invitation')">
|
||||
<span x-html="$icon('paper-airplane', 'w-3.5 h-3.5')"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template x-if="!store.is_pending || member.is_owner"><span></span></template>
|
||||
{# Slot 2: view (empty at store level) #}
|
||||
<span></span>
|
||||
{# Slot 3: edit (empty at store level) #}
|
||||
<span></span>
|
||||
{# Slot 4: remove from store #}
|
||||
<template x-if="!member.is_owner">
|
||||
<button @click="removeMember(store.store_id, member.user_id)"
|
||||
:disabled="saving"
|
||||
class="p-1 text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded transition-colors"
|
||||
:title="$t('tenancy.team.remove_member')">
|
||||
<span x-html="$icon('x-circle', 'w-3.5 h-3.5')"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template x-if="member.is_owner"><span></span></template>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user