feat(model-types): allow adding custom fields in restricted mode

When a category has linked items (pieces, components, products),
enable restricted mode instead of blocking all edits:
- Allow adding new custom fields
- Lock existing fields from modification or deletion
- Hide add buttons for products, pieces, and subcomponents
- Display informative message about restricted mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 19:53:56 +01:00
parent 202b964b24
commit 3705b8daed
8 changed files with 194 additions and 21 deletions

View File

@@ -7,10 +7,10 @@
Produits inclus par défaut
</h3>
<p class="text-xs text-base-content/70">
Ces produits safficheront lors de la création dune pièce basée sur cette catégorie.
Ces produits s'afficheront lors de la création d'une pièce basée sur cette catégorie.
</p>
</div>
<button type="button" class="btn btn-outline btn-xs" @click="addProduct">
<button v-if="!restrictedMode" type="button" class="btn btn-outline btn-xs" @click="addProduct">
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
Ajouter
</button>
@@ -35,6 +35,7 @@
<select
v-model="product.typeProductId"
class="select select-bordered select-xs"
:disabled="isProductLocked(product)"
@change="handleProductTypeSelect(product)"
>
<option value="">
@@ -51,12 +52,22 @@
</div>
</div>
<button
v-if="!isProductLocked(product)"
type="button"
class="btn btn-error btn-xs btn-square"
@click="removeProduct(index)"
>
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
</button>
<div v-else class="tooltip tooltip-left" data-tip="Ce produit ne peut pas être supprimé car des éléments utilisent cette catégorie">
<button
type="button"
class="btn btn-ghost btn-xs btn-square opacity-30 cursor-not-allowed"
disabled
>
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
</button>
</div>
</div>
</li>
</ul>
@@ -107,8 +118,9 @@
type="text"
class="input input-bordered input-xs"
placeholder="Nom du champ"
:disabled="isFieldLocked(field)"
>
<select v-model="field.type" class="select select-bordered select-xs">
<select v-model="field.type" class="select select-bordered select-xs" :disabled="isFieldLocked(field)">
<option value="text">
Texte
</option>
@@ -128,7 +140,7 @@
</div>
<div class="flex items-center gap-2 text-xs">
<input v-model="field.required" type="checkbox" class="checkbox checkbox-xs">
<input v-model="field.required" type="checkbox" class="checkbox checkbox-xs" :disabled="isFieldLocked(field)">
Obligatoire
</div>
@@ -137,16 +149,27 @@
v-model="field.optionsText"
class="textarea textarea-bordered textarea-xs h-20"
placeholder="Option 1&#10;Option 2"
:disabled="isFieldLocked(field)"
/>
</div>
<button
v-if="!isFieldLocked(field)"
type="button"
class="btn btn-error btn-xs btn-square"
@click="removeField(index)"
>
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
</button>
<div v-else class="tooltip tooltip-left" data-tip="Ce champ ne peut pas être supprimé car des éléments utilisent cette catégorie">
<button
type="button"
class="btn btn-ghost btn-xs btn-square opacity-30 cursor-not-allowed"
disabled
>
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
</button>
</div>
</div>
</li>
</ul>
@@ -181,6 +204,7 @@ type EditorProduct = {
const props = defineProps<{
modelValue?: PieceModelStructure | null
restrictedMode?: boolean
}>()
const emit = defineEmits<{
@@ -330,6 +354,19 @@ const fields = ref<EditorField[]>(hydrateFields(props.modelValue))
const products = ref<EditorProduct[]>(hydrateProducts(props.modelValue))
const restState = ref<Record<string, unknown>>(extractRest(props.modelValue))
const initialFieldUids = ref<Set<string>>(new Set(fields.value.map(f => f.uid)))
const initialProductUids = ref<Set<string>>(new Set(products.value.map(p => p.uid)))
const isFieldLocked = (field: EditorField): boolean => {
return props.restrictedMode === true && initialFieldUids.value.has(field.uid)
}
const isProductLocked = (product: EditorProduct): boolean => {
return props.restrictedMode === true && initialProductUids.value.has(product.uid)
}
const restrictedMode = computed(() => props.restrictedMode === true)
const applyOrderIndex = (list: EditorField[]): EditorField[] =>
list.map((field, index) => ({
...field,
@@ -438,6 +475,8 @@ watch(
products.value = hydrateProducts(value)
products.value.forEach((product) => updateProductTypeMetadata(product))
lastEmitted = incomingSerialized
initialFieldUids.value = new Set(fields.value.map(f => f.uid))
initialProductUids.value = new Set(products.value.map(p => p.uid))
},
{ deep: true },
)