feat(frontend) : ERP-27 - admin users page with DataTable and RBAC drawer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
126
frontend/modules/core/pages/admin/users.vue
Normal file
126
frontend/modules/core/pages/admin/users.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- En-tete -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">
|
||||
{{ t('admin.users.title') }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Table des utilisateurs -->
|
||||
<MalioDataTable
|
||||
class="mt-6"
|
||||
:columns="columns"
|
||||
:items="userItems"
|
||||
:total-items="users.length"
|
||||
:row-clickable="canManage"
|
||||
:empty-message="t('admin.users.noUsers')"
|
||||
@row-click="onRowClick"
|
||||
>
|
||||
<template #cell-admin="{ item }">
|
||||
<span
|
||||
v-if="item.admin"
|
||||
class="inline-flex items-center rounded-full bg-purple-100 px-2.5 py-0.5 text-xs font-medium text-purple-800"
|
||||
>
|
||||
{{ t('admin.users.table.admin') }}
|
||||
</span>
|
||||
</template>
|
||||
<template #cell-actions="{ item }">
|
||||
<div class="flex items-center justify-end gap-2" @click.stop>
|
||||
<MalioButtonIcon
|
||||
v-if="canManage"
|
||||
icon="mdi:shield-edit-outline"
|
||||
:aria-label="t('common.edit')"
|
||||
variant="ghost"
|
||||
@click="openDrawer(getUserById(item.id as number)!)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</MalioDataTable>
|
||||
|
||||
<!-- Drawer RBAC -->
|
||||
<UserRbacDrawer
|
||||
v-model="drawerOpen"
|
||||
:user="selectedUser"
|
||||
@saved="onUserSaved"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface UserListItem {
|
||||
id: number
|
||||
username: string
|
||||
isAdmin: boolean
|
||||
roles: string[]
|
||||
directPermissions: string[]
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApi()
|
||||
const { can } = usePermissions()
|
||||
|
||||
useHead({ title: t('admin.users.title') })
|
||||
|
||||
const canManage = can('core.users.manage')
|
||||
|
||||
const users = ref<UserListItem[]>([])
|
||||
const loading = ref(false)
|
||||
const drawerOpen = ref(false)
|
||||
const selectedUser = ref<UserListItem | null>(null)
|
||||
|
||||
const columns = [
|
||||
{ key: 'username', label: t('admin.users.table.username') },
|
||||
{ key: 'admin', label: t('admin.users.table.admin') },
|
||||
{ key: 'roles', label: t('admin.users.table.roles') },
|
||||
{ key: 'directPermissions', label: t('admin.users.table.directPermissions') },
|
||||
{ key: 'actions', label: t('admin.users.table.actions') },
|
||||
]
|
||||
|
||||
const userItems = computed(() =>
|
||||
users.value.map(user => ({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
admin: user.isAdmin,
|
||||
roles: user.roles.length,
|
||||
directPermissions: user.directPermissions.length,
|
||||
actions: '',
|
||||
}))
|
||||
)
|
||||
|
||||
async function loadUsers() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<{ member: UserListItem[] }>(
|
||||
'/users',
|
||||
{},
|
||||
{ toast: false },
|
||||
)
|
||||
users.value = data.member
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getUserById(id: number): UserListItem | undefined {
|
||||
return users.value.find(u => u.id === id)
|
||||
}
|
||||
|
||||
function openDrawer(user: UserListItem) {
|
||||
selectedUser.value = user
|
||||
drawerOpen.value = true
|
||||
}
|
||||
|
||||
function onRowClick(item: Record<string, unknown>) {
|
||||
const user = getUserById(item.id as number)
|
||||
if (user) openDrawer(user)
|
||||
}
|
||||
|
||||
function onUserSaved() {
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUsers()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user