feat : ajout d'un composant pour le champ d'immatriculation, ajout de la lib maska pour le format des champs et correction de la gestion des mises en attentes des receptions
This commit is contained in:
Generated
+15
-9
@@ -4,15 +4,13 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="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">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/AGENTS.md" beforeDir="false" afterPath="$PROJECT_DIR$/AGENTS.md" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/composables/useApi.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/useApi.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/nuxt.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/nuxt.config.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package-lock.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/package.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/services/reception.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/reception.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -47,7 +45,6 @@
|
|||||||
<commands />
|
<commands />
|
||||||
<urls />
|
<urls />
|
||||||
</component>
|
</component>
|
||||||
<component name="PhpDebugGeneral" listening_started="true" />
|
|
||||||
<component name="PhpServers">
|
<component name="PhpServers">
|
||||||
<servers>
|
<servers>
|
||||||
<server host="localhost" id="36c0c232-9151-4654-a36c-e0f5fd99da91" name="ferme-docker" port="8080" use_path_mappings="true">
|
<server host="localhost" id="36c0c232-9151-4654-a36c-e0f5fd99da91" name="ferme-docker" port="8080" use_path_mappings="true">
|
||||||
@@ -260,7 +257,7 @@
|
|||||||
<workItem from="1768287908317" duration="28058000" />
|
<workItem from="1768287908317" duration="28058000" />
|
||||||
<workItem from="1768374298711" duration="12403000" />
|
<workItem from="1768374298711" duration="12403000" />
|
||||||
<workItem from="1768460547451" duration="26946000" />
|
<workItem from="1768460547451" duration="26946000" />
|
||||||
<workItem from="1768547023783" duration="7809000" />
|
<workItem from="1768547023783" duration="11371000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
|
<task id="LOCAL-00001" summary="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -326,7 +323,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1768498751836</updated>
|
<updated>1768498751836</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="9" />
|
<task id="LOCAL-00009" summary="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1768555180530</created>
|
||||||
|
<option name="number" value="00009" />
|
||||||
|
<option name="presentableId" value="LOCAL-00009" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1768555180530</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="10" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -381,6 +386,7 @@
|
|||||||
<MESSAGE value="fix : correction du useApi pour qu'il n'y ait plus de retry lors d'une erreur 500 par exemple" />
|
<MESSAGE value="fix : correction du useApi pour qu'il n'y ait plus de retry lors d'une erreur 500 par exemple" />
|
||||||
<MESSAGE value="test : ajout de TU sur les services et providers" />
|
<MESSAGE value="test : ajout de TU sur les services et providers" />
|
||||||
<MESSAGE value="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" />
|
<MESSAGE value="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" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="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" />
|
<MESSAGE value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,15 +2,11 @@
|
|||||||
<form @submit.prevent="validate">
|
<form @submit.prevent="validate">
|
||||||
<div class="grid grid-cols-1 items-start gap-8 mb-16">
|
<div class="grid grid-cols-1 items-start gap-8 mb-16">
|
||||||
<h1 class="font-bold text-5xl uppercase">Réception</h1>
|
<h1 class="font-bold text-5xl uppercase">Réception</h1>
|
||||||
<div class="flex flex-col">
|
<div>
|
||||||
<label for="license-plate" class="font-bold uppercase text-xl mb-4">Immatriculation</label>
|
<UiLicensePlateInput
|
||||||
<input
|
|
||||||
id="license-plate"
|
|
||||||
v-model="form.licensePlate"
|
v-model="form.licensePlate"
|
||||||
type="text"
|
v-model:allowAny="allowAnyLicensePlate"
|
||||||
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
|
||||||
/>
|
/>
|
||||||
<p v-if="fieldErrors.licensePlate" class="text-red-600 text-sm">{{ fieldErrors.licensePlate }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label for="reception-date" class="font-bold uppercase text-xl mb-4">Date de reception</label>
|
<label for="reception-date" class="font-bold uppercase text-xl mb-4">Date de reception</label>
|
||||||
@@ -20,7 +16,6 @@
|
|||||||
type="date"
|
type="date"
|
||||||
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
||||||
/>
|
/>
|
||||||
<p v-if="fieldErrors.receptionDate" class="text-red-600 text-sm">{{ fieldErrors.receptionDate }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@@ -34,8 +29,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { z } from 'zod'
|
|
||||||
import { mapZodErrors } from '~/utils/zod-errors'
|
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
|
||||||
type ReceptionFormData = {
|
type ReceptionFormData = {
|
||||||
@@ -49,20 +42,7 @@ const form = reactive<ReceptionFormData>({
|
|||||||
licensePlate: '',
|
licensePlate: '',
|
||||||
receptionDate: new Date().toISOString().slice(0, 10)
|
receptionDate: new Date().toISOString().slice(0, 10)
|
||||||
})
|
})
|
||||||
const fieldErrors = reactive<Partial<Record<keyof ReceptionFormData, string>>>({
|
const allowAnyLicensePlate = ref(false)
|
||||||
licensePlate: undefined,
|
|
||||||
receptionDate: undefined
|
|
||||||
})
|
|
||||||
const formSchema = z.object({
|
|
||||||
licensePlate: z
|
|
||||||
.string()
|
|
||||||
.min(1, 'Immatriculation requise.')
|
|
||||||
.max(20, 'Immatriculation trop longue (20 caracteres max).'),
|
|
||||||
receptionDate: z
|
|
||||||
.string()
|
|
||||||
.min(1, 'Date de reception requise.')
|
|
||||||
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date de reception invalide.')
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => receptionStore.current,
|
() => receptionStore.current,
|
||||||
@@ -74,26 +54,14 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
async function validate() {
|
async function validate() {
|
||||||
fieldErrors.licensePlate = undefined
|
|
||||||
fieldErrors.receptionDate = undefined
|
|
||||||
const normalizedLicensePlate = form.licensePlate.trim()
|
const normalizedLicensePlate = form.licensePlate.trim()
|
||||||
const normalizedReceptionDate = form.receptionDate.trim()
|
const normalizedReceptionDate = form.receptionDate.trim()
|
||||||
const result = formSchema.safeParse({
|
|
||||||
licensePlate: normalizedLicensePlate,
|
|
||||||
receptionDate: normalizedReceptionDate
|
|
||||||
})
|
|
||||||
if (!result.success) {
|
|
||||||
const errors = mapZodErrors<ReceptionFormData>(result.error)
|
|
||||||
fieldErrors.licensePlate = errors.licensePlate ?? 'Formulaire invalide.'
|
|
||||||
fieldErrors.receptionDate = errors.receptionDate ?? 'Formulaire invalide.'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!receptionStore.current) {
|
if (!receptionStore.current) {
|
||||||
const created = await receptionStore.createReception({
|
const created = await receptionStore.createReception({
|
||||||
currentStep: 1,
|
currentStep: 1,
|
||||||
licensePlate: normalizedLicensePlate || null,
|
licensePlate: normalizedLicensePlate,
|
||||||
receptionDate: normalizedReceptionDate || null
|
receptionDate: normalizedReceptionDate
|
||||||
})
|
})
|
||||||
if (created) {
|
if (created) {
|
||||||
await router.push(`/reception/${created.id}`)
|
await router.push(`/reception/${created.id}`)
|
||||||
@@ -104,8 +72,8 @@ async function validate() {
|
|||||||
const nextStep = receptionStore.current.currentStep + 1
|
const nextStep = receptionStore.current.currentStep + 1
|
||||||
await receptionStore.updateReception(receptionStore.current.id, {
|
await receptionStore.updateReception(receptionStore.current.id, {
|
||||||
currentStep: nextStep,
|
currentStep: nextStep,
|
||||||
licensePlate: normalizedLicensePlate || null,
|
licensePlate: normalizedLicensePlate,
|
||||||
receptionDate: normalizedReceptionDate || null
|
receptionDate: normalizedReceptionDate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label :for="inputId" class="font-bold uppercase text-xl mb-4">{{ label }}</label>
|
||||||
|
<input
|
||||||
|
:id="inputId"
|
||||||
|
:value="modelValue"
|
||||||
|
v-maska="maskOptions"
|
||||||
|
type="text"
|
||||||
|
:maxlength="maxLength"
|
||||||
|
:placeholder="placeholderText"
|
||||||
|
class="border-b border-black justify-self-start text-xl pb-[6px] uppercase"
|
||||||
|
@input="handleInput"
|
||||||
|
/>
|
||||||
|
<label :for="checkboxId" class="mt-3 flex items-center gap-3 text-sm">
|
||||||
|
<input
|
||||||
|
:id="checkboxId"
|
||||||
|
:checked="allowAny"
|
||||||
|
type="checkbox"
|
||||||
|
class="h-4 w-4 accent-primary-500"
|
||||||
|
@change="toggleAllowAny"
|
||||||
|
/>
|
||||||
|
Autoriser un format libre
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { vMaska } from 'maska/vue'
|
||||||
|
type Props = {
|
||||||
|
modelValue: string
|
||||||
|
allowAny?: boolean
|
||||||
|
label?: string
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
allowAny: false,
|
||||||
|
label: 'Immatriculation',
|
||||||
|
id: 'license-plate'
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:modelValue', value: string): void
|
||||||
|
(event: 'update:allowAny', value: boolean): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const inputId = computed(() => props.id)
|
||||||
|
const checkboxId = computed(() => `${props.id}-format`)
|
||||||
|
|
||||||
|
const maskOptions = computed(() =>
|
||||||
|
props.allowAny
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
mask: '@@-###-@@',
|
||||||
|
eager: true,
|
||||||
|
tokens: {
|
||||||
|
'@': {
|
||||||
|
pattern: /[A-Za-z]/,
|
||||||
|
transform: (char: string) => char.toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const placeholderText = computed(() => (props.allowAny ? '' : 'AA-123-AA'))
|
||||||
|
const maxLength = computed(() => (props.allowAny ? 20 : 9))
|
||||||
|
|
||||||
|
const handleInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement | null
|
||||||
|
if (!target) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.allowAny) {
|
||||||
|
emit('update:modelValue', target.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update:modelValue', target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleAllowAny = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement | null
|
||||||
|
if (!target) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextValue = target.checked
|
||||||
|
emit('update:allowAny', nextValue)
|
||||||
|
if (!nextValue) {
|
||||||
|
emit('update:modelValue', props.modelValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Generated
+6
@@ -10,6 +10,7 @@
|
|||||||
"@nuxtjs/i18n": "^10.2.1",
|
"@nuxtjs/i18n": "^10.2.1",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"izitoast": "^1.4.0",
|
"izitoast": "^1.4.0",
|
||||||
|
"maska": "^3.2.0",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"nuxt-toast": "^1.4.0",
|
"nuxt-toast": "^1.4.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
@@ -9195,6 +9196,11 @@
|
|||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/maska": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/maska/-/maska-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-zSmSgs5/q9vMSmrdZT3rKOv9uLznNWR/niuuAdBZDTvB3SMKOX9vhMtDijFyExz+B4UClu2rvksylUh/ea1bLA=="
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@nuxtjs/i18n": "^10.2.1",
|
"@nuxtjs/i18n": "^10.2.1",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"izitoast": "^1.4.0",
|
"izitoast": "^1.4.0",
|
||||||
|
"maska": "^3.2.0",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"nuxt-toast": "^1.4.0",
|
"nuxt-toast": "^1.4.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between h-[52px] mb-[90px]">
|
<div class="flex justify-between h-[52px] mb-[90px]">
|
||||||
<p class="self-center">Indicateur d’étapes</p>
|
<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>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex flex-col justify-center uppercase text-xl bg-black text-white h-[50px] w-[272px] text-center"
|
||||||
|
@click="saveAndHold"
|
||||||
|
>Mettre en attente</button>
|
||||||
</div>
|
</div>
|
||||||
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
|
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
|
||||||
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
||||||
@@ -21,13 +25,39 @@ const router = useRouter()
|
|||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
const { current: storeReception } = storeToRefs(receptionStore)
|
const { current: storeReception } = storeToRefs(receptionStore)
|
||||||
|
|
||||||
onMounted(async () => {
|
const resolveReceptionId = (param: unknown) => {
|
||||||
const raw = route.params.id
|
const idStr = Array.isArray(param) ? param[0] : param
|
||||||
const idStr = Array.isArray(raw) ? raw[0] : raw
|
if (!idStr) {
|
||||||
const id = idStr ? Number(idStr) : null
|
return null
|
||||||
|
|
||||||
if (id !== null) {
|
|
||||||
await receptionStore.loadReception(id)
|
|
||||||
}
|
}
|
||||||
})
|
const id = Number(idStr)
|
||||||
|
return Number.isFinite(id) ? id : null
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
async (param) => {
|
||||||
|
const id = resolveReceptionId(param)
|
||||||
|
if (id === null) {
|
||||||
|
receptionStore.clearCurrent()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await receptionStore.loadReception(id)
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const saveAndHold = async () => {
|
||||||
|
if (!receptionStore.current) {
|
||||||
|
await router.push('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await receptionStore.updateReception(receptionStore.current.id, {
|
||||||
|
currentStep: receptionStore.current.currentStep,
|
||||||
|
licensePlate: receptionStore.current.licensePlate,
|
||||||
|
receptionDate: receptionStore.current.receptionDate
|
||||||
|
})
|
||||||
|
await router.push('/')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user