fix : correction des retours de la V0
This commit is contained in:
@@ -3,10 +3,9 @@
|
||||
<div class="flex justify-between h-[52px] mb-[80px]">
|
||||
<div class="flex flex-1 mr-16">
|
||||
<UiStepper
|
||||
:labels="SHIPMENT_STEP_LABELS"
|
||||
:labels="stepLabels"
|
||||
:current-step="storeShipment?.currentStep ?? 0"
|
||||
@select="handleStepSelect"
|
||||
|
||||
/>
|
||||
</div>
|
||||
<UiButton
|
||||
@@ -17,45 +16,69 @@
|
||||
</UiButton>
|
||||
</div>
|
||||
<ShipmentForm v-if="!storeShipment || storeShipment.currentStep === 0" ref="shipmentFormRef"/>
|
||||
<ShipmentWeight v-if="storeShipment?.currentStep === 1" mode="gross"/>
|
||||
<WorkflowWeight
|
||||
v-if="storeShipment?.currentStep === 1"
|
||||
mode="gross"
|
||||
entity-name="shipment"
|
||||
api-resource="shipments"
|
||||
:title-label="shipmentConfig.weighingLabels.gross"
|
||||
:is-final="false"
|
||||
:entity="storeShipment"
|
||||
:get-weight-from-scale="getWeightShipment"
|
||||
:update-entity="shipmentStore.updateShipment"
|
||||
:load-entity="shipmentStore.loadShipment"
|
||||
:clear-entity="shipmentStore.clearCurrent"
|
||||
:build-receipt-filename="shipmentConfig.buildReceiptFilename"
|
||||
/>
|
||||
<ShipmentLoading v-if="storeShipment?.currentStep === 2"/>
|
||||
<ShipmentWeight v-if="storeShipment?.currentStep === 3" mode="tare"/>
|
||||
|
||||
<WorkflowWeight
|
||||
v-if="storeShipment?.currentStep === 3"
|
||||
mode="tare"
|
||||
entity-name="shipment"
|
||||
api-resource="shipments"
|
||||
:title-label="shipmentConfig.weighingLabels.tare"
|
||||
:is-final="true"
|
||||
:entity="storeShipment"
|
||||
:get-weight-from-scale="getWeightShipment"
|
||||
:update-entity="shipmentStore.updateShipment"
|
||||
:load-entity="shipmentStore.loadShipment"
|
||||
:clear-entity="shipmentStore.clearCurrent"
|
||||
:build-receipt-filename="shipmentConfig.buildReceiptFilename"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
import {SHIPMENT_STEP_LABELS} from "~/constants/steps";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {useShipmentStore} from "~/stores/shipment";
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useShipmentStore } from '~/stores/shipment'
|
||||
import { useWorkflowSteps } from '~/composables/useWorkflowSteps'
|
||||
import { shipmentConfig } from '~/config/shipment.config'
|
||||
import { getWeightShipment } from '~/services/shipment'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const shipmentStore = useShipmentStore()
|
||||
const {current: storeShipment} = storeToRefs(shipmentStore)
|
||||
const { current: storeShipment } = storeToRefs(shipmentStore)
|
||||
const shipmentFormRef = ref<{ saveDraft: () => Promise<void> } | null>(null)
|
||||
|
||||
const { stepLabels, handleStepSelect } = useWorkflowSteps(shipmentConfig, shipmentStore)
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const resolveShipmentId = (param: unknown) => {
|
||||
const idStr = Array.isArray(param) ? param[0] : param
|
||||
if (!idStr) {
|
||||
return null
|
||||
}
|
||||
const id = Number(idStr)
|
||||
return Number.isFinite(id) ? id : null
|
||||
}
|
||||
|
||||
watch (
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async (param) => {
|
||||
const id = resolveShipmentId(param)
|
||||
if (id === null) {
|
||||
const idStr = Array.isArray(param) ? param[0] : param
|
||||
if (!idStr) {
|
||||
shipmentStore.clearCurrent()
|
||||
return
|
||||
}
|
||||
await shipmentStore.loadShipment(id)
|
||||
const id = Number(idStr)
|
||||
if (Number.isFinite(id)) {
|
||||
await shipmentStore.loadShipment(id)
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const saveAndHold = async () => {
|
||||
@@ -64,20 +87,4 @@ const saveAndHold = async () => {
|
||||
}
|
||||
await router.push('/')
|
||||
}
|
||||
const handleStepSelect = async (step: number) => {
|
||||
if (!shipmentStore.current) {
|
||||
return
|
||||
}
|
||||
|
||||
if (step === shipmentStore.current.currentStep) {
|
||||
return
|
||||
}
|
||||
|
||||
await shipmentStore.updateShipment(shipmentStore.current.id, {
|
||||
currentStep: step
|
||||
})
|
||||
await shipmentStore.loadShipment(shipmentStore.current.id)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<form @submit.prevent="validate">
|
||||
<form :class="{ submitted }" @submit.prevent="validate">
|
||||
<div class="grid grid-cols-2 h-[461px] items-start gap-y-8 gap-x-40 mb-16">
|
||||
<div class="flex items-center justify-between gap-10 relative col-start-1 row-start-1">
|
||||
<div class="flex flex-row absolute -left-[60px] justify-between">
|
||||
<Icon @click="router.push('/shipment/finish-shipment')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
||||
</div>
|
||||
<h1 class="font-bold text-4xl col-start-1 row-start-1 text-primary-500 uppercase">Expédition {{ form.identificationNumber }}</h1>
|
||||
<Icon @click="printReceipt" name="mdi:printer-outline" size="44" class="cursor-pointer text-primary-500"/>
|
||||
<div class="bg-primary-500 p-1 rounded-md flex items-center" title="Imprimer" @click="printReceipt">
|
||||
<Icon name="mdi:printer-outline" size="32" class="cursor-pointer text-white"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UiSelect
|
||||
@@ -19,6 +21,7 @@
|
||||
}))"
|
||||
:loading="isLoadingUsers"
|
||||
wrapper-class="col-start-1 row-start-2"
|
||||
required
|
||||
/>
|
||||
|
||||
<UiDateInput
|
||||
@@ -26,6 +29,7 @@
|
||||
v-model="form.shipmentDate"
|
||||
label="Date d'expédition"
|
||||
wrapper-class="col-start-1 row-start-3"
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="col-start-1 row-start-4 h-[64px]">
|
||||
@@ -41,6 +45,7 @@
|
||||
value: String(type.id),
|
||||
label: type.label
|
||||
}))"
|
||||
required
|
||||
/>
|
||||
<UiNumberInput
|
||||
id="shipment-type-quantity"
|
||||
@@ -63,6 +68,7 @@
|
||||
}))"
|
||||
:loading="isLoadingCustomers"
|
||||
wrapper-class="col-start-1 row-start-5"
|
||||
required
|
||||
/>
|
||||
|
||||
<UiSelect
|
||||
@@ -72,6 +78,7 @@
|
||||
:disabled="isLoadingCustomers || customerAddresses.length === 0"
|
||||
label="Adresse"
|
||||
wrapper-class="col-start-2 row-start-1"
|
||||
required
|
||||
/>
|
||||
|
||||
<UiSelect
|
||||
@@ -84,6 +91,7 @@
|
||||
}))"
|
||||
:loading="isLoadingTrucks"
|
||||
wrapper-class="col-start-2 row-start-2"
|
||||
required
|
||||
/>
|
||||
|
||||
<UiSelect
|
||||
@@ -95,12 +103,14 @@
|
||||
label: carrier.name
|
||||
}))"
|
||||
wrapper-class="col-start-2 row-start-3"
|
||||
required
|
||||
/>
|
||||
|
||||
<div v-if="!isLiotCarrier" class="col-start-2 row-start-4">
|
||||
<UiLicensePlateInput
|
||||
v-model="form.licensePlate"
|
||||
v-model:allowAny="allowAnyLicensePlate"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -116,6 +126,7 @@
|
||||
:loading="isLoadingVehicles"
|
||||
:disabled="isLoadingVehicles || filteredVehicles.length === 0"
|
||||
wrapper-class="col-start-2 row-start-4"
|
||||
required
|
||||
/>
|
||||
|
||||
<div class="col-start-2 row-start-5 min-h-[72px]">
|
||||
@@ -129,6 +140,7 @@
|
||||
label: driver.name
|
||||
}))"
|
||||
:loading="isLoadingDrivers"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,14 +149,20 @@
|
||||
<div class="flex justify-evenly gap-y-8 gap-x-41 mb-10 border-b border-primary-500/60">
|
||||
<h1
|
||||
class="font-bold text-3xl uppercase px-12 col-start-1 row-start-1 cursor-pointer"
|
||||
:class="activeTab === 'weights' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50'"
|
||||
:class="[
|
||||
activeTab === 'weights' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
|
||||
hasGrossWeightError ? '!text-red-500 !border-red-500' : ''
|
||||
]"
|
||||
@click="activeTab = 'weights'"
|
||||
>
|
||||
pesée à plein
|
||||
</h1>
|
||||
<h1
|
||||
class="font-bold text-3xl uppercase col-start-1 row-start-1 px-12 cursor-pointer"
|
||||
:class="activeTab === 'weightsEmpty' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50'"
|
||||
:class="[
|
||||
activeTab === 'weightsEmpty' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50',
|
||||
hasTareWeightError ? '!text-red-500 !border-red-500' : ''
|
||||
]"
|
||||
@click="activeTab = 'weightsEmpty'"
|
||||
>
|
||||
pesée à vide
|
||||
@@ -170,6 +188,7 @@
|
||||
<UiButton
|
||||
type="submit"
|
||||
class="text-xl mb-16 uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
|
||||
@click="submitted = true"
|
||||
>
|
||||
Valider
|
||||
</UiButton>
|
||||
@@ -220,6 +239,15 @@ const currentShipment = ref<ShipmentData | null>(null)
|
||||
const selectedShipmentTypeId = ref('')
|
||||
const shipmentQuantity = ref<number | null>(0)
|
||||
const allowAnyLicensePlate = ref(false)
|
||||
const submitted = ref(false)
|
||||
|
||||
const hasGrossWeightError = computed(() =>
|
||||
submitted.value && (grossWeight.value.weight === null || grossWeight.value.weighedAt === null || grossWeight.value.dsd === null)
|
||||
)
|
||||
const hasTareWeightError = computed(() =>
|
||||
submitted.value && (tareWeight.value.weight === null || tareWeight.value.weighedAt === null || tareWeight.value.dsd === null)
|
||||
)
|
||||
|
||||
const activeTab = ref<'weightsEmpty' | 'weights'>('weights')
|
||||
const grossWeight = ref<WeightEntryData>(createEmptyWeightEntry('gross'))
|
||||
const tareWeight = ref<WeightEntryData>(createEmptyWeightEntry('tare'))
|
||||
@@ -376,7 +404,7 @@ function hydrateFromShipment(shipment: ShipmentData | null) {
|
||||
isHydrating.value = true
|
||||
form.identificationNumber = shipment.identificationNumber ?? null
|
||||
form.licensePlate = shipment.licensePlate ?? ''
|
||||
form.shipmentDate = shipment.shipmentDate ?? new Date().toISOString().slice(0, 10)
|
||||
form.shipmentDate = shipment.shipmentDate?.slice(0, 10) ?? new Date().toISOString().slice(0, 10)
|
||||
form.userId = shipment.user?.id ? String(shipment.user.id) : form.userId
|
||||
form.customerId = shipment.customer?.id ? String(shipment.customer.id) : ''
|
||||
form.addressId = shipment.address?.id ? String(shipment.address.id) : ''
|
||||
@@ -613,6 +641,14 @@ async function validate() {
|
||||
return
|
||||
}
|
||||
|
||||
const hasInvalidWeights =
|
||||
grossWeight.value.weight === null || grossWeight.value.weighedAt === null || grossWeight.value.dsd === null ||
|
||||
tareWeight.value.weight === null || tareWeight.value.weighedAt === null || tareWeight.value.dsd === null
|
||||
|
||||
if (hasInvalidWeights) {
|
||||
return
|
||||
}
|
||||
|
||||
await updateShipment(shipmentId.value, {
|
||||
currentStep: currentShipment.value?.currentStep ?? 0,
|
||||
...buildPayload()
|
||||
|
||||
@@ -1,73 +1,85 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-10">
|
||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
||||
<h1 class="text-3xl font-bold uppercase text-primary-500">listes des expéditions en attente</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-[86px]">
|
||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
||||
<div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
||||
<div>Client</div>
|
||||
<div>Adresse</div>
|
||||
<div>Type d'expéditions</div>
|
||||
<div>Transporteur</div>
|
||||
<div>Immatriculation</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="shipment in shipmentList"
|
||||
:key="shipment.id"
|
||||
class="grid grid-cols-5 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="goToShipment(shipment.id)"
|
||||
@keydown.enter="goToShipment(shipment.id)"
|
||||
>
|
||||
<div>{{ shipment.customer?.name }}</div>
|
||||
<div>{{ shipment.address?.fullAddress }}</div>
|
||||
<div>
|
||||
<template v-if="formatShipmentLines(shipment).length">
|
||||
<div
|
||||
v-for="(line, index) in formatShipmentLines(shipment)"
|
||||
:key="index"
|
||||
class="leading-5"
|
||||
>
|
||||
{{ line }}
|
||||
</div>
|
||||
</template>
|
||||
<WorkflowWaitingList
|
||||
title="listes des expéditions en attente"
|
||||
:columns="columns"
|
||||
:items="shipmentList ?? []"
|
||||
route-prefix="/shipment"
|
||||
show-actions
|
||||
>
|
||||
<template #cell-shipmentDate="{ item }">
|
||||
{{ formatDate(item.shipmentDate) }}
|
||||
</template>
|
||||
<template #cell-shipmentType="{ item }">
|
||||
<template v-if="formatShipmentLines(item).length">
|
||||
<div
|
||||
v-for="(line, index) in formatShipmentLines(item)"
|
||||
:key="index"
|
||||
class="leading-5"
|
||||
>
|
||||
{{ line }}
|
||||
</div>
|
||||
<div>{{ shipment.carrier?.name }}</div>
|
||||
<div>{{ shipment.licensePlate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</template>
|
||||
<template #actions="{ item }">
|
||||
<Icon
|
||||
name="mdi:delete-outline"
|
||||
size="24"
|
||||
class="cursor-pointer text-red-500 hover:text-red-700"
|
||||
@click="confirmDelete(item)"
|
||||
/>
|
||||
</template>
|
||||
</WorkflowWaitingList>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ShipmentData } from '~/services/dto/shipment-data'
|
||||
import { getShipmentList, deleteShipment } from '~/services/shipment'
|
||||
|
||||
import type {ShipmentData} from "~/services/dto/shipment-data";
|
||||
import {getShipmentList} from "~/services/shipment";
|
||||
const columns = [
|
||||
{ key: 'shipmentDate', label: 'Date et heure' },
|
||||
{ key: 'customer.name', label: 'Client' },
|
||||
{ key: 'address.fullAddress', label: 'Adresse' },
|
||||
{ key: 'shipmentType', label: "Type d'expé." },
|
||||
{ key: 'carrier.name', label: 'Transporteur' },
|
||||
{ key: 'licensePlate', label: 'Immatriculation' }
|
||||
]
|
||||
|
||||
const shipmentList = ref<ShipmentData[]>()
|
||||
const router = useRouter()
|
||||
|
||||
const goToShipment = (id: number) => {
|
||||
router.push(`/shipment/${id}`)
|
||||
const formatDate = (date: string | null) => {
|
||||
if (!date) return '—'
|
||||
const d = new Date(date.replace(' ', 'T'))
|
||||
if (isNaN(d.getTime())) return date
|
||||
return d.toLocaleDateString('fr-FR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const formatShipmentLines = (shipment: ShipmentData) => {
|
||||
if (!shipment.shipmentType && shipment.nbBovinSend == null) {
|
||||
return []
|
||||
}
|
||||
|
||||
const label = typeof shipment.shipmentType === 'string'
|
||||
? shipment.shipmentType
|
||||
: shipment.shipmentType?.label
|
||||
|
||||
return [`${label ?? '—'} : ${shipment.nbBovinSend ?? '—'}`]
|
||||
}
|
||||
|
||||
const confirmDelete = async (shipment: ShipmentData) => {
|
||||
const confirmed = window.confirm(
|
||||
`Êtes-vous sûr de vouloir supprimer l'expédition ${shipment.identificationNumber ?? `#${shipment.id}`} ? Toutes les données liées seront supprimées.`
|
||||
)
|
||||
if (!confirmed) return
|
||||
|
||||
await deleteShipment(shipment.id)
|
||||
shipmentList.value = shipmentList.value?.filter(s => s.id !== shipment.id)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
shipmentList.value = await getShipmentList(false)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user