refactor(core) : reponse aux retours de review
- extrait PermissionAccordion (modules/core/components) : source unique pour l'accordeon de permissions par module, utilise par RoleDrawer et UserRbacDrawer (supprime la duplication selectedCountFor/directSelectedCount + ~25 lignes de markup) - expose le type PermissionModule dans shared/types/rbac - audit-log : simplifie toggleEntity (filter au lieu de Set, valeurs uniques par construction) - default.vue : commente les valeurs en dur (232/170/47) comme issues de la maquette Figma - audit-log : corrige iconSize -> icon-size (warning eslint vue/attribute-hyphenation)
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<!-- Accordeon de permissions groupees par module : un panneau par module,
|
||||
avec compteur (selectionnees/total) dans le titre, case "Tout selectionner"
|
||||
et liste des permissions individuelles. Source unique de cette UX, utilisee
|
||||
par RoleDrawer (permissions du role) et UserRbacDrawer (permissions directes). -->
|
||||
<MalioAccordion v-model="openModules">
|
||||
<MalioAccordionItem
|
||||
v-for="group in groupsByModule"
|
||||
:key="group.module"
|
||||
:value="group.module"
|
||||
:title="`${group.module} (${selectedCountFor(group)}/${group.permissions.length})`"
|
||||
header-class="capitalize"
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
<!-- Tout selectionner pour ce module -->
|
||||
<MalioCheckbox
|
||||
:id="`${idPrefix}-group-${group.module}`"
|
||||
:label="t('admin.roles.permissions.selectAll')"
|
||||
:model-value="allSelectedFor(group)"
|
||||
label-class="font-semibold text-sm text-neutral-700"
|
||||
@update:model-value="(val: boolean) => emit('toggle-all', group.module, val)"
|
||||
/>
|
||||
<div class="flex flex-col gap-2">
|
||||
<MalioCheckbox
|
||||
v-for="perm in group.permissions"
|
||||
:id="`${idPrefix}-perm-${perm.id}`"
|
||||
:key="perm.id"
|
||||
:label="perm.label"
|
||||
:model-value="selectedIds.has(perm.id)"
|
||||
label-class="text-sm text-neutral-600"
|
||||
@update:model-value="(val: boolean) => emit('toggle', perm.id, val)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MalioAccordionItem>
|
||||
</MalioAccordion>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PermissionModule } from '~/shared/types/rbac'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
/** Groupes de permissions a afficher, un par module. */
|
||||
groupsByModule: PermissionModule[]
|
||||
/** Ids des permissions actuellement selectionnees. */
|
||||
selectedIds: Set<number>
|
||||
/** Prefixe pour les ids HTML : evite les collisions si plusieurs accordeons coexistent (ex: "role" vs "direct"). */
|
||||
idPrefix: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggle: [permissionId: number, selected: boolean]
|
||||
'toggle-all': [module: string, selected: boolean]
|
||||
}>()
|
||||
|
||||
// Modules ouverts dans l'accordeon (mode multiple). Etat local : chaque instance
|
||||
// du composant garde sa propre liste, pas de partage entre drawers.
|
||||
const openModules = ref<string[]>([])
|
||||
|
||||
// Nombre de permissions selectionnees pour un module donne.
|
||||
function selectedCountFor(group: PermissionModule): number {
|
||||
return group.permissions.filter(p => props.selectedIds.has(p.id)).length
|
||||
}
|
||||
|
||||
// Vrai si toutes les permissions du module sont selectionnees.
|
||||
function allSelectedFor(group: PermissionModule): boolean {
|
||||
return group.permissions.length > 0 && selectedCountFor(group) === group.permissions.length
|
||||
}
|
||||
</script>
|
||||
@@ -50,39 +50,14 @@
|
||||
<div v-else-if="permissionsByModule.length === 0" class="text-sm text-neutral-400">
|
||||
{{ t('admin.roles.permissions.noPermissions') }}
|
||||
</div>
|
||||
<!-- Un panneau d'accordeon par module (mode multiple) ; le compteur
|
||||
selectionnees/total reste visible dans l'en-tete replie. -->
|
||||
<MalioAccordion v-else v-model="openModules">
|
||||
<MalioAccordionItem
|
||||
v-for="group in permissionsByModule"
|
||||
:key="group.module"
|
||||
:value="group.module"
|
||||
:title="`${group.module} (${selectedCountFor(group)}/${group.permissions.length})`"
|
||||
header-class="capitalize"
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
<!-- Tout selectionner pour ce module -->
|
||||
<MalioCheckbox
|
||||
:id="`role-group-${group.module}`"
|
||||
:label="t('admin.roles.permissions.selectAll')"
|
||||
:model-value="allSelectedFor(group)"
|
||||
label-class="font-semibold text-sm text-neutral-700"
|
||||
@update:model-value="(val: boolean) => handleToggleAll(group.module, val)"
|
||||
/>
|
||||
<div class="flex flex-col gap-2">
|
||||
<MalioCheckbox
|
||||
v-for="perm in group.permissions"
|
||||
:id="`role-perm-${perm.id}`"
|
||||
:key="perm.id"
|
||||
:label="perm.label"
|
||||
:model-value="selectedPermissionIds.has(perm.id)"
|
||||
label-class="text-sm text-neutral-600"
|
||||
@update:model-value="(val: boolean) => handleTogglePermission(perm.id, val)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MalioAccordionItem>
|
||||
</MalioAccordion>
|
||||
<PermissionAccordion
|
||||
v-else
|
||||
:groups-by-module="permissionsByModule"
|
||||
:selected-ids="selectedPermissionIds"
|
||||
id-prefix="role"
|
||||
@toggle="handleTogglePermission"
|
||||
@toggle-all="handleToggleAll"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -119,12 +94,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Permission, Role } from '~/shared/types/rbac'
|
||||
|
||||
interface PermissionModule {
|
||||
module: string
|
||||
permissions: Permission[]
|
||||
}
|
||||
import type { Permission, PermissionModule, Role } from '~/shared/types/rbac'
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApi()
|
||||
@@ -156,19 +126,6 @@ const form = ref({
|
||||
|
||||
const selectedPermissionIds = ref(new Set<number>())
|
||||
|
||||
// Modules ouverts dans l'accordeon des permissions (mode multiple)
|
||||
const openModules = ref<string[]>([])
|
||||
|
||||
// Nombre de permissions selectionnees pour un module donne
|
||||
function selectedCountFor(group: PermissionModule): number {
|
||||
return group.permissions.filter(p => selectedPermissionIds.value.has(p.id)).length
|
||||
}
|
||||
|
||||
// Vrai si toutes les permissions du module sont selectionnees
|
||||
function allSelectedFor(group: PermissionModule): boolean {
|
||||
return group.permissions.length > 0 && selectedCountFor(group) === group.permissions.length
|
||||
}
|
||||
|
||||
const isEditMode = computed(() => props.role !== null)
|
||||
|
||||
// Grouper les permissions par module
|
||||
|
||||
@@ -66,39 +66,14 @@
|
||||
<div v-if="permissionsByModule.length === 0" class="text-sm text-neutral-400">
|
||||
{{ t('admin.roles.permissions.noPermissions') }}
|
||||
</div>
|
||||
<!-- Un panneau d'accordeon par module (mode multiple) ; le compteur
|
||||
selectionnees/total reste visible dans l'en-tete replie. -->
|
||||
<MalioAccordion v-else v-model="openDirectModules">
|
||||
<MalioAccordionItem
|
||||
v-for="group in permissionsByModule"
|
||||
:key="group.module"
|
||||
:value="group.module"
|
||||
:title="`${group.module} (${directSelectedCount(group)}/${group.permissions.length})`"
|
||||
header-class="capitalize"
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
<!-- Tout selectionner pour ce module -->
|
||||
<MalioCheckbox
|
||||
:id="`direct-group-${group.module}`"
|
||||
:label="t('admin.roles.permissions.selectAll')"
|
||||
:model-value="directAllSelected(group)"
|
||||
label-class="font-semibold text-sm text-neutral-700"
|
||||
@update:model-value="(val: boolean) => handleToggleAll(group.module, val)"
|
||||
/>
|
||||
<div class="flex flex-col gap-2">
|
||||
<MalioCheckbox
|
||||
v-for="perm in group.permissions"
|
||||
:id="`direct-perm-${perm.id}`"
|
||||
:key="perm.id"
|
||||
:label="perm.label"
|
||||
:model-value="selectedDirectPermissionIds.has(perm.id)"
|
||||
label-class="text-sm text-neutral-600"
|
||||
@update:model-value="(val: boolean) => handleTogglePermission(perm.id, val)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MalioAccordionItem>
|
||||
</MalioAccordion>
|
||||
<PermissionAccordion
|
||||
v-else
|
||||
:groups-by-module="permissionsByModule"
|
||||
:selected-ids="selectedDirectPermissionIds"
|
||||
id-prefix="direct"
|
||||
@toggle="handleTogglePermission"
|
||||
@toggle-all="handleToggleAll"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Section Sites autorises (ticket 2 module Sites) -->
|
||||
@@ -153,14 +128,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Permission, Role, UserListItem, UserRbacDetail, EffectivePermission } from '~/shared/types/rbac'
|
||||
import type { Permission, PermissionModule, Role, UserListItem, UserRbacDetail, EffectivePermission } from '~/shared/types/rbac'
|
||||
import type { Site } from '~/shared/types/sites'
|
||||
|
||||
interface PermissionModule {
|
||||
module: string
|
||||
permissions: Permission[]
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApi()
|
||||
const auth = useAuthStore()
|
||||
@@ -189,19 +159,6 @@ const selectedRoleIds = ref(new Set<number>())
|
||||
const selectedDirectPermissionIds = ref(new Set<number>())
|
||||
const selectedSiteIds = ref(new Set<number>())
|
||||
|
||||
// Modules ouverts dans l'accordeon des permissions directes (mode multiple)
|
||||
const openDirectModules = ref<string[]>([])
|
||||
|
||||
// Nombre de permissions directes selectionnees pour un module donne
|
||||
function directSelectedCount(group: PermissionModule): number {
|
||||
return group.permissions.filter(p => selectedDirectPermissionIds.value.has(p.id)).length
|
||||
}
|
||||
|
||||
// Vrai si toutes les permissions directes du module sont selectionnees
|
||||
function directAllSelected(group: PermissionModule): boolean {
|
||||
return group.permissions.length > 0 && directSelectedCount(group) === group.permissions.length
|
||||
}
|
||||
|
||||
// Detecter l'auto-edition
|
||||
const isSelfEdit = computed(() => props.user?.id === auth.user?.id)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:label="t('audit.filters.title')"
|
||||
icon-name="mdi:tune"
|
||||
icon-position="left"
|
||||
iconSize="24"
|
||||
icon-size="24"
|
||||
button-class="w-[184px] justify-start gap-4 text-black"
|
||||
@click="openFilters"
|
||||
/>
|
||||
@@ -278,12 +278,12 @@ function openFilters(): void {
|
||||
filterDrawerOpen.value = true
|
||||
}
|
||||
|
||||
// Bascule un type d'entite dans le brouillon (multi-selection).
|
||||
// Bascule un type d'entite dans le brouillon (multi-selection). Les valeurs
|
||||
// sont uniques par construction (v-for sur entityTypeOptions), pas besoin de Set.
|
||||
function toggleEntity(value: string, selected: boolean): void {
|
||||
const set = new Set(draftEntityTypes.value)
|
||||
if (selected) set.add(value)
|
||||
else set.delete(value)
|
||||
draftEntityTypes.value = [...set]
|
||||
draftEntityTypes.value = selected
|
||||
? [...draftEntityTypes.value, value]
|
||||
: draftEntityTypes.value.filter(v => v !== value)
|
||||
}
|
||||
|
||||
// "Reinitialiser" : vide le brouillon ET les filtres actifs, puis recharge.
|
||||
|
||||
Reference in New Issue
Block a user