feat : dernier push avant finalisation de affichage reception finie
This commit is contained in:
@@ -1,33 +1,29 @@
|
||||
<template>
|
||||
<form @submit.prevent="validate">
|
||||
<div class="flex flex-col items-center gap-16">
|
||||
<form>
|
||||
<div
|
||||
class="flex flex-col gap-16 items-center w-full">
|
||||
<UiTextInput
|
||||
class="flex flex-col w-full">
|
||||
<div class="w-full col-start-1 row-start-1">
|
||||
<UiRadioGroup
|
||||
id="merchandise-type"
|
||||
v-model="selectedMerchandiseTypeId"
|
||||
label="Type de marchandises"
|
||||
:value="reception.merchandiseType?.label"
|
||||
wrapper-class="w-[550px]"
|
||||
:disabled="true"
|
||||
:options="merchandiseTypes.map((type) => ({
|
||||
value: String(type.id),
|
||||
label: type.label
|
||||
}))"
|
||||
input-class="accent-primary-700 focus:ring-primary-700"
|
||||
item-class="text-primary-700/50 [&:has(input:checked)]:text-primary-700"
|
||||
option-label-class="uppercase"
|
||||
wrapper-class="w-full uppercase "
|
||||
group-class="grid grid-cols-[335px_337px_355px_200px] w-[160px_160px_200px_200px] mt-9 mb-7"
|
||||
:disabled="!auth.isAdmin"
|
||||
/>
|
||||
<div
|
||||
v-if="merchandiseTypeId && isAutres"
|
||||
class="flex flex-col w-full max-w-[550px]"
|
||||
>
|
||||
<UiTextInput
|
||||
id="merchandise-detail"
|
||||
:disabled="!auth.isAdmin"
|
||||
v-model="merchandiseDetail"
|
||||
label="Préciser"
|
||||
placeholder="Précisions complémentaires"
|
||||
:maxlength="255"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class=" w-full grid grid-cols-[3fr_1fr] gap-[80px] col-start-2 row-start-1 ">
|
||||
<div
|
||||
v-if="merchandiseTypeId && !isGranule"
|
||||
class="flex gap-4 w-[550px] justify-evenly"
|
||||
class="flex gap-[187px]"
|
||||
>
|
||||
<div
|
||||
v-for="building in buildings"
|
||||
@@ -38,28 +34,44 @@
|
||||
:value="String(building.id)"
|
||||
:label="building.label"
|
||||
:disabled="!auth.isAdmin"
|
||||
label-class="text-xl"
|
||||
input-class="accent-primary-700 focus:ring-primary-700"
|
||||
label-class="text-xl uppercase"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="merchandiseTypeId && isAutres"
|
||||
class="flex flex-col w-full max-w-[225px]"
|
||||
>
|
||||
<UiTextInput
|
||||
id="merchandise-detail"
|
||||
:disabled="!auth.isAdmin"
|
||||
v-model="merchandiseDetail"
|
||||
placeholder="Préciser"
|
||||
:maxlength="255"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="merchandiseTypeId && isGranule"
|
||||
class="flex flex-col gap-10 w-full max-w-[1100px]"
|
||||
class="flex flex-col gap-10 w-full col-start-2 row-start-1 "
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-10 md:grid-cols-4">
|
||||
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
||||
<p class="font-bold uppercase">{{ type.label }}</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-[max-content_max-content_max-content_max-content] justify-between">
|
||||
|
||||
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
||||
<p class="mb-1 font-medium uppercase">{{ type.label }}</p>
|
||||
<div
|
||||
v-for="building in buildings"
|
||||
:key="building.id"
|
||||
class="flex items-center gap-2 text-lg"
|
||||
class="flex items-center text-lg"
|
||||
>
|
||||
<UiCheckbox
|
||||
v-model="selectedPelletBuildingIds[String(type.id)]"
|
||||
:value="String(building.id)"
|
||||
:label="building.label"
|
||||
:disabled="!auth.isAdmin"
|
||||
input-class="accent-primary-700 focus:ring-primary-700"
|
||||
label-class="text-lg"
|
||||
/>
|
||||
</div>
|
||||
@@ -67,19 +79,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UiButton
|
||||
v-if="auth.isAdmin"
|
||||
type="submit"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||
:disabled="!auth.isAdmin"
|
||||
>Valider
|
||||
</UiButton>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, ref} from 'vue'
|
||||
import {computed, onMounted, ref, watch} from 'vue'
|
||||
import {getBuildingList} from '~/services/building'
|
||||
import {getMerchandiseTypeList} from '~/services/merchandise-type'
|
||||
import type {MerchandiseTypeData} from '~/services/dto/merchandise-type-data'
|
||||
@@ -102,12 +106,17 @@ const selectedBuildingIds = ref<string[]>([])
|
||||
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
||||
const merchandiseDetail = ref('')
|
||||
const auth = useAuthStore()
|
||||
const initialMerchandiseTypeId = ref<string | null>(null)
|
||||
const initialMerchandiseDetail = ref<string | null>(null)
|
||||
const initialBuildingIds = ref<string[]>([])
|
||||
const initialPelletSelections = ref<Record<string, string[]>>({})
|
||||
const props = defineProps<{
|
||||
idReception: number
|
||||
isValidate: boolean
|
||||
}>()
|
||||
const receptionId = props.idReception
|
||||
const reception = await getReception(receptionId)
|
||||
const merchandiseTypeId = await reception.receptionType?.id
|
||||
const merchandiseTypeId = reception.receptionType?.id
|
||||
|
||||
// Extrait l'ID d'une relation depuis un IRI ou un objet complet.
|
||||
const getRelationId = (value: unknown): string | null => {
|
||||
@@ -142,6 +151,52 @@ const isGranule = computed(() => selectedMerchandiseType.value?.code === MERCHAN
|
||||
// Indique si le type est "Autres"
|
||||
const isAutres = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.AUTRES)
|
||||
|
||||
const hasMerchandiseChanged = () => {
|
||||
const currentTypeId = selectedMerchandiseTypeId.value || null
|
||||
if (initialMerchandiseTypeId.value !== currentTypeId) {
|
||||
return true
|
||||
}
|
||||
|
||||
const currentDetail = isAutres.value ? merchandiseDetail.value.trim() : ''
|
||||
if ((initialMerchandiseDetail.value ?? '') !== currentDetail) {
|
||||
return true
|
||||
}
|
||||
|
||||
const currentBuildings = isGranule.value ? [] : [...selectedBuildingIds.value].sort()
|
||||
const initialBuildings = [...initialBuildingIds.value].sort()
|
||||
if (currentBuildings.length !== initialBuildings.length) {
|
||||
return true
|
||||
}
|
||||
for (let i = 0; i < currentBuildings.length; i += 1) {
|
||||
if (currentBuildings[i] !== initialBuildings[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const currentPellets = normalizePelletSelections(selectedPelletBuildingIds.value)
|
||||
const initialPellets = normalizePelletSelections(initialPelletSelections.value)
|
||||
if (currentPellets.length !== initialPellets.length) {
|
||||
return true
|
||||
}
|
||||
for (let i = 0; i < currentPellets.length; i += 1) {
|
||||
if (currentPellets[i] !== initialPellets[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const normalizePelletSelections = (selections: Record<string, string[]>) => {
|
||||
const pairs: string[] = []
|
||||
for (const [pelletTypeId, buildingIds] of Object.entries(selections)) {
|
||||
for (const buildingId of buildingIds) {
|
||||
pairs.push(`${pelletTypeId}:${buildingId}`)
|
||||
}
|
||||
}
|
||||
return pairs.sort()
|
||||
}
|
||||
|
||||
// Charge les référentiels et hydrate le formulaire depuis la réception
|
||||
onMounted(async () => {
|
||||
const [merchandiseTypeList, buildingList, pelletTypeList] = await Promise.all([
|
||||
@@ -183,14 +238,36 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
selectedPelletBuildingIds.value = selectionMap
|
||||
initialMerchandiseTypeId.value = selectedMerchandiseTypeId.value || null
|
||||
initialMerchandiseDetail.value = isAutres.value ? merchandiseDetail.value.trim() : ''
|
||||
initialBuildingIds.value = [...selectedBuildingIds.value]
|
||||
initialPelletSelections.value = JSON.parse(JSON.stringify(selectedPelletBuildingIds.value))
|
||||
})
|
||||
// Enregistre les sélections et passe à l'étape suivante
|
||||
async function validate() {
|
||||
|
||||
watch(
|
||||
() => props.isValidate,
|
||||
async (val) => {
|
||||
if (!val) return
|
||||
await runValidate()
|
||||
}
|
||||
)
|
||||
|
||||
const runValidate = async () => {
|
||||
if (!hasMerchandiseChanged()) {
|
||||
return
|
||||
}
|
||||
const receptionIri = `/api/receptions/${reception.id}`
|
||||
|
||||
// 1) supprimer toutes les anciennes associations
|
||||
await clearPelletSelections(receptionIri)
|
||||
|
||||
// 2) update reception (type + détails + buildings non-granulé)
|
||||
await updateReception(reception.id, {
|
||||
merchandiseDetail: isAutres.value ? merchandiseDetail.value.trim() : null,
|
||||
merchandiseType: selectedMerchandiseTypeId.value
|
||||
? `/api/merchandise_types/${selectedMerchandiseTypeId.value}`
|
||||
: null,
|
||||
merchandiseDetail: isAutres.value ? merchandiseDetail.value.trim() :
|
||||
null,
|
||||
buildings: isGranule.value
|
||||
? []
|
||||
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
|
||||
@@ -198,11 +275,15 @@ async function validate() {
|
||||
bovinesTypes: null,
|
||||
})
|
||||
|
||||
// 3) si granulé ALORS créer les nouvelles associations
|
||||
if (isGranule.value) {
|
||||
await syncPelletSelections(receptionIri)
|
||||
} else {
|
||||
await clearPelletSelections(receptionIri)
|
||||
}
|
||||
|
||||
initialMerchandiseTypeId.value = selectedMerchandiseTypeId.value || null
|
||||
initialMerchandiseDetail.value = isAutres.value ? merchandiseDetail.value.trim() : ''
|
||||
initialBuildingIds.value = [...selectedBuildingIds.value]
|
||||
initialPelletSelections.value = JSON.parse(JSON.stringify(selectedPelletBuildingIds.value))
|
||||
}
|
||||
|
||||
// Supprime toutes les associations granulés/bâtiments existantes
|
||||
@@ -255,4 +336,5 @@ async function syncPelletSelections(receptionIri: string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user