feat: add file upload on componet and delete code champs
This commit is contained in:
@@ -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>()
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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,
|
||||
() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<{
|
||||
|
||||
Reference in New Issue
Block a user