Files
Coltura/frontend/modules/core/pages/admin/roles.vue
2026-04-16 09:20:10 +02:00

185 lines
6.1 KiB
Vue

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