feat : ajout de la génération du bon de reception, correction de la base du formulaire multi-etape de reception et ajout d'une gestion d'erreur global
This commit is contained in:
@@ -27,15 +27,13 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] justify-self-end"
|
||||
>Valider
|
||||
>Peser
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="errorMessage" class="text-red-600 mt-4">{{ errorMessage }}</p>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { z } from 'zod'
|
||||
import { mapZodErrors } from '~/utils/zod-errors'
|
||||
import { useReceptionStore } from '~/stores/reception'
|
||||
@@ -45,17 +43,16 @@ type ReceptionFormData = {
|
||||
receptionDate: string
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const receptionStore = useReceptionStore()
|
||||
const { errorMessage: storeErrorMessage, current: storeReception } = storeToRefs(receptionStore)
|
||||
const form = reactive<ReceptionFormData>({
|
||||
licensePlate: '',
|
||||
receptionDate: ''
|
||||
receptionDate: new Date().toISOString().slice(0, 10)
|
||||
})
|
||||
const fieldErrors = reactive<Partial<Record<keyof ReceptionFormData, string>>>({
|
||||
licensePlate: undefined,
|
||||
receptionDate: undefined
|
||||
})
|
||||
const errorMessage = computed(() => storeErrorMessage.value)
|
||||
const formSchema = z.object({
|
||||
licensePlate: z
|
||||
.string()
|
||||
@@ -68,19 +65,15 @@ const formSchema = z.object({
|
||||
})
|
||||
|
||||
watch(
|
||||
storeReception,
|
||||
() => receptionStore.current,
|
||||
(reception) => {
|
||||
form.licensePlate = reception?.licensePlate ?? ''
|
||||
form.receptionDate = reception?.receptionDate ?? ''
|
||||
form.receptionDate = reception?.receptionDate ?? new Date().toISOString().slice(0, 10)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
async function validate() {
|
||||
if (!receptionStore.current) {
|
||||
return
|
||||
}
|
||||
|
||||
fieldErrors.licensePlate = undefined
|
||||
fieldErrors.receptionDate = undefined
|
||||
const normalizedLicensePlate = form.licensePlate.trim()
|
||||
@@ -96,6 +89,18 @@ async function validate() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!receptionStore.current) {
|
||||
const created = await receptionStore.createReception({
|
||||
currentStep: 1,
|
||||
licensePlate: normalizedLicensePlate || null,
|
||||
receptionDate: normalizedReceptionDate || null
|
||||
})
|
||||
if (created) {
|
||||
await router.push(`/reception/${created.id}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const nextStep = receptionStore.current.currentStep + 1
|
||||
await receptionStore.updateReception(receptionStore.current.id, {
|
||||
currentStep: nextStep,
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<div class="flex flex-col items-center mt-[164px] gap-32">
|
||||
<div class="flex gap-8 items-center justify-center">
|
||||
<!--@TODO Prendre en compte que l'on peut aussi décharger de la marchandise-->
|
||||
<h1 class="text-3xl uppercase font-bold">Décharger les bêtes</h1>
|
||||
<h1 class="text-4xl uppercase font-bold">Décharger les bêtes</h1>
|
||||
<UiLoadingDots />
|
||||
</div>
|
||||
<button
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||
@click="goNext"
|
||||
>Suivant</button>
|
||||
>Peser</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -5,20 +5,19 @@
|
||||
<!--@TODO Voir comment faire pour savoir si le pont-bascule et bien connecté + ajouter un icon comme sur la maquette-->
|
||||
<p class="text-primary-500 uppercase text-2xl mt-2">Pont-bascule connecté</p>
|
||||
<div
|
||||
v-if="errorMessage || showLoadingBox"
|
||||
v-if="showLoadingBox"
|
||||
class="w-full flex flex-col items-center justify-center border border-black h-[90px] mt-12 mb-[86px]">
|
||||
<p v-if="errorMessage" class="text-red-500">{{ errorMessage }}</p>
|
||||
<UiLoadingDots v-else />
|
||||
<UiLoadingDots />
|
||||
</div>
|
||||
<div v-else-if="displayWeight !== null" class="w-full">
|
||||
<div
|
||||
class="w-full flex flex-col items-center justify-center border border-black h-[90px] mt-12 mb-[25px] text-4xl">
|
||||
{{ displayWeight }} kg
|
||||
</div>
|
||||
<div class="grid grid-cols-2 border border-black text-center">
|
||||
<p class="border-r border-black py-3 text-4xl font-bold">DSD</p>
|
||||
<p class="py-3 text-4xl">{{ displayDsd }}</p>
|
||||
</div>
|
||||
<!-- <div class="grid grid-cols-2 border border-black text-center">-->
|
||||
<!-- <p class="border-r border-black py-3 text-4xl font-bold">DSD</p>-->
|
||||
<!-- <p class="py-3 text-4xl">{{ displayDsd }}</p>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,15 +27,22 @@
|
||||
@click="fetchWeight"
|
||||
>{{ displayWeight !== null ? 'refaire une pesee' : 'peser' }}</button>
|
||||
<button
|
||||
v-if="displayWeight !== null"
|
||||
v-if="displayWeight !== null && !showGenerateReceipt"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
|
||||
@click="saveWeight"
|
||||
>Valider la pesée</button>
|
||||
<button
|
||||
v-if="showGenerateReceipt"
|
||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] ml-4"
|
||||
@click="printReceipt"
|
||||
>Générer le bon</button>
|
||||
</div>
|
||||
<UiPdfPrinter ref="pdfPrinter" />
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useWeighing } from '~/composables/useWeighing'
|
||||
import { useReceptionStore } from '~/stores/reception'
|
||||
@@ -45,13 +51,18 @@ const props = defineProps<{
|
||||
mode: 'gross' | 'tare'
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const receptionStore = useReceptionStore()
|
||||
const { current: storeReception, errorMessage: storeErrorMessage } = storeToRefs(receptionStore)
|
||||
const { current: storeReception } = storeToRefs(receptionStore)
|
||||
type PdfPrinterHandle = {
|
||||
print: (url: string) => Promise<void>
|
||||
}
|
||||
// Ref sur le composant d'impression pour déclencher le print() du PDF.
|
||||
const pdfPrinter = ref<PdfPrinterHandle | null>(null)
|
||||
const {
|
||||
displayWeight,
|
||||
displayDsd,
|
||||
title,
|
||||
errorMessage,
|
||||
showLoadingBox,
|
||||
fetchWeight,
|
||||
saveWeight
|
||||
@@ -59,9 +70,35 @@ const {
|
||||
mode: props.mode,
|
||||
reception: storeReception,
|
||||
updateReception: receptionStore.updateReception,
|
||||
loadReception: receptionStore.loadReception,
|
||||
storeError: storeErrorMessage
|
||||
loadReception: receptionStore.loadReception
|
||||
})
|
||||
// @TODO Voir comment mettre en place la genération du bon, la validation de la reception et le dernier step
|
||||
const showGenerateReceipt = computed(
|
||||
() => props.mode === 'tare' && displayWeight.value !== null
|
||||
)
|
||||
|
||||
const printReceipt = async () => {
|
||||
if (!import.meta.client || !receptionStore.current || !pdfPrinter.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await saveWeight()
|
||||
await pdfPrinter.value.print(`/receptions/${receptionStore.current.id}/receipt`)
|
||||
|
||||
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
|
||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||
|
||||
const result = await receptionStore.updateReception(receptionStore.current.id, {
|
||||
isValid: true
|
||||
})
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
|
||||
receptionStore.clearCurrent()
|
||||
await router.push('/')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchWeight()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<iframe ref="printFrame" class="hidden" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { usePdfPrinter } from '~/composables/usePdfPrinter'
|
||||
|
||||
const printFrame = ref<HTMLIFrameElement | null>(null)
|
||||
const { printPdf } = usePdfPrinter()
|
||||
|
||||
// Expose une methode simple pour imprimer un PDF depuis les ecrans.
|
||||
const print = async (url: string): Promise<void> => {
|
||||
return printPdf(url, printFrame)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
print
|
||||
})
|
||||
</script>
|
||||
@@ -4,22 +4,72 @@ import { $fetch, FetchError } from 'ofetch'
|
||||
export type AnyObject = Record<string, unknown>
|
||||
|
||||
export type ApiClient = {
|
||||
get<T>(url: string, query?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
|
||||
post<T>(url: string, body?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
|
||||
put<T>(url: string, body?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
|
||||
patch<T>(url: string, body?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
|
||||
delete<T>(url: string, query?: AnyObject, options?: FetchOptions<'json'>): Promise<T>
|
||||
get<T>(url: string, query?: AnyObject, options?: ApiFetchOptions<'json'>): Promise<T>
|
||||
getBlob(url: string, query?: AnyObject, options?: ApiFetchOptions<'blob'>): Promise<Blob>
|
||||
post<T>(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise<T>
|
||||
put<T>(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise<T>
|
||||
patch<T>(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise<T>
|
||||
delete<T>(url: string, query?: AnyObject, options?: ApiFetchOptions<'json'>): Promise<T>
|
||||
}
|
||||
|
||||
export type ApiFetchOptions<ResponseType extends 'json' | 'blob'> =
|
||||
FetchOptions<ResponseType> & {
|
||||
toast?: boolean
|
||||
toastTitle?: string
|
||||
}
|
||||
|
||||
export const useApi = (): ApiClient => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseURL = config.public.apiBase ?? '/api'
|
||||
const client = $fetch.create({ baseURL, retry: 0 })
|
||||
const toast = useToast()
|
||||
|
||||
const extractErrorMessage = (error: unknown, responseData?: unknown): string => {
|
||||
const data = responseData ?? (error as FetchError)?.data
|
||||
|
||||
if (typeof data === 'string') {
|
||||
return data
|
||||
}
|
||||
|
||||
if (data && typeof data === 'object') {
|
||||
const record = data as Record<string, unknown>
|
||||
return (
|
||||
(record['hydra:description'] as string) ||
|
||||
(record.detail as string) ||
|
||||
(record.message as string) ||
|
||||
(record.error as string) ||
|
||||
(record.title as string) ||
|
||||
(record['hydra:title'] as string) ||
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
return (error as FetchError)?.message ?? 'Erreur inconnue.'
|
||||
}
|
||||
|
||||
const client = $fetch.create({
|
||||
baseURL,
|
||||
retry: 0,
|
||||
onResponseError({ response, error, options }) {
|
||||
const apiOptions = options as ApiFetchOptions<'json'>
|
||||
if (apiOptions?.toast === false) {
|
||||
return
|
||||
}
|
||||
|
||||
const message =
|
||||
extractErrorMessage(error, response?._data) ||
|
||||
'Une erreur est survenue.'
|
||||
|
||||
toast.error({
|
||||
title: apiOptions?.toastTitle ?? 'Erreur',
|
||||
message
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const request = <T>(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
||||
url: string,
|
||||
options: FetchOptions<'json'> = {}
|
||||
options: ApiFetchOptions<'json'> = {}
|
||||
) => {
|
||||
const needsJsonBody = method === 'POST' || method === 'PUT'
|
||||
const needsMergePatch = method === 'PATCH'
|
||||
@@ -36,19 +86,22 @@ export const useApi = (): ApiClient => {
|
||||
}
|
||||
|
||||
return {
|
||||
get<T>(url: string, query: AnyObject = {}, options: FetchOptions<'json'> = {}) {
|
||||
get<T>(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||
return request<T>('GET', url, { ...options, query })
|
||||
},
|
||||
post<T>(url: string, body: AnyObject = {}, options: FetchOptions<'json'> = {}) {
|
||||
getBlob(url: string, query: AnyObject = {}, options: ApiFetchOptions<'blob'> = {}) {
|
||||
return client<Blob>(url, { ...options, method: 'GET', query, responseType: 'blob' })
|
||||
},
|
||||
post<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||
return request<T>('POST', url, { ...options, body })
|
||||
},
|
||||
put<T>(url: string, body: AnyObject = {}, options: FetchOptions<'json'> = {}) {
|
||||
put<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||
return request<T>('PUT', url, { ...options, body })
|
||||
},
|
||||
patch<T>(url: string, body: AnyObject = {}, options: FetchOptions<'json'> = {}) {
|
||||
patch<T>(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||
return request<T>('PATCH', url, { ...options, body })
|
||||
},
|
||||
delete<T>(url: string, query: AnyObject = {}, options: FetchOptions<'json'> = {}) {
|
||||
delete<T>(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) {
|
||||
return request<T>('DELETE', url, { ...options, query })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { useApi } from '~/composables/useApi'
|
||||
|
||||
type PrintFrameRef = Ref<HTMLIFrameElement | null>
|
||||
|
||||
export const usePdfPrinter = () => {
|
||||
const api = useApi()
|
||||
|
||||
const printPdf = async (url: string, frameRef: PrintFrameRef): Promise<void> => {
|
||||
if (!import.meta.client) {
|
||||
return
|
||||
}
|
||||
|
||||
const frame = frameRef.value
|
||||
if (!frame) {
|
||||
return
|
||||
}
|
||||
|
||||
// On charge le PDF en blob pour rester en same-origin dans l'iframe.
|
||||
const blob = await api.getBlob(url)
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
|
||||
const tryPrint = () => {
|
||||
frame.contentWindow?.focus()
|
||||
frame.contentWindow?.print()
|
||||
}
|
||||
|
||||
frame.onload = () => {
|
||||
tryPrint()
|
||||
// On libere l'URL blob apres l'impression.
|
||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 2000)
|
||||
}
|
||||
frame.src = blobUrl
|
||||
setTimeout(tryPrint, 1200)
|
||||
}
|
||||
|
||||
return {
|
||||
printPdf
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { ReceptionData, WeightEntryData } from '~/services/dto/reception-data'
|
||||
import type { WeightData } from '~/services/dto/weight-data'
|
||||
import { getWeight } from '~/services/reception'
|
||||
import { createWeight, updateWeight } from '~/services/weight'
|
||||
import type {Ref} from 'vue'
|
||||
import {computed, ref} from 'vue'
|
||||
import type {ReceptionData, WeightEntryData} from '~/services/dto/reception-data'
|
||||
import type {WeightData} from '~/services/dto/weight-data'
|
||||
import {getWeight} from '~/services/reception'
|
||||
import {createWeight, updateWeight} from '~/services/weight'
|
||||
|
||||
export type WeighingMode = 'gross' | 'tare'
|
||||
|
||||
@@ -12,18 +12,16 @@ type UseWeighingOptions = {
|
||||
reception: Ref<ReceptionData | null>
|
||||
updateReception: (id: number, payload: Partial<ReceptionData>) => Promise<ReceptionData | null>
|
||||
loadReception?: (id: number) => Promise<ReceptionData | null>
|
||||
storeError?: Ref<string | null>
|
||||
}
|
||||
|
||||
export const useWeighing = ({
|
||||
mode,
|
||||
reception,
|
||||
updateReception,
|
||||
loadReception,
|
||||
storeError
|
||||
loadReception
|
||||
}: UseWeighingOptions) => {
|
||||
const weightData = ref<WeightData | null>(null)
|
||||
const localErrorMessage = ref<string | null>(null)
|
||||
const isFetching = ref(false)
|
||||
|
||||
const currentWeightEntry = computed<WeightEntryData | null>(() => {
|
||||
const weights = reception.value?.weights ?? []
|
||||
@@ -33,22 +31,19 @@ export const useWeighing = ({
|
||||
const displayWeight = computed(() => weightData.value?.weight ?? currentWeightEntry.value?.weight ?? null)
|
||||
const displayDsd = computed(() => weightData.value?.dsd ?? currentWeightEntry.value?.dsd ?? '-')
|
||||
const title = computed(() => (mode === 'gross' ? 'Pesée à plein' : 'Pesée à vide'))
|
||||
const errorMessage = computed(() => localErrorMessage.value ?? storeError?.value ?? null)
|
||||
const showLoadingBox = computed(() => displayWeight.value === null && !errorMessage.value)
|
||||
const showLoadingBox = computed(
|
||||
() => isFetching.value || (displayWeight.value === null && currentWeightEntry.value === null)
|
||||
)
|
||||
|
||||
const fetchWeight = async () => {
|
||||
localErrorMessage.value = null
|
||||
try {
|
||||
weightData.value = await getWeight()
|
||||
} catch (error) {
|
||||
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
|
||||
}
|
||||
isFetching.value = true
|
||||
weightData.value = await getWeight().finally(() => {
|
||||
isFetching.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const saveWeight = async () => {
|
||||
localErrorMessage.value = null
|
||||
if (!reception.value) {
|
||||
localErrorMessage.value = 'Réception introuvable.'
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,36 +53,32 @@ export const useWeighing = ({
|
||||
const baseWeighedAt = weightData.value?.weighedAt ?? existingEntry?.weighedAt ?? null
|
||||
|
||||
if (baseWeight === null) {
|
||||
localErrorMessage.value = 'Veuillez d’abord peser.'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (existingEntry?.id) {
|
||||
await updateWeight(existingEntry.id, {
|
||||
type: mode,
|
||||
dsd: baseDsd,
|
||||
weight: baseWeight,
|
||||
weighedAt: baseWeighedAt
|
||||
})
|
||||
} else {
|
||||
await createWeight({
|
||||
reception: `/receptions/${reception.value.id}`,
|
||||
type: mode,
|
||||
dsd: baseDsd,
|
||||
weight: baseWeight,
|
||||
weighedAt: baseWeighedAt
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
localErrorMessage.value = error?.data?.error ?? error?.message ?? 'Erreur inconnue.'
|
||||
return
|
||||
if (existingEntry?.id) {
|
||||
await updateWeight(existingEntry.id, {
|
||||
type: mode,
|
||||
dsd: baseDsd,
|
||||
weight: baseWeight,
|
||||
weighedAt: baseWeighedAt
|
||||
})
|
||||
} else {
|
||||
await createWeight({
|
||||
reception: `/receptions/${reception.value.id}`,
|
||||
type: mode,
|
||||
dsd: baseDsd,
|
||||
weight: baseWeight,
|
||||
weighedAt: baseWeighedAt
|
||||
})
|
||||
}
|
||||
|
||||
const nextStep = reception.value.currentStep + 1
|
||||
const nextStep = mode === 'tare'
|
||||
? reception.value.currentStep
|
||||
: reception.value.currentStep + 1
|
||||
await updateReception(reception.value.id, {
|
||||
currentStep: nextStep,
|
||||
isValid: mode === 'tare' ? true : reception.value.isValid
|
||||
isValid: reception.value.isValid
|
||||
})
|
||||
|
||||
if (loadReception) {
|
||||
@@ -101,7 +92,6 @@ export const useWeighing = ({
|
||||
displayWeight,
|
||||
displayDsd,
|
||||
title,
|
||||
errorMessage,
|
||||
showLoadingBox,
|
||||
fetchWeight,
|
||||
saveWeight
|
||||
|
||||
@@ -2,7 +2,7 @@ export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true },
|
||||
ssr: false,
|
||||
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
|
||||
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt', 'nuxt-toast'],
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: process.env.NUXT_PUBLIC_API_BASE
|
||||
|
||||
Generated
+18
@@ -8,7 +8,9 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"izitoast": "^1.4.0",
|
||||
"nuxt": "^4.2.2",
|
||||
"nuxt-toast": "^1.4.0",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.26",
|
||||
"vue-router": "^4.6.4",
|
||||
@@ -6463,6 +6465,11 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/izitoast": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/izitoast/-/izitoast-1.4.0.tgz",
|
||||
"integrity": "sha512-Oc1X2wiQtPp39i5VpIjf3GJf5sfCtHKXZ5szx7RareyEeFLUlcEW0FSfBni28+Ul6KNKZRKzhVuWzSP4Xngh0w=="
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
@@ -8056,6 +8063,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nuxt-toast": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/nuxt-toast/-/nuxt-toast-1.4.0.tgz",
|
||||
"integrity": "sha512-LGqzlRlLj4ildwAPL8w0MEQWN++DNLYhYPLT+ONkanofTI0yY/5HcUa4x+Y0vozII/Z1e51O3v1hDyCzEWFcHQ==",
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^4.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"izitoast": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.11.3",
|
||||
"izitoast": "^1.4.0",
|
||||
"nuxt": "^4.2.2",
|
||||
"nuxt-toast": "^1.4.0",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.26",
|
||||
"vue-router": "^4.6.4",
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<div v-if="errorMessage" class="text-red-600">{{ errorMessage }}</div>
|
||||
<div v-else>
|
||||
<div>
|
||||
<div class="flex justify-between h-[52px] mb-[90px]">
|
||||
<p class="self-center">Indicateur d’étapes</p>
|
||||
<NuxtLink to="/" class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center">Mettre en attente</NuxtLink>
|
||||
</div>
|
||||
<ReceptionForm v-if="storeReception?.currentStep === 0"/>
|
||||
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
|
||||
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
||||
<ReceptionUnloading v-if="storeReception?.currentStep === 2"/>
|
||||
<ReceptionWeight v-if="storeReception?.currentStep === 3" mode="tare"/>
|
||||
<ReceptionWeight v-if="storeReception?.currentStep !== null && storeReception?.currentStep >= 3" mode="tare"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,16 +19,14 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const receptionStore = useReceptionStore()
|
||||
const { current: storeReception, errorMessage } = storeToRefs(receptionStore)
|
||||
const { current: storeReception } = storeToRefs(receptionStore)
|
||||
|
||||
onMounted(async () => {
|
||||
const raw = route.params.id
|
||||
const idStr = Array.isArray(raw) ? raw[0] : raw
|
||||
const id = idStr ? Number(idStr) : null
|
||||
|
||||
if (id === null) {
|
||||
await receptionStore.createReception()
|
||||
} else {
|
||||
if (id !== null) {
|
||||
await receptionStore.loadReception(id)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,49 +2,27 @@ import { useApi } from '~/composables/useApi'
|
||||
import type { ReceptionData } from '~/services/dto/reception-data'
|
||||
import type { WeightData } from '~/services/dto/weight-data'
|
||||
|
||||
const api = useApi()
|
||||
|
||||
export async function getReceptionList() {
|
||||
try {
|
||||
return await api.get<ReceptionData>(`receptions`)
|
||||
} catch (error) {
|
||||
console.error(error.message, error)
|
||||
return error
|
||||
}
|
||||
const api = useApi()
|
||||
return api.get<ReceptionData>(`receptions`)
|
||||
}
|
||||
|
||||
export async function getReception(id: number) {
|
||||
try {
|
||||
return await api.get<ReceptionData>(`receptions/${id}`)
|
||||
} catch (error) {
|
||||
console.error(error.message, error)
|
||||
return error
|
||||
}
|
||||
const api = useApi()
|
||||
return api.get<ReceptionData>(`receptions/${id}`)
|
||||
}
|
||||
|
||||
export async function createReception(payload: Partial<ReceptionData> = {}) {
|
||||
try {
|
||||
return await api.post<ReceptionData>('receptions', payload)
|
||||
} catch (error) {
|
||||
console.error(error.message, error)
|
||||
return error
|
||||
}
|
||||
const api = useApi()
|
||||
return api.post<ReceptionData>('receptions', payload)
|
||||
}
|
||||
|
||||
export async function updateReception(id: number, payload: Partial<ReceptionData>) {
|
||||
try {
|
||||
return await api.patch<ReceptionData>(`receptions/${id}`, payload)
|
||||
} catch (error) {
|
||||
console.error(error.message, error)
|
||||
return error
|
||||
}
|
||||
const api = useApi()
|
||||
return api.patch<ReceptionData>(`receptions/${id}`, payload)
|
||||
}
|
||||
|
||||
export async function getWeight(): Promise<WeightData> {
|
||||
try {
|
||||
return await api.get<WeightData>('receptions/weigh')
|
||||
} catch (error) {
|
||||
console.error(error.message, error)
|
||||
throw error
|
||||
}
|
||||
const api = useApi()
|
||||
return api.get<WeightData>('receptions/weigh')
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import type { WeightEntryData } from '~/services/dto/reception-data'
|
||||
|
||||
const api = useApi()
|
||||
|
||||
export type WeightPayload = {
|
||||
reception: string
|
||||
type: 'gross' | 'tare'
|
||||
@@ -12,19 +10,11 @@ export type WeightPayload = {
|
||||
}
|
||||
|
||||
export async function createWeight(payload: WeightPayload) {
|
||||
try {
|
||||
return await api.post<WeightEntryData>('weights', payload)
|
||||
} catch (error) {
|
||||
console.error(error.message, error)
|
||||
throw error
|
||||
}
|
||||
const api = useApi()
|
||||
return api.post<WeightEntryData>('weights', payload)
|
||||
}
|
||||
|
||||
export async function updateWeight(id: number, payload: Partial<WeightPayload>) {
|
||||
try {
|
||||
return await api.patch<WeightEntryData>(`weights/${id}`, payload)
|
||||
} catch (error) {
|
||||
console.error(error.message, error)
|
||||
throw error
|
||||
}
|
||||
const api = useApi()
|
||||
return api.patch<WeightEntryData>(`weights/${id}`, payload)
|
||||
}
|
||||
|
||||
@@ -9,64 +9,51 @@ const isReceptionData = (value: unknown): value is ReceptionData => {
|
||||
export const useReceptionStore = defineStore('reception', {
|
||||
state: () => ({
|
||||
current: null as ReceptionData | null,
|
||||
isLoading: false,
|
||||
errorMessage: null as string | null
|
||||
isLoading: false
|
||||
}),
|
||||
actions: {
|
||||
setCurrent(reception: ReceptionData | null) {
|
||||
this.current = reception
|
||||
},
|
||||
clearError() {
|
||||
this.errorMessage = null
|
||||
clearCurrent() {
|
||||
this.current = null
|
||||
},
|
||||
async loadReception(id: number) {
|
||||
this.isLoading = true
|
||||
this.errorMessage = null
|
||||
try {
|
||||
const result = await getReception(id)
|
||||
if (!isReceptionData(result)) {
|
||||
this.errorMessage = 'Réception introuvable.'
|
||||
this.current = null
|
||||
return null
|
||||
}
|
||||
|
||||
this.current = result
|
||||
return result
|
||||
} finally {
|
||||
const result = await getReception(id).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
if (!isReceptionData(result)) {
|
||||
this.current = null
|
||||
return null
|
||||
}
|
||||
|
||||
this.current = result
|
||||
return result
|
||||
},
|
||||
async createReception() {
|
||||
async createReception(payload: Partial<ReceptionData> = {}) {
|
||||
this.isLoading = true
|
||||
this.errorMessage = null
|
||||
try {
|
||||
const result = await createReception()
|
||||
if (!isReceptionData(result)) {
|
||||
this.errorMessage = 'Impossible de créer la réception.'
|
||||
return null
|
||||
}
|
||||
|
||||
this.current = result
|
||||
return result
|
||||
} finally {
|
||||
const result = await createReception(payload).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
if (!isReceptionData(result)) {
|
||||
return null
|
||||
}
|
||||
|
||||
this.current = result
|
||||
return result
|
||||
},
|
||||
async updateReception(id: number, payload: Partial<ReceptionData>) {
|
||||
this.isLoading = true
|
||||
this.errorMessage = null
|
||||
try {
|
||||
const result = await updateReception(id, payload)
|
||||
if (!isReceptionData(result)) {
|
||||
this.errorMessage = 'Impossible de mettre à jour la réception.'
|
||||
return null
|
||||
}
|
||||
|
||||
this.current = result
|
||||
return result
|
||||
} finally {
|
||||
const result = await updateReception(id, payload).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
if (!isReceptionData(result)) {
|
||||
return null
|
||||
}
|
||||
|
||||
this.current = result
|
||||
return result
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user