feat(permissions) : add role-based UI guards and readonly mode for viewers

- Add usePermissions composable (isAdmin, canEdit, canView)
- Password-protected profile login with modal on profiles page
- Disable all form fields for ROLE_VIEWER across edit/create pages
- Show navigation buttons (Modifier/Consulter) for all roles, hide delete for viewers
- Add readonly prop to ModelTypeForm for category pages
- Disable modal fields (sites, constructeurs) for viewers
- Guard /admin routes in middleware

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-02-26 13:36:42 +01:00
parent 6bed715b7f
commit cc70fe2b29
46 changed files with 946 additions and 423 deletions

View File

@@ -72,7 +72,7 @@
v-model="editionForm.name"
type="text"
class="input input-bordered input-sm md:input-md"
:disabled="saving"
:disabled="!canEdit || saving"
placeholder="Nom affiché dans le catalogue"
required
>
@@ -88,7 +88,7 @@
v-model="editionForm.reference"
type="text"
class="input input-bordered input-sm md:input-md"
:disabled="saving"
:disabled="!canEdit || saving"
placeholder="Référence interne ou fournisseur"
>
</div>
@@ -100,7 +100,7 @@
<ConstructeurSelect
v-model="editionForm.constructeurIds"
class="w-full"
:disabled="saving"
:disabled="!canEdit || saving"
placeholder="Rechercher un ou plusieurs fournisseurs..."
:initial-options="component?.constructeurs || []"
/>
@@ -118,7 +118,7 @@
step="0.01"
min="0"
class="input input-bordered input-sm md:input-md"
:disabled="saving"
:disabled="!canEdit || saving"
placeholder="Valeur indicatrice"
>
</div>
@@ -277,7 +277,7 @@
type="text"
class="input input-bordered input-sm md:input-md"
:required="field.required"
:disabled="saving"
:disabled="!canEdit || saving"
>
<input
v-else-if="field.type === 'number'"
@@ -286,14 +286,14 @@
step="0.01"
class="input input-bordered input-sm md:input-md"
:required="field.required"
:disabled="saving"
:disabled="!canEdit || saving"
>
<select
v-else-if="field.type === 'select'"
v-model="field.value"
class="select select-bordered select-sm md:select-md"
:required="field.required"
:disabled="saving"
:disabled="!canEdit || saving"
>
<option value="">Sélectionner...</option>
<option
@@ -311,7 +311,7 @@
class="checkbox checkbox-sm"
true-value="true"
false-value="false"
:disabled="saving"
:disabled="!canEdit || saving"
>
<span class="text-sm">{{ field.value === 'true' ? 'Oui' : 'Non' }}</span>
</div>
@@ -321,7 +321,7 @@
type="date"
class="input input-bordered input-sm md:input-md"
:required="field.required"
:disabled="saving"
:disabled="!canEdit || saving"
>
<input
v-else
@@ -329,7 +329,7 @@
type="text"
class="input input-bordered input-sm md:input-md"
:required="field.required"
:disabled="saving"
:disabled="!canEdit || saving"
>
</div>
</div>
@@ -347,7 +347,7 @@
{{ selectedFiles.length }} document{{ selectedFiles.length > 1 ? 's' : '' }} prêt{{ selectedFiles.length > 1 ? 's' : '' }} à être ajouté{{ selectedFiles.length > 1 ? 's' : '' }}
</span>
</header>
<div :class="{ 'pointer-events-none opacity-60': saving || uploadingDocuments }">
<div :class="{ 'pointer-events-none opacity-60': !canEdit || saving || uploadingDocuments }">
<DocumentUpload
v-model="selectedFiles"
title="Déposer vos fichiers"
@@ -419,6 +419,7 @@
Télécharger
</button>
<button
v-if="canEdit"
type="button"
class="btn btn-error btn-xs"
:disabled="uploadingDocuments"
@@ -566,6 +567,7 @@ interface ComponentCatalogType extends ModelType {
customFields?: Array<Record<string, any>>
}
const { canEdit } = usePermissions()
const route = useRoute()
const router = useRouter()
const { get } = useApi()
@@ -746,6 +748,7 @@ const requiredCustomFieldsFilled = computed(() =>
)
const canSubmit = computed(() => Boolean(
canEdit.value &&
component.value &&
editionForm.name &&
requiredCustomFieldsFilled.value &&