refactor(front) : formatDateFr mutualisé dans shared/utils/date + rendu déterministe (ERP-191)
Trois copies identiques de formatDateFr (logistique weighingTicketFormat, transport carriers/index, CarrierQualimatTab) fusionnées en un seul helper partagé. La nouvelle version lit la date directement dans la chaîne ISO (10 premiers caractères) au lieu de new Date(value).getDate() : un datetime porteur d'un offset (…+02:00, …Z) ne bascule plus d'un jour selon le fuseau du navigateur / runner CI, et reste cohérent avec l'écran d'édition (slice) et l'export serveur (format d/m/Y). weighingTicketFormat ré-exporte le helper (imports inchangés côté écrans). Tests de déterminisme fuseau ajoutés dans shared/utils/date.test.ts.
This commit is contained in:
@@ -5,23 +5,9 @@
|
||||
* mettre en forme la valeur déjà normalisée renvoyée par l'API.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Date courte française `JJ-MM-AAAA` (spec M5). Chaîne vide si la valeur est
|
||||
* absente ou invalide. Lit les composantes locales (cohérent avec l'affichage
|
||||
* des autres répertoires M1→M4).
|
||||
*/
|
||||
export function formatDateFr(value: string | null | undefined): string {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return ''
|
||||
}
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
return `${day}-${month}-${date.getFullYear()}`
|
||||
}
|
||||
// Date courte française `JJ-MM-AAAA` (spec M5) : helper partagé inter-modules
|
||||
// (mutualisé avec les répertoires M1→M4). Re-exporté ici pour les écrans M5.
|
||||
export { formatDateFr } from '~/shared/utils/date'
|
||||
|
||||
/**
|
||||
* Poids en kg avec séparateur de milliers (espace) + suffixe « Kg »
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { debounce } from '~/shared/utils/debounce'
|
||||
import { formatDateFr } from '~/shared/utils/date'
|
||||
import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
||||
|
||||
/**
|
||||
@@ -92,19 +93,6 @@ function isExpired(value: string): boolean {
|
||||
return date.getTime() < today.getTime()
|
||||
}
|
||||
|
||||
/** Format court français JJ-MM-AAAA (chaîne vide si date absente / invalide). */
|
||||
function formatDateFr(value: string | null | undefined): string {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return ''
|
||||
}
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
return `${day}-${month}-${date.getFullYear()}`
|
||||
}
|
||||
|
||||
// ── Confirmation d'intégration ───────────────────────────────────────────────
|
||||
const confirmOpen = ref(false)
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { formatDateFr } from '~/shared/utils/date'
|
||||
|
||||
interface FilterOption {
|
||||
value: string
|
||||
@@ -235,20 +236,6 @@ function isValidityExpired(item: Record<string, unknown>): boolean {
|
||||
return date.getTime() < today.getTime()
|
||||
}
|
||||
|
||||
/** Format court francais JJ-MM-AAAA (spec M4). Chaine vide si date absente / invalide. */
|
||||
function formatDateFr(value: string | null | undefined): string {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return ''
|
||||
}
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
return `${day}-${month}-${date.getFullYear()}`
|
||||
}
|
||||
|
||||
/** Clic sur une ligne → ecran Consultation (route a plat /carriers/{id}). */
|
||||
function onRowClick(item: Record<string, unknown>): void {
|
||||
router.push(`/carriers/${item.id}`)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { todayIso } from '../date'
|
||||
import { formatDateFr, todayIso } from '../date'
|
||||
|
||||
describe('todayIso', () => {
|
||||
it('formate la date locale en YYYY-MM-DD (zero-pad mois/jour)', () => {
|
||||
@@ -17,3 +17,28 @@ describe('todayIso', () => {
|
||||
expect(todayIso(new Date(2026, 11, 31, 12, 0))).toBe('2026-12-31')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatDateFr', () => {
|
||||
it('formate un datetime ISO avec offset en JJ-MM-AAAA', () => {
|
||||
expect(formatDateFr('2026-06-17T09:12:00+02:00')).toBe('17-06-2026')
|
||||
})
|
||||
|
||||
it('lit la date dans la CHAINE, sans decalage de fuseau (deterministe)', () => {
|
||||
// Minuit UTC : une lecture via new Date().getDate() basculerait au 4 dans un
|
||||
// fuseau negatif (ex. America). On lit la chaine -> reste le 05 partout.
|
||||
expect(formatDateFr('2026-01-05T00:00:00Z')).toBe('05-01-2026')
|
||||
// Idem juste avant minuit avec offset +02:00 : la date affichee est celle
|
||||
// portee par la chaine (17), pas le 16 d'un runtime UTC.
|
||||
expect(formatDateFr('2026-06-17T00:30:00+02:00')).toBe('17-06-2026')
|
||||
})
|
||||
|
||||
it('accepte une date nue YYYY-MM-DD', () => {
|
||||
expect(formatDateFr('2026-03-07')).toBe('07-03-2026')
|
||||
})
|
||||
|
||||
it('renvoie une chaine vide pour une valeur absente ou non ISO', () => {
|
||||
expect(formatDateFr(null)).toBe('')
|
||||
expect(formatDateFr(undefined)).toBe('')
|
||||
expect(formatDateFr('pas-une-date')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,3 +30,22 @@ export function nowIsoDateTime(now: Date = new Date()): string {
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0')
|
||||
return `${todayIso(now)}T${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Date courte française `JJ-MM-AAAA` à partir d'une valeur ISO (`YYYY-MM-DD` ou
|
||||
* datetime `YYYY-MM-DDTHH:mm:ss±HH:mm`). Chaîne vide si absente ou non ISO.
|
||||
*
|
||||
* On lit les composantes DIRECTEMENT dans la chaîne (10 premiers caractères) au
|
||||
* lieu de `new Date(value).getDate()` : un datetime porteur d'un offset (ex.
|
||||
* `…T00:30:00+02:00`, ou `…Z`) basculerait d'un jour selon le fuseau du
|
||||
* navigateur / du runner CI. Rendu ainsi déterministe et cohérent avec l'écran
|
||||
* d'édition (slice de la chaîne brute) et l'export serveur (`format('d/m/Y')`).
|
||||
*/
|
||||
export function formatDateFr(value: string | null | undefined): string {
|
||||
const match = value ? /^(\d{4})-(\d{2})-(\d{2})/.exec(value) : null
|
||||
if (!match) {
|
||||
return ''
|
||||
}
|
||||
const [, year, month, day] = match
|
||||
return `${day}-${month}-${year}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user