feat(frontend) : ERP-26 - admin roles page with table, drawer, delete modal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
184
frontend/modules/core/pages/admin/roles.vue
Normal file
184
frontend/modules/core/pages/admin/roles.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<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.roles.title') }}
|
||||
</h1>
|
||||
<MalioButton
|
||||
v-if="can('core.roles.manage')"
|
||||
:label="t('admin.roles.newRole')"
|
||||
icon-name="mdi:plus"
|
||||
@click="openCreateDrawer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Table des roles -->
|
||||
<div class="mt-6 overflow-x-auto rounded-lg border border-neutral-200">
|
||||
<table v-if="roles.length > 0" class="w-full text-left text-sm">
|
||||
<thead class="bg-neutral-50 text-xs uppercase text-neutral-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">{{ t('admin.roles.table.label') }}</th>
|
||||
<th class="px-4 py-3">{{ t('admin.roles.table.code') }}</th>
|
||||
<th class="px-4 py-3 text-center">{{ t('admin.roles.table.permissions') }}</th>
|
||||
<th class="px-4 py-3 text-center">{{ t('admin.roles.table.system') }}</th>
|
||||
<th class="px-4 py-3 text-right">{{ t('admin.roles.table.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-neutral-100">
|
||||
<tr
|
||||
v-for="role in roles"
|
||||
:key="role.id"
|
||||
class="cursor-pointer hover:bg-neutral-50 transition-colors"
|
||||
@click="openEditDrawer(role)"
|
||||
>
|
||||
<td class="px-4 py-3 font-medium text-neutral-900">
|
||||
{{ role.label }}
|
||||
</td>
|
||||
<td class="px-4 py-3 font-mono text-xs text-neutral-500">
|
||||
{{ role.code }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center text-neutral-600">
|
||||
{{ role.permissions.length }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<span
|
||||
v-if="role.isSystem"
|
||||
class="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800"
|
||||
>
|
||||
{{ t('admin.roles.table.system') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right" @click.stop>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<MalioButtonIcon
|
||||
v-if="can('core.roles.manage')"
|
||||
icon="mdi:pencil-outline"
|
||||
:aria-label="t('common.edit')"
|
||||
variant="ghost"
|
||||
@click="openEditDrawer(role)"
|
||||
/>
|
||||
<MalioButtonIcon
|
||||
v-if="can('core.roles.manage')"
|
||||
icon="mdi:delete-outline"
|
||||
:aria-label="t('common.delete')"
|
||||
variant="ghost"
|
||||
:disabled="role.isSystem"
|
||||
@click="confirmDelete(role)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Etat vide -->
|
||||
<div v-else class="flex flex-col items-center justify-center py-12 text-neutral-400">
|
||||
<Icon name="mdi:shield-off-outline" class="mb-3 size-12" />
|
||||
<p class="text-sm">{{ t('admin.roles.noRoles') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drawer creation/edition -->
|
||||
<RoleDrawer
|
||||
v-model="drawerOpen"
|
||||
:role="selectedRole"
|
||||
@saved="onRoleSaved"
|
||||
/>
|
||||
|
||||
<!-- Modale de suppression -->
|
||||
<RoleDeleteModal
|
||||
v-model="deleteModalOpen"
|
||||
:role-label="roleToDelete?.label ?? ''"
|
||||
:loading="deleting"
|
||||
@confirm="handleDelete"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Permission {
|
||||
id: number
|
||||
code: string
|
||||
label: string
|
||||
module: string
|
||||
orphan: boolean
|
||||
}
|
||||
|
||||
interface Role {
|
||||
id: number
|
||||
code: string
|
||||
label: string
|
||||
description: string | null
|
||||
isSystem: boolean
|
||||
permissions: Permission[]
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApi()
|
||||
const { can } = usePermissions()
|
||||
|
||||
useHead({ title: t('admin.roles.title') })
|
||||
|
||||
const roles = ref<Role[]>([])
|
||||
const loading = ref(false)
|
||||
const drawerOpen = ref(false)
|
||||
const selectedRole = ref<Role | null>(null)
|
||||
const deleteModalOpen = ref(false)
|
||||
const roleToDelete = ref<Role | null>(null)
|
||||
const deleting = ref(false)
|
||||
|
||||
// Charger la liste des roles
|
||||
async function loadRoles() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<{ 'hydra:member': Role[] }>(
|
||||
'/roles',
|
||||
{},
|
||||
{ toast: false },
|
||||
)
|
||||
roles.value = data['hydra:member']
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateDrawer() {
|
||||
selectedRole.value = null
|
||||
drawerOpen.value = true
|
||||
}
|
||||
|
||||
function openEditDrawer(role: Role) {
|
||||
selectedRole.value = role
|
||||
drawerOpen.value = true
|
||||
}
|
||||
|
||||
function confirmDelete(role: Role) {
|
||||
if (role.isSystem) return
|
||||
roleToDelete.value = role
|
||||
deleteModalOpen.value = true
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
if (!roleToDelete.value) return
|
||||
deleting.value = true
|
||||
try {
|
||||
await api.delete(`/roles/${roleToDelete.value.id}`, {}, {
|
||||
toastSuccessMessage: t('admin.roles.toast.deleted'),
|
||||
})
|
||||
deleteModalOpen.value = false
|
||||
roleToDelete.value = null
|
||||
await loadRoles()
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onRoleSaved() {
|
||||
loadRoles()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadRoles()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user