Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3705b8daed | |||
|
|
202b964b24 |
16
app/app.vue
16
app/app.vue
@@ -297,15 +297,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div class="avatar placeholder">
|
<div class="avatar">
|
||||||
<div
|
<div class="w-14">
|
||||||
class="bg-primary text-primary-content rounded-lg w-10 grid place-items-center"
|
<img
|
||||||
>
|
:src="logoSrc"
|
||||||
<IconLucideBoxes class="w-6 h-6" aria-hidden="true" />
|
alt="Logo Malio"
|
||||||
|
class="h-full w-full object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NuxtLink to="/" class="btn btn-ghost text-xl">
|
<NuxtLink to="/" class="btn btn-ghost text-xl">
|
||||||
Inventaire Pro
|
Inventory
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -705,13 +707,13 @@ import { useRoute, navigateTo, useRuntimeConfig } from "#imports";
|
|||||||
import { useProfileSession } from "~/composables/useProfileSession";
|
import { useProfileSession } from "~/composables/useProfileSession";
|
||||||
import IconLucideMenu from "~icons/lucide/menu";
|
import IconLucideMenu from "~icons/lucide/menu";
|
||||||
import IconLucideSettings from "~icons/lucide/settings";
|
import IconLucideSettings from "~icons/lucide/settings";
|
||||||
import IconLucideBoxes from "~icons/lucide/boxes";
|
|
||||||
import IconLucidePlus from "~icons/lucide/plus";
|
import IconLucidePlus from "~icons/lucide/plus";
|
||||||
import IconLucideCpu from "~icons/lucide/cpu";
|
import IconLucideCpu from "~icons/lucide/cpu";
|
||||||
import IconLucideFilePlus from "~icons/lucide/file-plus";
|
import IconLucideFilePlus from "~icons/lucide/file-plus";
|
||||||
import IconLucideMapPin from "~icons/lucide/map-pin";
|
import IconLucideMapPin from "~icons/lucide/map-pin";
|
||||||
import IconLucideChevronRight from "~icons/lucide/chevron-right";
|
import IconLucideChevronRight from "~icons/lucide/chevron-right";
|
||||||
import IconLucideLogOut from "~icons/lucide/log-out";
|
import IconLucideLogOut from "~icons/lucide/log-out";
|
||||||
|
import logoSrc from "~/assets/LOGO_CARRE_BLANC.png";
|
||||||
|
|
||||||
// État du modal des paramètres d'affichage
|
// État du modal des paramètres d'affichage
|
||||||
const displaySettingsOpen = ref(false);
|
const displaySettingsOpen = ref(false);
|
||||||
|
|||||||
BIN
app/assets/LOGO_CARRE_BLANC.png
Normal file
BIN
app/assets/LOGO_CARRE_BLANC.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -10,6 +10,7 @@
|
|||||||
:locked-type-label="displayedRootTypeLabel"
|
:locked-type-label="displayedRootTypeLabel"
|
||||||
:allow-subcomponents="allowSubcomponents"
|
:allow-subcomponents="allowSubcomponents"
|
||||||
:max-subcomponent-depth="maxSubcomponentDepth"
|
:max-subcomponent-depth="maxSubcomponentDepth"
|
||||||
|
:restricted-mode="restrictedMode"
|
||||||
is-root
|
is-root
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,6 +56,10 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: Infinity,
|
default: Infinity,
|
||||||
},
|
},
|
||||||
|
restrictedMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
Produits inclus par défaut
|
Produits inclus par défaut
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-xs text-base-content/70">
|
<p class="text-xs text-base-content/70">
|
||||||
Ces produits s’afficheront lors de la création d’une 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>
|
</p>
|
||||||
</div>
|
</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" />
|
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
|
||||||
Ajouter
|
Ajouter
|
||||||
</button>
|
</button>
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
<select
|
<select
|
||||||
v-model="product.typeProductId"
|
v-model="product.typeProductId"
|
||||||
class="select select-bordered select-xs"
|
class="select select-bordered select-xs"
|
||||||
|
:disabled="isProductLocked(product)"
|
||||||
@change="handleProductTypeSelect(product)"
|
@change="handleProductTypeSelect(product)"
|
||||||
>
|
>
|
||||||
<option value="">
|
<option value="">
|
||||||
@@ -51,12 +52,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
v-if="!isProductLocked(product)"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-error btn-xs btn-square"
|
class="btn btn-error btn-xs btn-square"
|
||||||
@click="removeProduct(index)"
|
@click="removeProduct(index)"
|
||||||
>
|
>
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -107,8 +118,9 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="input input-bordered input-xs"
|
class="input input-bordered input-xs"
|
||||||
placeholder="Nom du champ"
|
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">
|
<option value="text">
|
||||||
Texte
|
Texte
|
||||||
</option>
|
</option>
|
||||||
@@ -128,7 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 text-xs">
|
<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
|
Obligatoire
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -137,16 +149,27 @@
|
|||||||
v-model="field.optionsText"
|
v-model="field.optionsText"
|
||||||
class="textarea textarea-bordered textarea-xs h-20"
|
class="textarea textarea-bordered textarea-xs h-20"
|
||||||
placeholder="Option 1 Option 2"
|
placeholder="Option 1 Option 2"
|
||||||
|
:disabled="isFieldLocked(field)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="!isFieldLocked(field)"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-error btn-xs btn-square"
|
class="btn btn-error btn-xs btn-square"
|
||||||
@click="removeField(index)"
|
@click="removeField(index)"
|
||||||
>
|
>
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -181,6 +204,7 @@ type EditorProduct = {
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue?: PieceModelStructure | null
|
modelValue?: PieceModelStructure | null
|
||||||
|
restrictedMode?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -330,6 +354,19 @@ const fields = ref<EditorField[]>(hydrateFields(props.modelValue))
|
|||||||
const products = ref<EditorProduct[]>(hydrateProducts(props.modelValue))
|
const products = ref<EditorProduct[]>(hydrateProducts(props.modelValue))
|
||||||
const restState = ref<Record<string, unknown>>(extractRest(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[] =>
|
const applyOrderIndex = (list: EditorField[]): EditorField[] =>
|
||||||
list.map((field, index) => ({
|
list.map((field, index) => ({
|
||||||
...field,
|
...field,
|
||||||
@@ -438,6 +475,8 @@ watch(
|
|||||||
products.value = hydrateProducts(value)
|
products.value = hydrateProducts(value)
|
||||||
products.value.forEach((product) => updateProductTypeMetadata(product))
|
products.value.forEach((product) => updateProductTypeMetadata(product))
|
||||||
lastEmitted = incomingSerialized
|
lastEmitted = incomingSerialized
|
||||||
|
initialFieldUids.value = new Set(fields.value.map(f => f.uid))
|
||||||
|
initialProductUids.value = new Set(products.value.map(p => p.uid))
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<select
|
<select
|
||||||
v-model="node.typeComposantId"
|
v-model="node.typeComposantId"
|
||||||
class="select select-bordered select-sm w-full"
|
class="select select-bordered select-sm w-full"
|
||||||
|
:disabled="isLocked"
|
||||||
@change="handleComponentTypeSelect(node)"
|
@change="handleComponentTypeSelect(node)"
|
||||||
>
|
>
|
||||||
<option value="">
|
<option value="">
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="input input-bordered input-xs"
|
class="input input-bordered input-xs"
|
||||||
placeholder="Alias du sous-composant"
|
placeholder="Alias du sous-composant"
|
||||||
|
:disabled="isLocked"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -52,13 +54,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="!isRoot"
|
v-if="!isRoot && !isLocked"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-error btn-xs btn-square"
|
class="btn btn-error btn-xs btn-square"
|
||||||
@click="emit('remove')"
|
@click="emit('remove')"
|
||||||
>
|
>
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
<div v-else-if="!isRoot && isLocked" class="tooltip tooltip-left" data-tip="Ce sous-composant ne peut pas être supprimé">
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="px-4 py-4 space-y-5">
|
<div class="px-4 py-4 space-y-5">
|
||||||
@@ -107,8 +114,9 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="input input-bordered input-xs"
|
class="input input-bordered input-xs"
|
||||||
placeholder="Nom du champ"
|
placeholder="Nom du champ"
|
||||||
|
:disabled="isCustomFieldLocked(index)"
|
||||||
/>
|
/>
|
||||||
<select v-model="field.type" class="select select-bordered select-xs">
|
<select v-model="field.type" class="select select-bordered select-xs" :disabled="isCustomFieldLocked(index)">
|
||||||
<option value="text">Texte</option>
|
<option value="text">Texte</option>
|
||||||
<option value="number">Nombre</option>
|
<option value="number">Nombre</option>
|
||||||
<option value="select">Liste</option>
|
<option value="select">Liste</option>
|
||||||
@@ -117,7 +125,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 text-xs">
|
<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="isCustomFieldLocked(index)" />
|
||||||
Obligatoire
|
Obligatoire
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -125,15 +133,26 @@
|
|||||||
v-model="field.optionsText"
|
v-model="field.optionsText"
|
||||||
class="textarea textarea-bordered textarea-xs h-20"
|
class="textarea textarea-bordered textarea-xs h-20"
|
||||||
placeholder="Option 1 Option 2"
|
placeholder="Option 1 Option 2"
|
||||||
|
:disabled="isCustomFieldLocked(index)"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
v-if="!isCustomFieldLocked(index)"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-error btn-xs btn-square"
|
class="btn btn-error btn-xs btn-square"
|
||||||
@click="removeCustomField(index)"
|
@click="removeCustomField(index)"
|
||||||
>
|
>
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +163,7 @@
|
|||||||
<h4 :class="headingClass">
|
<h4 :class="headingClass">
|
||||||
{{ isRoot ? 'Produits inclus par défaut' : 'Produits' }}
|
{{ isRoot ? 'Produits inclus par défaut' : 'Produits' }}
|
||||||
</h4>
|
</h4>
|
||||||
<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" />
|
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
|
||||||
Ajouter
|
Ajouter
|
||||||
</button>
|
</button>
|
||||||
@@ -179,6 +198,7 @@
|
|||||||
<select
|
<select
|
||||||
v-model="product.typeProductId"
|
v-model="product.typeProductId"
|
||||||
class="select select-bordered select-xs"
|
class="select select-bordered select-xs"
|
||||||
|
:disabled="isProductLocked(index)"
|
||||||
@change="handleProductTypeSelect(product)"
|
@change="handleProductTypeSelect(product)"
|
||||||
>
|
>
|
||||||
<option value="">
|
<option value="">
|
||||||
@@ -194,9 +214,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-error btn-xs btn-square" @click="removeProduct(index)">
|
<button v-if="!isProductLocked(index)" type="button" class="btn btn-error btn-xs btn-square" @click="removeProduct(index)">
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -207,7 +236,7 @@
|
|||||||
<h4 :class="headingClass">
|
<h4 :class="headingClass">
|
||||||
{{ isRoot ? 'Pièces incluses par défaut' : 'Pièces' }}
|
{{ isRoot ? 'Pièces incluses par défaut' : 'Pièces' }}
|
||||||
</h4>
|
</h4>
|
||||||
<button type="button" class="btn btn-outline btn-xs" @click="addPiece">
|
<button v-if="!restrictedMode" type="button" class="btn btn-outline btn-xs" @click="addPiece">
|
||||||
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
|
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
|
||||||
Ajouter
|
Ajouter
|
||||||
</button>
|
</button>
|
||||||
@@ -243,6 +272,7 @@
|
|||||||
<select
|
<select
|
||||||
v-model="piece.typePieceId"
|
v-model="piece.typePieceId"
|
||||||
class="select select-bordered select-xs"
|
class="select select-bordered select-xs"
|
||||||
|
:disabled="isPieceLocked(index)"
|
||||||
@change="handlePieceTypeSelect(piece)"
|
@change="handlePieceTypeSelect(piece)"
|
||||||
>
|
>
|
||||||
<option value="">
|
<option value="">
|
||||||
@@ -262,9 +292,14 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-error btn-xs btn-square" @click="removePiece(index)">
|
<button v-if="!isPieceLocked(index)" type="button" class="btn btn-error btn-xs btn-square" @click="removePiece(index)">
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
<div v-else class="tooltip tooltip-left" data-tip="Cette pièce ne peut pas être supprimée">
|
||||||
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -274,7 +309,7 @@
|
|||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<h4 :class="headingClass">Sous-composants</h4>
|
<h4 :class="headingClass">Sous-composants</h4>
|
||||||
<button
|
<button
|
||||||
v-if="canManageSubcomponents"
|
v-if="canManageSubcomponents && !restrictedMode"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-outline btn-xs"
|
class="btn btn-outline btn-xs"
|
||||||
@click="addSubComponent"
|
@click="addSubComponent"
|
||||||
@@ -317,6 +352,8 @@
|
|||||||
:product-types="productTypes"
|
:product-types="productTypes"
|
||||||
:allow-subcomponents="childAllowSubcomponents"
|
:allow-subcomponents="childAllowSubcomponents"
|
||||||
:max-subcomponent-depth="maxSubcomponentDepth"
|
:max-subcomponent-depth="maxSubcomponentDepth"
|
||||||
|
:restricted-mode="restrictedMode"
|
||||||
|
:is-locked="isSubcomponentLocked(index)"
|
||||||
@remove="removeSubComponent(index)"
|
@remove="removeSubComponent(index)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -359,6 +396,8 @@ const props = withDefaults(defineProps<{
|
|||||||
lockedTypeLabel?: string
|
lockedTypeLabel?: string
|
||||||
allowSubcomponents?: boolean
|
allowSubcomponents?: boolean
|
||||||
maxSubcomponentDepth?: number
|
maxSubcomponentDepth?: number
|
||||||
|
restrictedMode?: boolean
|
||||||
|
isLocked?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
componentTypes: () => [],
|
componentTypes: () => [],
|
||||||
@@ -369,10 +408,52 @@ const props = withDefaults(defineProps<{
|
|||||||
lockedTypeLabel: '',
|
lockedTypeLabel: '',
|
||||||
allowSubcomponents: true,
|
allowSubcomponents: true,
|
||||||
maxSubcomponentDepth: Infinity,
|
maxSubcomponentDepth: Infinity,
|
||||||
|
restrictedMode: false,
|
||||||
|
isLocked: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['remove'])
|
const emit = defineEmits(['remove'])
|
||||||
|
|
||||||
|
const initialCustomFieldIndices = ref<Set<number>>(new Set())
|
||||||
|
const initialPieceIndices = ref<Set<number>>(new Set())
|
||||||
|
const initialProductIndices = ref<Set<number>>(new Set())
|
||||||
|
const initialSubcomponentIndices = ref<Set<number>>(new Set())
|
||||||
|
|
||||||
|
const initializeLockedIndices = () => {
|
||||||
|
if (props.restrictedMode) {
|
||||||
|
const customFieldsLength = Array.isArray(props.node.customFields) ? props.node.customFields.length : 0
|
||||||
|
const piecesLength = Array.isArray(props.node.pieces) ? props.node.pieces.length : 0
|
||||||
|
const productsLength = Array.isArray(props.node.products) ? props.node.products.length : 0
|
||||||
|
const subcomponentsLength = Array.isArray(props.node.subcomponents) ? props.node.subcomponents.length : 0
|
||||||
|
|
||||||
|
initialCustomFieldIndices.value = new Set(Array.from({ length: customFieldsLength }, (_, i) => i))
|
||||||
|
initialPieceIndices.value = new Set(Array.from({ length: piecesLength }, (_, i) => i))
|
||||||
|
initialProductIndices.value = new Set(Array.from({ length: productsLength }, (_, i) => i))
|
||||||
|
initialSubcomponentIndices.value = new Set(Array.from({ length: subcomponentsLength }, (_, i) => i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeLockedIndices()
|
||||||
|
|
||||||
|
const isCustomFieldLocked = (index: number): boolean => {
|
||||||
|
return props.restrictedMode === true && initialCustomFieldIndices.value.has(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPieceLocked = (index: number): boolean => {
|
||||||
|
return props.restrictedMode === true && initialPieceIndices.value.has(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isProductLocked = (index: number): boolean => {
|
||||||
|
return props.restrictedMode === true && initialProductIndices.value.has(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSubcomponentLocked = (index: number): boolean => {
|
||||||
|
return props.restrictedMode === true && initialSubcomponentIndices.value.has(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLocked = computed(() => props.isLocked === true)
|
||||||
|
const restrictedMode = computed(() => props.restrictedMode === true)
|
||||||
|
|
||||||
const componentTypes = computed(() => props.componentTypes ?? [])
|
const componentTypes = computed(() => props.componentTypes ?? [])
|
||||||
const pieceTypes = computed(() => props.pieceTypes ?? [])
|
const pieceTypes = computed(() => props.pieceTypes ?? [])
|
||||||
const productTypes = computed(() => props.productTypes ?? [])
|
const productTypes = computed(() => props.productTypes ?? [])
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
minlength="2"
|
minlength="2"
|
||||||
maxlength="120"
|
maxlength="120"
|
||||||
required
|
required
|
||||||
|
:disabled="restrictedMode"
|
||||||
/>
|
/>
|
||||||
<p v-if="errors.name" class="mt-1 text-sm text-error">{{ errors.name }}</p>
|
<p v-if="errors.name" class="mt-1 text-sm text-error">{{ errors.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
rows="4"
|
rows="4"
|
||||||
name="notes"
|
name="notes"
|
||||||
maxlength="2000"
|
maxlength="2000"
|
||||||
|
:disabled="restrictedMode"
|
||||||
></textarea>
|
></textarea>
|
||||||
<p class="mt-1 text-xs text-base-content/70">Saisissez des informations complémentaires (facultatif).</p>
|
<p class="mt-1 text-xs text-base-content/70">Saisissez des informations complémentaires (facultatif).</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,6 +83,7 @@
|
|||||||
v-model="componentStructure"
|
v-model="componentStructure"
|
||||||
:allow-subcomponents="allowComponentSubcomponents"
|
:allow-subcomponents="allowComponentSubcomponents"
|
||||||
:max-subcomponent-depth="componentSubcomponentMaxDepth"
|
:max-subcomponent-depth="componentSubcomponentMaxDepth"
|
||||||
|
:restricted-mode="restrictedMode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -92,7 +95,7 @@
|
|||||||
Aperçu :
|
Aperçu :
|
||||||
<span class="font-medium text-base-content">{{ pieceStructurePreview }}</span>
|
<span class="font-medium text-base-content">{{ pieceStructurePreview }}</span>
|
||||||
</p>
|
</p>
|
||||||
<PieceModelStructureEditor v-model="pieceStructure" />
|
<PieceModelStructureEditor v-model="pieceStructure" :restricted-mode="restrictedMode" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -103,11 +106,21 @@
|
|||||||
Aperçu :
|
Aperçu :
|
||||||
<span class="font-medium text-base-content">{{ productStructurePreview }}</span>
|
<span class="font-medium text-base-content">{{ productStructurePreview }}</span>
|
||||||
</p>
|
</p>
|
||||||
<PieceModelStructureEditor v-model="productStructure" />
|
<PieceModelStructureEditor v-model="productStructure" :restricted-mode="restrictedMode" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="restrictedMode && restrictedModeMessage"
|
||||||
|
class="alert alert-info"
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||||
|
<span>{{ restrictedModeMessage }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="disableSubmit"
|
v-if="disableSubmit"
|
||||||
class="alert alert-warning"
|
class="alert alert-warning"
|
||||||
@@ -161,6 +174,8 @@ const props = withDefaults(defineProps<{
|
|||||||
componentSubcomponentMaxDepth?: number
|
componentSubcomponentMaxDepth?: number
|
||||||
disableSubmit?: boolean
|
disableSubmit?: boolean
|
||||||
disableSubmitMessage?: string
|
disableSubmitMessage?: string
|
||||||
|
restrictedMode?: boolean
|
||||||
|
restrictedModeMessage?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
initialData: null,
|
initialData: null,
|
||||||
saving: false,
|
saving: false,
|
||||||
@@ -170,6 +185,8 @@ const props = withDefaults(defineProps<{
|
|||||||
componentSubcomponentMaxDepth: 1,
|
componentSubcomponentMaxDepth: 1,
|
||||||
disableSubmit: false,
|
disableSubmit: false,
|
||||||
disableSubmitMessage: '',
|
disableSubmitMessage: '',
|
||||||
|
restrictedMode: false,
|
||||||
|
restrictedModeMessage: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -192,6 +209,12 @@ const disableSubmitMessage = computed(() =>
|
|||||||
? props.disableSubmitMessage
|
? props.disableSubmitMessage
|
||||||
: 'Cette catégorie ne peut pas être modifiée car des éléments y sont déjà liés.',
|
: 'Cette catégorie ne peut pas être modifiée car des éléments y sont déjà liés.',
|
||||||
)
|
)
|
||||||
|
const restrictedMode = computed(() => props.restrictedMode === true)
|
||||||
|
const restrictedModeMessage = computed(() =>
|
||||||
|
(props.restrictedModeMessage && props.restrictedModeMessage.trim())
|
||||||
|
? props.restrictedModeMessage
|
||||||
|
: '',
|
||||||
|
)
|
||||||
|
|
||||||
const form = reactive<ModelTypePayload>({
|
const form = reactive<ModelTypePayload>({
|
||||||
name: '',
|
name: '',
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const extractTotal = (payload: any, fallbackLength: number) => {
|
|||||||
|
|
||||||
export function useCategoryEditGuard (config: GuardConfig) {
|
export function useCategoryEditGuard (config: GuardConfig) {
|
||||||
const { get } = useApi()
|
const { get } = useApi()
|
||||||
const { showError } = useToast()
|
const { showInfo } = useToast()
|
||||||
|
|
||||||
const linkedCount = ref(0)
|
const linkedCount = ref(0)
|
||||||
const linkedLoading = ref(false)
|
const linkedLoading = ref(false)
|
||||||
@@ -64,11 +64,15 @@ export function useCategoryEditGuard (config: GuardConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSubmitBlocked = computed(
|
const isRestrictedMode = computed(
|
||||||
() => linkedLoading.value || linkedCount.value > 0,
|
() => !linkedLoading.value && linkedCount.value > 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
const submitBlockMessage = computed(() => {
|
const isSubmitBlocked = computed(
|
||||||
|
() => linkedLoading.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
const restrictedModeMessage = computed(() => {
|
||||||
if (linkedLoading.value) {
|
if (linkedLoading.value) {
|
||||||
return config.labels.verifying
|
return config.labels.verifying
|
||||||
}
|
}
|
||||||
@@ -76,23 +80,32 @@ export function useCategoryEditGuard (config: GuardConfig) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
if (linkedCount.value === 1) {
|
if (linkedCount.value === 1) {
|
||||||
return `Modification bloquée : 1 ${config.labels.singular} est déjà lié à cette catégorie.`
|
return `Mode restreint : 1 ${config.labels.singular} est déjà lié à cette catégorie. Vous pouvez ajouter de nouveaux champs personnalisés, mais pas modifier ou supprimer les existants.`
|
||||||
}
|
}
|
||||||
return `Modification bloquée : ${linkedCount.value} ${config.labels.plural} sont déjà liés à cette catégorie.`
|
return `Mode restreint : ${linkedCount.value} ${config.labels.plural} sont déjà liés à cette catégorie. Vous pouvez ajouter de nouveaux champs personnalisés, mais pas modifier ou supprimer les existants.`
|
||||||
|
})
|
||||||
|
|
||||||
|
const submitBlockMessage = computed(() => {
|
||||||
|
if (linkedLoading.value) {
|
||||||
|
return config.labels.verifying
|
||||||
|
}
|
||||||
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const guardSubmitOrNotify = () => {
|
const guardSubmitOrNotify = () => {
|
||||||
if (!isSubmitBlocked.value) {
|
if (!isSubmitBlocked.value) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
showError(submitBlockMessage.value || 'Modification bloquée pour cette catégorie.')
|
showInfo(submitBlockMessage.value || 'Veuillez patienter...')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
linkedCount,
|
linkedCount,
|
||||||
linkedLoading,
|
linkedLoading,
|
||||||
|
isRestrictedMode,
|
||||||
isSubmitBlocked,
|
isSubmitBlocked,
|
||||||
|
restrictedModeMessage,
|
||||||
submitBlockMessage,
|
submitBlockMessage,
|
||||||
loadLinkedCount,
|
loadLinkedCount,
|
||||||
guardSubmitOrNotify,
|
guardSubmitOrNotify,
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
:saving="saving"
|
:saving="saving"
|
||||||
:disable-submit="isSubmitBlocked"
|
:disable-submit="isSubmitBlocked"
|
||||||
:disable-submit-message="submitBlockMessage"
|
:disable-submit-message="submitBlockMessage"
|
||||||
|
:restricted-mode="isRestrictedMode"
|
||||||
|
:restricted-mode-message="restrictedModeMessage"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
/>
|
/>
|
||||||
@@ -52,7 +54,9 @@ const saving = ref(false)
|
|||||||
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
isRestrictedMode,
|
||||||
isSubmitBlocked,
|
isSubmitBlocked,
|
||||||
|
restrictedModeMessage,
|
||||||
submitBlockMessage,
|
submitBlockMessage,
|
||||||
loadLinkedCount,
|
loadLinkedCount,
|
||||||
guardSubmitOrNotify,
|
guardSubmitOrNotify,
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
:saving="saving"
|
:saving="saving"
|
||||||
:disable-submit="isSubmitBlocked"
|
:disable-submit="isSubmitBlocked"
|
||||||
:disable-submit-message="submitBlockMessage"
|
:disable-submit-message="submitBlockMessage"
|
||||||
|
:restricted-mode="isRestrictedMode"
|
||||||
|
:restricted-mode-message="restrictedModeMessage"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
/>
|
/>
|
||||||
@@ -52,7 +54,9 @@ const saving = ref(false)
|
|||||||
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
isRestrictedMode,
|
||||||
isSubmitBlocked,
|
isSubmitBlocked,
|
||||||
|
restrictedModeMessage,
|
||||||
submitBlockMessage,
|
submitBlockMessage,
|
||||||
loadLinkedCount,
|
loadLinkedCount,
|
||||||
guardSubmitOrNotify,
|
guardSubmitOrNotify,
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
:saving="saving"
|
:saving="saving"
|
||||||
:disable-submit="isSubmitBlocked"
|
:disable-submit="isSubmitBlocked"
|
||||||
:disable-submit-message="submitBlockMessage"
|
:disable-submit-message="submitBlockMessage"
|
||||||
|
:restricted-mode="isRestrictedMode"
|
||||||
|
:restricted-mode-message="restrictedModeMessage"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
/>
|
/>
|
||||||
@@ -52,7 +54,9 @@ const saving = ref(false)
|
|||||||
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
isRestrictedMode,
|
||||||
isSubmitBlocked,
|
isSubmitBlocked,
|
||||||
|
restrictedModeMessage,
|
||||||
submitBlockMessage,
|
submitBlockMessage,
|
||||||
loadLinkedCount,
|
loadLinkedCount,
|
||||||
guardSubmitOrNotify,
|
guardSubmitOrNotify,
|
||||||
|
|||||||
Reference in New Issue
Block a user