Supprime la colonne actions des tables users et roles (la ligne cliquable ouvre deja le drawer). Deplace la suppression d'un role dans le drawer d'edition (bouton danger avec icone, desactive pour les roles systeme). Harmonise les boutons annuler en variant tertiary et ajoute les icones manquantes (plus pour nouveau role, poubelle pour supprimer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
225 lines
6.9 KiB
Vue
225 lines
6.9 KiB
Vue
<template>
|
|
<MalioDrawer
|
|
:model-value="modelValue"
|
|
:title="isEditMode ? t('admin.roles.editRole') : t('admin.roles.createRole')"
|
|
drawer-class="w-full max-w-lg"
|
|
@update:model-value="emit('update:modelValue', $event)"
|
|
>
|
|
<form class="flex flex-col gap-6 p-4" @submit.prevent="handleSave">
|
|
<!-- Champs du role -->
|
|
<MalioInputText
|
|
v-model="form.label"
|
|
:label="t('admin.roles.form.label')"
|
|
input-class="w-full"
|
|
required
|
|
/>
|
|
|
|
<MalioInputText
|
|
v-model="form.code"
|
|
:label="t('admin.roles.form.code')"
|
|
input-class="w-full"
|
|
required
|
|
:readonly="isEditMode"
|
|
/>
|
|
|
|
<MalioInputTextArea
|
|
v-model="form.description"
|
|
:label="t('admin.roles.form.description')"
|
|
input-class="w-full"
|
|
/>
|
|
|
|
<!-- Permissions groupees par module -->
|
|
<div>
|
|
<h4 class="mb-3 text-sm font-semibold text-neutral-700">
|
|
{{ t('admin.roles.form.permissions') }}
|
|
</h4>
|
|
<div v-if="permissionsByModule.length === 0" class="text-sm text-neutral-400">
|
|
{{ t('admin.roles.permissions.noPermissions') }}
|
|
</div>
|
|
<div class="flex flex-col gap-4">
|
|
<PermissionGroup
|
|
v-for="group in permissionsByModule"
|
|
:key="group.module"
|
|
:module="group.module"
|
|
:module-label="group.module"
|
|
:permissions="group.permissions"
|
|
:selected-ids="selectedPermissionIds"
|
|
@toggle="handleTogglePermission"
|
|
@toggle-all="handleToggleAll"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Boutons -->
|
|
<div class="flex justify-end gap-3 border-t border-neutral-200 pt-4">
|
|
<MalioButton
|
|
v-if="isEditMode"
|
|
:label="t('common.delete')"
|
|
variant="danger"
|
|
icon-name="mdi:delete-outline"
|
|
icon-position="left"
|
|
:disabled="role?.isSystem"
|
|
@click="emit('delete')"
|
|
/>
|
|
<MalioButton
|
|
v-else
|
|
:label="t('common.cancel')"
|
|
variant="tertiary"
|
|
@click="emit('update:modelValue', false)"
|
|
/>
|
|
<MalioButton
|
|
:label="t('common.save')"
|
|
variant="primary"
|
|
:disabled="saving"
|
|
@click="handleSave"
|
|
/>
|
|
</div>
|
|
</form>
|
|
</MalioDrawer>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Permission, Role } from '~/shared/types/rbac'
|
|
|
|
interface PermissionModule {
|
|
module: string
|
|
permissions: Permission[]
|
|
}
|
|
|
|
const { t } = useI18n()
|
|
const api = useApi()
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
role: Role | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean]
|
|
saved: []
|
|
delete: []
|
|
}>()
|
|
|
|
const saving = ref(false)
|
|
const allPermissions = ref<Permission[]>([])
|
|
|
|
const form = ref({
|
|
label: '',
|
|
code: '',
|
|
description: '',
|
|
})
|
|
|
|
const selectedPermissionIds = ref(new Set<number>())
|
|
|
|
const isEditMode = computed(() => props.role !== null)
|
|
|
|
// Grouper les permissions par module
|
|
const permissionsByModule = computed<PermissionModule[]>(() => {
|
|
const groups = new Map<string, Permission[]>()
|
|
for (const perm of allPermissions.value) {
|
|
if (perm.orphan) continue
|
|
const list = groups.get(perm.module) || []
|
|
list.push(perm)
|
|
groups.set(perm.module, list)
|
|
}
|
|
return Array.from(groups.entries())
|
|
.map(([module, permissions]) => ({ module, permissions }))
|
|
.sort((a, b) => a.module.localeCompare(b.module))
|
|
})
|
|
|
|
// Charger les permissions au montage
|
|
async function loadPermissions() {
|
|
const data = await api.get<{ member: Permission[] }>(
|
|
'/permissions',
|
|
{ 'orphan': false, itemsPerPage: 999 },
|
|
{ toast: false },
|
|
)
|
|
allPermissions.value = data.member
|
|
}
|
|
|
|
// Remplir le formulaire quand le role change
|
|
watch(() => props.role, (role) => {
|
|
if (role) {
|
|
form.value.label = role.label
|
|
form.value.code = role.code
|
|
form.value.description = role.description || ''
|
|
selectedPermissionIds.value = new Set(role.permissions.map(p => {
|
|
// L'API peut retourner des objets Permission ou des IRIs string
|
|
if (typeof p === 'string') {
|
|
return Number(p.split('/').pop())
|
|
}
|
|
return p.id
|
|
}))
|
|
} else {
|
|
form.value.label = ''
|
|
form.value.code = ''
|
|
form.value.description = ''
|
|
selectedPermissionIds.value = new Set()
|
|
}
|
|
}, { immediate: true })
|
|
|
|
// Charger les permissions quand le drawer s'ouvre
|
|
watch(() => props.modelValue, (open) => {
|
|
if (open) loadPermissions()
|
|
})
|
|
|
|
// Basculer une permission individuelle
|
|
function handleTogglePermission(id: number, selected: boolean) {
|
|
const ids = new Set(selectedPermissionIds.value)
|
|
if (selected) {
|
|
ids.add(id)
|
|
} else {
|
|
ids.delete(id)
|
|
}
|
|
selectedPermissionIds.value = ids
|
|
}
|
|
|
|
// Basculer toutes les permissions d'un module
|
|
function handleToggleAll(module: string, selected: boolean) {
|
|
const ids = new Set(selectedPermissionIds.value)
|
|
const group = permissionsByModule.value.find(g => g.module === module)
|
|
if (!group) return
|
|
for (const perm of group.permissions) {
|
|
if (selected) {
|
|
ids.add(perm.id)
|
|
} else {
|
|
ids.delete(perm.id)
|
|
}
|
|
}
|
|
selectedPermissionIds.value = ids
|
|
}
|
|
|
|
// Sauvegarder le role (creation ou edition)
|
|
async function handleSave() {
|
|
saving.value = true
|
|
try {
|
|
const permissions = Array.from(selectedPermissionIds.value).map(id => `/api/permissions/${id}`)
|
|
|
|
if (isEditMode.value && props.role) {
|
|
// Le code est immuable apres creation (garde backend RoleProcessor)
|
|
await api.patch(`/roles/${props.role.id}`, {
|
|
label: form.value.label,
|
|
description: form.value.description || null,
|
|
permissions,
|
|
}, {
|
|
toastSuccessMessage: t('admin.roles.toast.updated'),
|
|
})
|
|
} else {
|
|
await api.post('/roles', {
|
|
label: form.value.label,
|
|
code: form.value.code,
|
|
description: form.value.description || null,
|
|
permissions,
|
|
}, {
|
|
toastSuccessMessage: t('admin.roles.toast.created'),
|
|
})
|
|
}
|
|
|
|
emit('saved')
|
|
emit('update:modelValue', false)
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
</script>
|