feat(front) : retouches UX saisie bovin (filtre, toast, partial save, fix isSaisi)
- isSaisi : != null couvre les champs absents du JSON (API Platform strip null) - UiNumberInput : ne réécrit target.value que si réellement clampé (fix saisie décimaux) - Form : champs optionnels, payload partiel, toast de confirmation - Page : filtre N° national au-dessus de la liste Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,32 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<form class="space-y-6" :class="{ submitted }" @submit.prevent="submit">
|
<form class="space-y-6" @submit.prevent="submit">
|
||||||
<div class="grid grid-cols-2 gap-x-12 gap-y-6">
|
<div class="grid grid-cols-4 gap-x-12 gap-y-6">
|
||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
v-model="form.receivedWeight"
|
v-model="form.receivedWeight"
|
||||||
label="Poids d'arrivée (kg)"
|
label="Poids d'arrivée (kg)"
|
||||||
|
wrapperClass="flex-col"
|
||||||
|
labelClass="font-bold uppercase text-xl text-primary-700"
|
||||||
:min="0"
|
:min="0"
|
||||||
:step="1"
|
:step="1"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
v-model="form.pricePerKg"
|
v-model="form.pricePerKg"
|
||||||
label="Prix d'achat (kg)"
|
label="Prix au kg"
|
||||||
|
wrapperClass="flex-col"
|
||||||
|
labelClass="font-bold uppercase text-xl text-primary-700"
|
||||||
:min="0"
|
:min="0"
|
||||||
:step="0.01"
|
:step="0.01"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<UiSelect
|
<UiSelect
|
||||||
v-model="form.buildingId"
|
v-model="form.buildingId"
|
||||||
label="Bâtiment"
|
label="Bâtiment"
|
||||||
:options="buildingOptions"
|
:options="buildingOptions"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
<UiSelect
|
<UiSelect
|
||||||
v-model="form.buildingCaseId"
|
v-model="form.buildingCaseId"
|
||||||
label="Case"
|
label="Case"
|
||||||
:options="caseOptions"
|
:options="caseOptions"
|
||||||
:disabled="form.buildingId === null"
|
:disabled="form.buildingId === null"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -66,15 +66,14 @@ interface FormState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const form = reactive<FormState>({
|
const form = reactive<FormState>({
|
||||||
receivedWeight: props.bovine.receivedWeight,
|
receivedWeight: props.bovine.receivedWeight ?? null,
|
||||||
pricePerKg: props.bovine.pricePerKg,
|
pricePerKg: props.bovine.pricePerKg ?? null,
|
||||||
buildingId: props.bovine.buildingCase?.building?.id
|
buildingId: props.bovine.buildingCase?.building?.id
|
||||||
?? props.bovine.effectiveBuilding?.id
|
?? props.bovine.effectiveBuilding?.id
|
||||||
?? null,
|
?? null,
|
||||||
buildingCaseId: props.bovine.buildingCase?.id ?? null
|
buildingCaseId: props.bovine.buildingCase?.id ?? null
|
||||||
})
|
})
|
||||||
|
|
||||||
const submitted = ref(false)
|
|
||||||
const isSaving = ref(false)
|
const isSaving = ref(false)
|
||||||
|
|
||||||
const buildingOptions = computed(() =>
|
const buildingOptions = computed(() =>
|
||||||
@@ -101,13 +100,15 @@ watch(() => form.buildingId, (newId) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
submitted.value = true
|
const payload: Record<string, unknown> = {}
|
||||||
if (
|
if (form.receivedWeight != null) payload.receivedWeight = form.receivedWeight
|
||||||
form.receivedWeight === null
|
if (form.pricePerKg != null) payload.pricePerKg = form.pricePerKg
|
||||||
|| form.pricePerKg === null
|
if (form.buildingCaseId != null) {
|
||||||
|| form.buildingId === null
|
payload.buildingCase = `/api/building_cases/${form.buildingCaseId}`
|
||||||
|| form.buildingCaseId === null
|
}
|
||||||
) {
|
|
||||||
|
if (Object.keys(payload).length === 0) {
|
||||||
|
emit('saved', props.bovine)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,11 +116,8 @@ const submit = async () => {
|
|||||||
try {
|
try {
|
||||||
const updated = await api.patch<BovineData>(
|
const updated = await api.patch<BovineData>(
|
||||||
`bovines/${props.bovine.id}`,
|
`bovines/${props.bovine.id}`,
|
||||||
{
|
payload,
|
||||||
receivedWeight: form.receivedWeight,
|
{ toastSuccessMessage: `Bovin ${props.bovine.nationalNumber} enregistré.` }
|
||||||
pricePerKg: form.pricePerKg,
|
|
||||||
buildingCase: `/api/building_cases/${form.buildingCaseId}`
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
emit('saved', updated)
|
emit('saved', updated)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ const onInput = (event: Event) => {
|
|||||||
numeric = Math.min(max, numeric)
|
numeric = Math.min(max, numeric)
|
||||||
}
|
}
|
||||||
|
|
||||||
target.value = String(numeric)
|
if (numeric !== parsed) {
|
||||||
|
target.value = String(numeric)
|
||||||
|
}
|
||||||
emit('update:modelValue', numeric)
|
emit('update:modelValue', numeric)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,36 +14,49 @@
|
|||||||
|
|
||||||
<div v-if="loading" class="text-center text-slate-500">Chargement…</div>
|
<div v-if="loading" class="text-center text-slate-500">Chargement…</div>
|
||||||
|
|
||||||
<div v-else class="space-y-3">
|
<div v-else>
|
||||||
<UiAccordion
|
<div class="mb-4 max-w-[200px]">
|
||||||
v-for="bovine in sortedBovines"
|
<UiTextInput
|
||||||
:key="bovine.id"
|
v-model="searchQuery"
|
||||||
:model-value="openId === bovine.id"
|
placeholder="N° national"
|
||||||
@update:model-value="onToggle(bovine.id, $event)"
|
size="compact"
|
||||||
>
|
inputmode="numeric"
|
||||||
<template #header>
|
pattern="[0-9]*"
|
||||||
<span class="flex items-center gap-3 normal-case">
|
inputClass="text-xl"
|
||||||
<span class="font-bold text-base">{{ bovine.nationalNumber }}</span>
|
|
||||||
<span
|
|
||||||
v-if="isSaisi(bovine)"
|
|
||||||
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-green-100 text-green-700"
|
|
||||||
>
|
|
||||||
Saisie
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-yellow-100 text-yellow-700"
|
|
||||||
>
|
|
||||||
Attente saisie
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<BovineInfoForm
|
|
||||||
:bovine="bovine"
|
|
||||||
:buildings="buildings"
|
|
||||||
@saved="onSaved"
|
|
||||||
/>
|
/>
|
||||||
</UiAccordion>
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<UiAccordion
|
||||||
|
v-for="bovine in filteredBovines"
|
||||||
|
:key="bovine.id"
|
||||||
|
:model-value="openId === bovine.id"
|
||||||
|
@update:model-value="onToggle(bovine.id, $event)"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span class="flex items-center gap-3 normal-case">
|
||||||
|
<span class="font-bold text-base">{{ bovine.nationalNumber }}</span>
|
||||||
|
<span
|
||||||
|
v-if="isSaisi(bovine)"
|
||||||
|
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-green-100 text-green-700"
|
||||||
|
>
|
||||||
|
Saisie
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="inline-block rounded px-2 py-0.5 text-xs font-semibold bg-yellow-100 text-yellow-700"
|
||||||
|
>
|
||||||
|
Attente saisie
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<BovineInfoForm
|
||||||
|
:bovine="bovine"
|
||||||
|
:buildings="buildings"
|
||||||
|
@saved="onSaved"
|
||||||
|
/>
|
||||||
|
</UiAccordion>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -53,6 +66,7 @@ import type { BovineData } from '~/services/dto/bovine-data'
|
|||||||
import type { BuildingData } from '~/services/dto/building-data'
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
import type { ReceptionData } from '~/services/dto/reception-data'
|
||||||
import { getBuildingList } from '~/services/building'
|
import { getBuildingList } from '~/services/building'
|
||||||
|
import BovineInfoForm from '~/components/entry-exit/bovine-info-form.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -65,15 +79,22 @@ const bovines = ref<BovineData[]>([])
|
|||||||
const buildings = ref<BuildingData[]>([])
|
const buildings = ref<BuildingData[]>([])
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const openId = ref<number | null>(null)
|
const openId = ref<number | null>(null)
|
||||||
|
const searchQueryRaw = ref('')
|
||||||
|
const searchQuery = computed<string>({
|
||||||
|
get: () => searchQueryRaw.value,
|
||||||
|
set: (value) => {
|
||||||
|
searchQueryRaw.value = value.replace(/\D/g, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: () => `Saisie information bovin ${reception.value?.identificationNumber ?? ''}`.trim()
|
title: () => `Saisie information bovin ${reception.value?.identificationNumber ?? ''}`.trim()
|
||||||
})
|
})
|
||||||
|
|
||||||
const isSaisi = (bovine: BovineData) =>
|
const isSaisi = (bovine: BovineData) =>
|
||||||
bovine.receivedWeight !== null
|
bovine.receivedWeight != null
|
||||||
&& bovine.pricePerKg !== null
|
&& bovine.pricePerKg != null
|
||||||
&& bovine.buildingCase !== null
|
&& bovine.buildingCase != null
|
||||||
|
|
||||||
const sortedBovines = computed(() => {
|
const sortedBovines = computed(() => {
|
||||||
const pending = bovines.value.filter(b => !isSaisi(b))
|
const pending = bovines.value.filter(b => !isSaisi(b))
|
||||||
@@ -81,6 +102,14 @@ const sortedBovines = computed(() => {
|
|||||||
return [...pending, ...done]
|
return [...pending, ...done]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const filteredBovines = computed(() => {
|
||||||
|
const query = searchQuery.value.trim().toLowerCase()
|
||||||
|
if (!query) return sortedBovines.value
|
||||||
|
return sortedBovines.value.filter(b =>
|
||||||
|
b.nationalNumber.toLowerCase().includes(query)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const onToggle = (bovineId: number, value: boolean) => {
|
const onToggle = (bovineId: number, value: boolean) => {
|
||||||
openId.value = value ? bovineId : null
|
openId.value = value ? bovineId : null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user