Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [ ] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: #57 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
178 lines
5.0 KiB
Vue
178 lines
5.0 KiB
Vue
<template>
|
|
<form>
|
|
<div class="flex flex-row justify-between gap-x-12 font-bold uppercase mb-8">
|
|
<div
|
|
v-for="type in bovineTypes"
|
|
:key="type.id"
|
|
>
|
|
<UiNumberInput
|
|
:label="type.label"
|
|
:code="type.code"
|
|
v-model="localQuantities[String(type.id)]"
|
|
:disabled="!isAdmin"
|
|
:placeholder="0"
|
|
:min="0"
|
|
:max="10"
|
|
wrapperClass="w-44 flex-col"
|
|
inputClass="font-medium"
|
|
/>
|
|
</div>
|
|
<UiNumberInput
|
|
label="Autres"
|
|
v-model="localOtherQuantity"
|
|
:disabled="!isAdmin"
|
|
wrapperClass="w-44 flex-col"
|
|
inputClass="font-medium"
|
|
/>
|
|
</div>
|
|
</form>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|
import { getBovineTypeList } from '~/services/bovine-type'
|
|
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
|
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'
|
|
|
|
const props = defineProps<{
|
|
modelValue: ReceptionBovineTypeData[]
|
|
otherQuantity: number | null
|
|
isAdmin: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(event: 'update:modelValue', value: ReceptionBovineTypeData[]): void
|
|
(event: 'update:otherQuantity', value: number | null): void
|
|
}>()
|
|
|
|
// Types activés par l'admin (display=true), chargés depuis l'API.
|
|
const displayedTypes = ref<BovineTypeData[]>([])
|
|
// On affiche les types activés ET ceux déjà saisis sur la réception (même masqués),
|
|
// pour ne pas faire disparaître/perdre une quantité existante.
|
|
const bovineTypes = computed<BovineTypeData[]>(() => {
|
|
const seen = new Set(displayedTypes.value.map((type) => type.id))
|
|
const fromExisting = props.modelValue
|
|
.map((entry) => entry.bovineType)
|
|
.filter((type): type is BovineTypeData => Boolean(type) && !seen.has(type.id))
|
|
|
|
return [...displayedTypes.value, ...fromExisting]
|
|
})
|
|
const localQuantities = reactive<Record<string, number | null>>({})
|
|
const localOtherQuantity = ref<number | null>(props.otherQuantity ?? 0)
|
|
// Verrou pour éviter les boucles props -> local -> emit -> props.
|
|
const isSyncing = ref(false)
|
|
|
|
function entriesEqualByTypeAndQuantity(
|
|
left: ReceptionBovineTypeData[],
|
|
right: ReceptionBovineTypeData[]
|
|
): boolean {
|
|
const toMap = (entries: ReceptionBovineTypeData[]) => {
|
|
const map = new Map<number, number>()
|
|
for (const entry of entries) {
|
|
const typeId = entry.bovineType?.id ?? 0
|
|
map.set(typeId, entry.quantity ?? 0)
|
|
}
|
|
|
|
return map
|
|
}
|
|
|
|
const a = toMap(left)
|
|
const b = toMap(right)
|
|
if (a.size !== b.size) {
|
|
return false
|
|
}
|
|
|
|
for (const [typeId, quantity] of a.entries()) {
|
|
if ((b.get(typeId) ?? 0) !== quantity) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
function buildEntriesFromLocal(): ReceptionBovineTypeData[] {
|
|
return bovineTypes.value.map((type) => {
|
|
const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
|
|
return {
|
|
id: existing?.id ?? 0,
|
|
bovineType: type,
|
|
quantity: localQuantities[String(type.id)] ?? 0
|
|
}
|
|
})
|
|
}
|
|
|
|
function syncLocalFromProps() {
|
|
isSyncing.value = true
|
|
try {
|
|
for (const key of Object.keys(localQuantities)) {
|
|
delete localQuantities[key]
|
|
}
|
|
|
|
for (const type of bovineTypes.value) {
|
|
const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
|
|
localQuantities[String(type.id)] = existing?.quantity ?? 0
|
|
}
|
|
} finally {
|
|
isSyncing.value = false
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.otherQuantity,
|
|
(value) => {
|
|
if (isSyncing.value) {
|
|
return
|
|
}
|
|
|
|
const next = value ?? 0
|
|
isSyncing.value = true
|
|
localOtherQuantity.value = next
|
|
isSyncing.value = false
|
|
}
|
|
)
|
|
|
|
watch(localOtherQuantity, (value) => {
|
|
if (isSyncing.value) {
|
|
return
|
|
}
|
|
|
|
const next = value ?? 0
|
|
emit('update:otherQuantity', next)
|
|
})
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
() => {
|
|
// Hydratation locale uniquement quand le parent change.
|
|
syncLocalFromProps()
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
watch(
|
|
localQuantities,
|
|
() => {
|
|
if (isSyncing.value) {
|
|
return
|
|
}
|
|
// N'émet que si les quantités diffèrent réellement du parent.
|
|
const nextEntries = buildEntriesFromLocal()
|
|
if (!entriesEqualByTypeAndQuantity(nextEntries, props.modelValue)) {
|
|
emit('update:modelValue', nextEntries)
|
|
}
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
// Re-synchronise dès que la liste fusionnée change (chargement async des types).
|
|
watch(bovineTypes, () => {
|
|
syncLocalFromProps()
|
|
})
|
|
|
|
onMounted(async () => {
|
|
displayedTypes.value = await getBovineTypeList({ display: true })
|
|
syncLocalFromProps()
|
|
})
|
|
</script>
|