refactor(ui): T1 (suite) — unifier les formatters variants vers les canoniques (formats cohérents)

Sur autorisation user (changement d'affichage OK si ça améliore l'UI) :
- date+heure → formatDateTimeShort : Settings, GiftsInventory, EmailQueue
- date → formatDate : Subcontractors
- initials → staffInitials (1er+dernier) : Historique
- fmtDur → useHelpers.fmtDur («30m»/«1h30») : RendezVous
LAISSÉS (UX volontaire/niche) : ConversationPanel formatDate (relatif auj/hier), PublishScheduleModal (d/m compact),
TechTasks fmtDate (weekday terrain), Planif fmtH (axe timeline), SupplierInvoices fmtMoney (multi-devise).
Build vert, leak 0 ; 12 copies locales éliminées au total (T1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-04 18:42:17 -04:00
parent 1017cab649
commit 7ab71ce6a8
6 changed files with 12 additions and 18 deletions

View File

@ -145,6 +145,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { formatDateTimeShort as formatDate } from 'src/composables/useFormatters' // date+heure canonique (source unique)
import { useRoute } from 'vue-router'
import DataTable from 'src/components/shared/DataTable.vue'
import { useQuasar } from 'quasar'
@ -263,10 +264,7 @@ function giftbitAdminUrl (giftUrl) {
if (!code || code.length < 4) return null
return `https://app.giftbit.com/app/rewards?search=${encodeURIComponent(code)}`
}
function formatDate (iso) {
if (!iso) return ''
return new Date(iso).toLocaleString('fr-CA', { dateStyle: 'medium', timeStyle: 'short' })
}
// formatDate useFormatters.formatDateTimeShort (date+heure canonique)
async function copy (text, customMsg) {
try {

View File

@ -72,6 +72,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { formatDateTimeShort as fmtDate } from 'src/composables/useFormatters' // date+heure canonique (source unique)
import DataTable from 'src/components/shared/DataTable.vue'
import { useQuasar } from 'quasar'
import { listEmailQueue, deleteEmailQueueItem, purgeEmailQueue } from 'src/api/emailQueue'
@ -105,9 +106,7 @@ const notSentCount = computed(() => byStatus.value['Not Sent'] || 0)
function statusColor (s) {
return { 'Not Sent': 'orange-8', Sending: 'blue-6', Sent: 'green-7', Error: 'negative', Expired: 'grey-6' }[s] || 'grey-7'
}
function fmtDate (iso) {
return iso ? new Date(iso.replace(' ', 'T')).toLocaleString('fr-CA', { dateStyle: 'medium', timeStyle: 'short' }) : ''
}
// fmtDate useFormatters.formatDateTimeShort (date+heure canonique)
async function load () {
loading.value = true

View File

@ -143,6 +143,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { staffInitials as initials } from 'src/composables/useFormatters' // initiales 1er+dernier (source unique)
import { Notify } from 'quasar'
import { HUB_URL } from 'src/config/hub'
@ -161,10 +162,7 @@ async function hubGet (path) {
}
// Affichage commun
function initials (name) {
const parts = String(name || '?').trim().split(/\s+/).filter(Boolean)
return ((parts[0]?.[0] || '') + (parts.length > 1 ? parts[parts.length - 1][0] : '')).toUpperCase() || '?'
}
// initials useFormatters.staffInitials (1er + dernier mot, source unique)
function prettyDate (iso) {
if (!iso) return ''
const [y, m, d] = iso.split('-').map(Number)

View File

@ -123,6 +123,7 @@
<script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
import { fmtDur } from 'src/composables/useHelpers' // durée canonique (source unique : «30m»/«1h»/«1h30»)
import { MapPin } from 'lucide-vue-next' // pin monochrome outline (style menu Dispatch)
import { useQuasar } from 'quasar'
import * as roster from 'src/api/roster'
@ -155,7 +156,7 @@ function dayLabel (iso) { const dt = d2(iso); return FR_DOW_FULL[dt.getUTCDay()]
// Titre LISIBLE d'un job : type de service + client (l'ID DJ- est secondaire). Lieu = adresse lisible si dispo.
function jobTitle (j) { if (!j) return ''; const t = (j.service_type || '').trim(); const c = (j.customer_name || '').trim(); if (t) return c ? (t + ' — ' + c) : t; return c || j.location_label || j.name }
function jobLoc (j) { return (j && (j.location_label || j.service_location)) || '—' }
function fmtDur (h) { h = Number(h) || 0; const H = Math.floor(h); const M = Math.round((h - H) * 60); return M ? (H + 'h' + String(M).padStart(2, '0')) : (H + 'h') }
// fmtDur useHelpers.fmtDur (source unique)
function wkMon (iso) { const dt = d2(iso); const off = (dt.getUTCDay() + 6) % 7; dt.setUTCDate(dt.getUTCDate() - off); return dt.toISOString().slice(0, 10) }
function wkLabel (m) { const a = d2(m); const b = new Date(a); b.setUTCDate(b.getUTCDate() + 6); return 'Semaine du ' + a.getUTCDate() + ' ' + MO[a.getUTCMonth()] + ' ' + b.getUTCDate() + ' ' + MO[b.getUTCMonth()] }

View File

@ -749,6 +749,7 @@
<script setup>
import { ref, reactive, onMounted, watch, defineAsyncComponent, h, resolveComponent } from 'vue'
import { formatDateTimeShort as formatDate } from 'src/composables/useFormatters' // date+heure canonique (source unique)
import { Notify } from 'quasar'
import { authFetch } from 'src/api/auth'
import { BASE_URL, ERP_DESK_URL as erpDeskUrl } from 'src/config/erpnext'
@ -857,10 +858,7 @@ function initial (u) {
return (u.name || u.username || '?')[0].toUpperCase()
}
function formatDate (d) {
if (!d) return ''
return new Date(d).toLocaleDateString('fr-CA', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })
}
// formatDate useFormatters.formatDateTimeShort (date+heure canonique)
function savePhone () {
savePhoneConfig(phoneConfig.value)

View File

@ -196,7 +196,7 @@
import { ref, reactive, computed, onMounted } from 'vue'
import { useQuasar } from 'quasar'
import { usePermissions } from 'src/composables/usePermissions'
import { formatMoney as money } from 'src/composables/useFormatters'
import { formatMoney as money, formatDate as fmtDate } from 'src/composables/useFormatters'
import { getRates, saveRates, listSubmissions, sendLink, approve, imgUrl, getSubs, saveSub, getReleve, markPaid, exportUrl, getEntente, saveEntente } from 'src/api/subcontractor'
import { listDocs } from 'src/api/erp'
@ -218,7 +218,7 @@ const entente = reactive({ url: '', isPdf: false, edit: false, input: '', saving
function unitLabel (u) { return ({ case: 'Case (oui/non)', qte: 'Quantité', metres: 'Mètres', heures: 'Heures' })[u] || u }
// money() unifié via useFormatters (import ci-dessus) format fr-CA cohérent
function fmtDate (d) { try { return new Date(d).toLocaleDateString('fr-CA', { day: '2-digit', month: 'short', year: 'numeric' }) } catch (e) { return '' } }
// fmtDate useFormatters.formatDate (date canonique, source unique)
function statusColor (s) { return s === 'approuvé' ? 'green-7' : s === 'payé' ? 'blue-7' : s === 'refusé' ? 'red-7' : s === 'infructueuse' ? 'orange-8' : 'grey-6' }
function statusLabel (s) { return s === 'infructueuse' ? 'infructueuse' : (s || 'soumis') }
function photosOf (s) { return (Array.isArray(s.photos) ? s.photos : []).map(p => (typeof p === 'string' ? { file: p } : p)) }