feat: add file upload on componet and delete code champs

This commit is contained in:
Matthieu
2025-10-16 10:05:32 +02:00
parent ebc02f41d9
commit 8eada12438
10 changed files with 527 additions and 51 deletions

View File

@@ -266,10 +266,8 @@ const lockedTypeDisplay = computed(() => {
return getComponentTypeLabel(props.node?.typeComposantId) || 'Famille non définie'
})
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => {
if (!type) return ''
return type.code ? `${type.name} (${type.code})` : type.name
}
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) =>
type?.name ?? ''
const componentTypeMap = computed(() => {
const map = new Map<string, ModelTypeOption>()

View File

@@ -68,7 +68,7 @@ const props = withDefaults(
const selectedCategory = ref<ModelCategory>(props.category);
const searchInput = ref("");
const searchTerm = ref("");
const sort = ref<"name" | "code" | "createdAt">("createdAt");
const sort = ref<"name" | "createdAt">("createdAt");
const dir = ref<"asc" | "desc">("desc");
const limit = ref(20);
const offset = ref(0);
@@ -188,7 +188,7 @@ const onCategoryChange = (value: ModelCategory) => {
}
};
const onSortChange = (value: "name" | "code" | "createdAt") => {
const onSortChange = (value: "name" | "createdAt") => {
if (sort.value !== value) {
sort.value = value;
refresh({ resetOffset: true });

View File

@@ -18,26 +18,6 @@
/>
<p v-if="errors.name" class="mt-1 text-sm text-error">{{ errors.name }}</p>
</div>
<div>
<label class="label" for="model-type-code">
<span class="label-text">Code *</span>
</label>
<input
id="model-type-code"
v-model.trim="form.code"
type="text"
class="input input-bordered w-full"
name="code"
minlength="2"
maxlength="60"
required
autocomplete="off"
/>
<p class="mt-1 text-xs text-base-content/70">Caractères autorisés : lettres, chiffres, -, _ et .</p>
<p v-if="errors.code" class="mt-1 text-sm text-error">{{ errors.code }}</p>
</div>
<div>
<label class="label" for="model-type-category">
<span class="label-text">Catégorie *</span>
@@ -177,13 +157,26 @@ const form = reactive<ModelTypePayload>({
structure: undefined,
})
const errors = reactive<{ name?: string; code?: string }>({})
const errors = reactive<{ name?: string }>({})
const nameInput = ref<HTMLInputElement | null>(null)
const codePattern = /^[a-z0-9\-_.]+$/i
const componentStructure = ref(normalizeStructureForSave(defaultStructure()))
const pieceStructure = ref(normalizePieceStructureForSave(defaultPieceStructure()))
const generateCodeFromName = (name: string) => {
const fallback = 'type'
if (!name) {
return fallback
}
return name
.normalize('NFD')
.replace(/[\u0300-\u036F]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-+/g, '-') || fallback
}
const resetStructures = (incomingStructure: ModelTypePayload['structure'], category: ModelCategory) => {
if (category === 'COMPONENT') {
componentStructure.value = normalizeStructureForSave(
@@ -204,7 +197,9 @@ const resetStructures = (incomingStructure: ModelTypePayload['structure'], categ
const resetForm = () => {
const incoming = props.initialData ?? {}
form.name = typeof incoming.name === 'string' ? incoming.name : ''
form.code = typeof incoming.code === 'string' ? incoming.code : ''
form.code = typeof incoming.code === 'string' && incoming.code
? incoming.code
: generateCodeFromName(form.name)
form.category = incoming.category ?? props.initialCategory
form.notes = typeof incoming.notes === 'string'
? incoming.notes
@@ -213,7 +208,6 @@ const resetForm = () => {
: ''
errors.name = undefined
errors.code = undefined
resetStructures(incoming.structure, form.category)
}
@@ -223,22 +217,16 @@ const isSubmitDisabled = computed(() => saving.value || structureLoading.value)
const validate = () => {
errors.name = undefined
errors.code = undefined
if (form.name.trim().length < 2) {
const trimmedName = form.name.trim()
if (trimmedName.length < 2) {
errors.name = 'Le nom doit contenir au moins 2 caractères.'
}
if (form.name.trim().length > 120) {
if (trimmedName.length > 120) {
errors.name = 'Le nom ne peut pas dépasser 120 caractères.'
}
if (!codePattern.test(form.code.trim())) {
errors.code = 'Le code doit respecter le format demandé.'
} else if (form.code.trim().length < 2 || form.code.trim().length > 60) {
errors.code = 'Le code doit contenir entre 2 et 60 caractères.'
}
return !errors.name && !errors.code
return !errors.name
}
const handleSubmit = () => {
@@ -246,9 +234,14 @@ const handleSubmit = () => {
return
}
const trimmedName = form.name.trim()
const resolvedCode = form.code?.trim()
? form.code.trim()
: generateCodeFromName(trimmedName)
const common = {
name: form.name.trim(),
code: form.code.trim(),
name: trimmedName,
code: resolvedCode,
notes: form.notes?.trim() ? form.notes.trim() : undefined,
}
@@ -276,6 +269,15 @@ watch(
{ deep: true, immediate: true },
)
watch(
() => form.name,
(value) => {
if (props.mode === 'create') {
form.code = generateCodeFromName(value)
}
},
)
watch(
() => props.initialCategory,
() => {

View File

@@ -33,7 +33,6 @@
<thead>
<tr class="text-base-content/70">
<th scope="col">Nom</th>
<th scope="col">Code</th>
<th scope="col">Catégorie</th>
<th scope="col">Notes</th>
<th scope="col" class="w-32 text-right">Actions</th>
@@ -42,7 +41,6 @@
<tbody>
<tr v-for="item in items" :key="item.id">
<td class="font-medium">{{ item.name }}</td>
<td><code class="badge badge-neutral badge-sm">{{ item.code }}</code></td>
<td>{{ categoryLabel(item.category) }}</td>
<td class="max-w-xs align-middle">
<span v-if="item.notes" class="block text-sm text-base-content/80 break-words">{{ item.notes }}</span>
@@ -72,7 +70,6 @@
<h3 class="text-lg font-semibold text-base-content">{{ item.name }}</h3>
<p class="text-sm text-base-content/60">{{ categoryLabel(item.category) }}</p>
</div>
<code class="badge badge-neutral">{{ item.code }}</code>
</header>
<p class="mt-3 text-sm text-base-content/80" v-if="item.notes">{{ item.notes }}</p>
<p class="mt-3 text-sm text-base-content/50" v-else>Pas de notes</p>

View File

@@ -28,7 +28,7 @@
:value="search"
type="search"
class="grow min-w-0"
placeholder="Rechercher par nom ou code…"
placeholder="Rechercher par nom…"
autocomplete="off"
@input="onSearch"
/>
@@ -43,7 +43,6 @@
@change="emit('update:sort', ($event.target as HTMLSelectElement).value as SortField)"
>
<option value="name">Nom</option>
<option value="code">Code</option>
<option value="createdAt">Date de création</option>
</select>
</div>
@@ -80,7 +79,7 @@ import { computed } from 'vue';
import IconLucidePlus from '~icons/lucide/plus';
import IconLucideSearch from '~icons/lucide/search';
type SortField = 'name' | 'code' | 'createdAt';
type SortField = 'name' | 'createdAt';
type SortDirection = 'asc' | 'desc';
const props = defineProps<{