codevdesign 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,197 +1,197 @@
1
- <template>
2
- <div>
3
- <v-autocomplete
4
- v-model="selection"
5
- v-model:search="search"
6
- :items="items"
7
- :loading="chargementInterne"
8
- density="compact"
9
- :hide-no-data="!doitAfficherAucunResultat"
10
- :no-data-text="$t('csqc.csqcRechercheUtilisateur.aucunResultat')"
11
- :item-title="formatterUtilisateur"
12
- item-value="id"
13
- :placeholder="$t('csqc.csqcRechercheUtilisateur.placeholderRechercheUtilisateur')"
14
- return-object
15
- autofocus
16
- auto-select-first
17
- variant="outlined"
18
- />
19
- </div>
20
- </template>
21
-
22
- <script setup lang="ts">
23
- import { ref, watch, computed } from 'vue'
24
- import { useI18n } from 'vue-i18n'
25
- import appAxios from '../outils/appAxios'
26
- import type { EmployeMinsLsCodev } from '../modeles/employeMinsLsCodev'
27
-
28
- const props = defineProps<{
29
- matriculeDefaut?: string | null
30
- chargement?: boolean
31
- urlBase: string
32
- }>()
33
-
34
- const emit = defineEmits<{
35
- (e: 'selection', value: string | null): void
36
- (e: 'selectionPlus', utilisateur: EmployeMinsLsCodev | null): void
37
- (e: 'erreur', message: string): void
38
- }>()
39
-
40
- const { t } = useI18n({ useScope: 'global' })
41
-
42
- /** État interne */
43
- const selection = ref<EmployeMinsLsCodev | null>(null)
44
- const utilisateurs = ref<EmployeMinsLsCodev[]>([])
45
- const search = ref<string>('')
46
- const erreur = ref<string | null>(null)
47
-
48
- /** Charge interne : on combine le chargement externe (prop) et l’interne */
49
- const chargementInterne = ref(false)
50
- const chargementEffectif = computed(() => props.chargement === true || chargementInterne.value)
51
-
52
- /** Items sécurisés pour l’autocomplete */
53
- const items = computed<EmployeMinsLsCodev[]>(() => (Array.isArray(utilisateurs.value) ? utilisateurs.value : []))
54
-
55
- /** Cache clé→liste (toujours des arrays valides) */
56
- const cache = ref<Record<string, EmployeMinsLsCodev[]>>({})
57
-
58
- /** Détection d’un terme valide (limite les calls) */
59
- const estTermeValide = (terme?: string): boolean => !!terme && terme.trim().length >= 4 && !terme.includes('(')
60
-
61
- /** Affichage “aucun résultat” contrôlé */
62
- const doitAfficherAucunResultat = computed(
63
- () => estTermeValide(search.value) && !chargementEffectif.value && items.value.length === 0,
64
- )
65
-
66
- /** Formattage d’un employé en ligne lisible */
67
- const formatterUtilisateur = (item: EmployeMinsLsCodev): string => {
68
- const lieu = [item.numeroLieuPrincipal, item.nomLieuPrincipal].filter(Boolean).join('-')
69
- const corps = [item.corpsEmploiPrincipal, item.corpsEmploiPrincipalDescription].filter(Boolean).join('-')
70
- return `${item.prenom} ${item.nom} (${item.matricule}), ${item.courrielProfessionnel}${lieu ? `, ${lieu}` : ''}${corps ? `, ${corps}` : ''}`
71
- }
72
-
73
- /** Type guard: on s’assure qu’on a bien un tableau d’employés */
74
- function isEmployeArray(x: unknown): x is EmployeMinsLsCodev[] {
75
- return (
76
- Array.isArray(x) && x.every(u => u && typeof u === 'object' && 'prenom' in u && 'nom' in u && 'matricule' in u)
77
- )
78
- }
79
-
80
- /** Réinitialise proprement la liste + émet une erreur optionnelle */
81
- function safeClearUsers(msg?: string) {
82
- utilisateurs.value = []
83
- if (msg) {
84
- erreur.value = msg
85
- emit('erreur', msg)
86
- }
87
- }
88
-
89
- /** Anti-race condition: on ne retient le résultat que si l’ID correspond */
90
- let lastQueryId = 0
91
-
92
- async function rechercherUtilisateurs(terme?: string) {
93
- if (!estTermeValide(terme)) {
94
- utilisateurs.value = []
95
- return
96
- }
97
-
98
- const queryId = ++lastQueryId
99
- const termeLower = terme!.toLowerCase()
100
-
101
- // Cache prefix-match: on prend la plus longue clé qui matche le début
102
- const cachedKey = Object.keys(cache.value)
103
- .filter(k => termeLower.startsWith(k))
104
- .sort((a, b) => b.length - a.length)[0]
105
-
106
- if (cachedKey) {
107
- const cached = cache.value[cachedKey]
108
- if (isEmployeArray(cached)) {
109
- // Filtre côté client pour les affiner
110
- const tl = termeLower
111
- utilisateurs.value = cached.filter(user => formatterUtilisateur(user).toLowerCase().includes(tl))
112
- return
113
- } else {
114
- // Cache corrompu (théorique)
115
- delete cache.value[cachedKey]
116
- }
117
- }
118
-
119
- try {
120
- chargementInterne.value = true
121
- const url = `${props.urlBase}/api/ComposantUI/Utilisateurs/${encodeURIComponent(terme as string)}`
122
- const reponse = await appAxios.getAxios().get<unknown>(url)
123
- const data = (reponse as any)?.data ?? reponse
124
-
125
- if (queryId !== lastQueryId) {
126
- // Une requête plus récente est arrivée, on ignore
127
- return
128
- }
129
-
130
- if (!isEmployeArray(data)) {
131
- // Probable HTML/redirect login/erreur
132
- safeClearUsers(t('csqc.csqcRechercheUtilisateur.erreur'))
133
- return
134
- }
135
-
136
- utilisateurs.value = data
137
- cache.value[termeLower] = data
138
- } catch (e: unknown) {
139
- let message = t('csqc.csqcRechercheUtilisateur.erreur')
140
- if (e instanceof Error) message = e.message
141
- else if (typeof e === 'string') message = e
142
- safeClearUsers(message)
143
- } finally {
144
- chargementInterne.value = false
145
- }
146
- }
147
-
148
- /** Debounce recherche */
149
- let timer: number | undefined
150
- watch(
151
- search,
152
- terme => {
153
- if (chargementEffectif.value) return
154
- window.clearTimeout(timer)
155
- timer = window.setTimeout(() => rechercherUtilisateurs(terme), 250)
156
- },
157
- { flush: 'post' },
158
- )
159
-
160
- /** Émettre la sélection (email & objet complet) */
161
- watch(selection, utilisateur => {
162
- emit('selection', utilisateur?.courrielProfessionnel ?? null)
163
- emit('selectionPlus', utilisateur ?? null)
164
- })
165
-
166
- /** Préchargement par matricule par défaut */
167
- watch(
168
- () => props.matriculeDefaut,
169
- async (nv, ov) => {
170
- // Si vide / nul → on nettoie
171
- if (!nv || nv.trim() === '') {
172
- selection.value = null
173
- search.value = ''
174
- utilisateurs.value = []
175
- return
176
- }
177
-
178
- // Si la même valeur est déjà sélectionnée → rien à faire
179
- if (selection.value?.matricule === nv) return
180
-
181
- // Recherche par matricule par défaut
182
- await rechercherUtilisateurs(nv)
183
- if (utilisateurs.value.length > 0) {
184
- const utilisateur = utilisateurs.value.find(u => u.matricule === nv) ?? null
185
- if (utilisateur) {
186
- search.value = formatterUtilisateur(utilisateur)
187
- selection.value = utilisateur
188
- } else {
189
- // Pas trouvé → reset “propre”
190
- selection.value = null
191
- search.value = nv // ou ''
192
- }
193
- }
194
- },
195
- { immediate: true },
196
- )
197
- </script>
1
+ <template>
2
+ <div>
3
+ <v-autocomplete
4
+ v-model="selection"
5
+ v-model:search="search"
6
+ :items="items"
7
+ :loading="chargementInterne"
8
+ density="compact"
9
+ :hide-no-data="!doitAfficherAucunResultat"
10
+ :no-data-text="$t('csqc.csqcRechercheUtilisateur.aucunResultat')"
11
+ :item-title="formatterUtilisateur"
12
+ item-value="id"
13
+ :placeholder="$t('csqc.csqcRechercheUtilisateur.placeholderRechercheUtilisateur')"
14
+ return-object
15
+ autofocus
16
+ auto-select-first
17
+ variant="outlined"
18
+ />
19
+ </div>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import { ref, watch, computed } from 'vue'
24
+ import { useI18n } from 'vue-i18n'
25
+ import appAxios from '../../outils/appAxios'
26
+ import type { EmployeMinsLsCodev } from '../modeles/employeMinsLsCodev'
27
+
28
+ const props = defineProps<{
29
+ matriculeDefaut?: string | null
30
+ chargement?: boolean
31
+ urlBase: string
32
+ }>()
33
+
34
+ const emit = defineEmits<{
35
+ (e: 'selection', value: string | null): void
36
+ (e: 'selectionPlus', utilisateur: EmployeMinsLsCodev | null): void
37
+ (e: 'erreur', message: string): void
38
+ }>()
39
+
40
+ const { t } = useI18n({ useScope: 'global' })
41
+
42
+ /** État interne */
43
+ const selection = ref<EmployeMinsLsCodev | null>(null)
44
+ const utilisateurs = ref<EmployeMinsLsCodev[]>([])
45
+ const search = ref<string>('')
46
+ const erreur = ref<string | null>(null)
47
+
48
+ /** Charge interne : on combine le chargement externe (prop) et l’interne */
49
+ const chargementInterne = ref(false)
50
+ const chargementEffectif = computed(() => props.chargement === true || chargementInterne.value)
51
+
52
+ /** Items sécurisés pour l’autocomplete */
53
+ const items = computed<EmployeMinsLsCodev[]>(() => (Array.isArray(utilisateurs.value) ? utilisateurs.value : []))
54
+
55
+ /** Cache clé→liste (toujours des arrays valides) */
56
+ const cache = ref<Record<string, EmployeMinsLsCodev[]>>({})
57
+
58
+ /** Détection d’un terme valide (limite les calls) */
59
+ const estTermeValide = (terme?: string): boolean => !!terme && terme.trim().length >= 4 && !terme.includes('(')
60
+
61
+ /** Affichage “aucun résultat” contrôlé */
62
+ const doitAfficherAucunResultat = computed(
63
+ () => estTermeValide(search.value) && !chargementEffectif.value && items.value.length === 0,
64
+ )
65
+
66
+ /** Formattage d’un employé en ligne lisible */
67
+ const formatterUtilisateur = (item: EmployeMinsLsCodev): string => {
68
+ const lieu = [item.numeroLieuPrincipal, item.nomLieuPrincipal].filter(Boolean).join('-')
69
+ const corps = [item.corpsEmploiPrincipal, item.corpsEmploiPrincipalDescription].filter(Boolean).join('-')
70
+ return `${item.prenom} ${item.nom} (${item.matricule}), ${item.courrielProfessionnel}${lieu ? `, ${lieu}` : ''}${corps ? `, ${corps}` : ''}`
71
+ }
72
+
73
+ /** Type guard: on s’assure qu’on a bien un tableau d’employés */
74
+ function isEmployeArray(x: unknown): x is EmployeMinsLsCodev[] {
75
+ return (
76
+ Array.isArray(x) && x.every(u => u && typeof u === 'object' && 'prenom' in u && 'nom' in u && 'matricule' in u)
77
+ )
78
+ }
79
+
80
+ /** Réinitialise proprement la liste + émet une erreur optionnelle */
81
+ function safeClearUsers(msg?: string) {
82
+ utilisateurs.value = []
83
+ if (msg) {
84
+ erreur.value = msg
85
+ emit('erreur', msg)
86
+ }
87
+ }
88
+
89
+ /** Anti-race condition: on ne retient le résultat que si l’ID correspond */
90
+ let lastQueryId = 0
91
+
92
+ async function rechercherUtilisateurs(terme?: string) {
93
+ if (!estTermeValide(terme)) {
94
+ utilisateurs.value = []
95
+ return
96
+ }
97
+
98
+ const queryId = ++lastQueryId
99
+ const termeLower = terme!.toLowerCase()
100
+
101
+ // Cache prefix-match: on prend la plus longue clé qui matche le début
102
+ const cachedKey = Object.keys(cache.value)
103
+ .filter(k => termeLower.startsWith(k))
104
+ .sort((a, b) => b.length - a.length)[0]
105
+
106
+ if (cachedKey) {
107
+ const cached = cache.value[cachedKey]
108
+ if (isEmployeArray(cached)) {
109
+ // Filtre côté client pour les affiner
110
+ const tl = termeLower
111
+ utilisateurs.value = cached.filter(user => formatterUtilisateur(user).toLowerCase().includes(tl))
112
+ return
113
+ } else {
114
+ // Cache corrompu (théorique)
115
+ delete cache.value[cachedKey]
116
+ }
117
+ }
118
+
119
+ try {
120
+ chargementInterne.value = true
121
+ const url = `${props.urlBase}/api/ComposantUI/Utilisateurs/${encodeURIComponent(terme as string)}`
122
+ const reponse = await appAxios.getAxios().get<unknown>(url)
123
+ const data = (reponse as any)?.data ?? reponse
124
+
125
+ if (queryId !== lastQueryId) {
126
+ // Une requête plus récente est arrivée, on ignore
127
+ return
128
+ }
129
+
130
+ if (!isEmployeArray(data)) {
131
+ // Probable HTML/redirect login/erreur
132
+ safeClearUsers(t('csqc.csqcRechercheUtilisateur.erreur'))
133
+ return
134
+ }
135
+
136
+ utilisateurs.value = data
137
+ cache.value[termeLower] = data
138
+ } catch (e: unknown) {
139
+ let message = t('csqc.csqcRechercheUtilisateur.erreur')
140
+ if (e instanceof Error) message = e.message
141
+ else if (typeof e === 'string') message = e
142
+ safeClearUsers(message)
143
+ } finally {
144
+ chargementInterne.value = false
145
+ }
146
+ }
147
+
148
+ /** Debounce recherche */
149
+ let timer: number | undefined
150
+ watch(
151
+ search,
152
+ terme => {
153
+ if (chargementEffectif.value) return
154
+ window.clearTimeout(timer)
155
+ timer = window.setTimeout(() => rechercherUtilisateurs(terme), 250)
156
+ },
157
+ { flush: 'post' },
158
+ )
159
+
160
+ /** Émettre la sélection (email & objet complet) */
161
+ watch(selection, utilisateur => {
162
+ emit('selection', utilisateur?.courrielProfessionnel ?? null)
163
+ emit('selectionPlus', utilisateur ?? null)
164
+ })
165
+
166
+ /** Préchargement par matricule par défaut */
167
+ watch(
168
+ () => props.matriculeDefaut,
169
+ async (nv, ov) => {
170
+ // Si vide / nul → on nettoie
171
+ if (!nv || nv.trim() === '') {
172
+ selection.value = null
173
+ search.value = ''
174
+ utilisateurs.value = []
175
+ return
176
+ }
177
+
178
+ // Si la même valeur est déjà sélectionnée → rien à faire
179
+ if (selection.value?.matricule === nv) return
180
+
181
+ // Recherche par matricule par défaut
182
+ await rechercherUtilisateurs(nv)
183
+ if (utilisateurs.value.length > 0) {
184
+ const utilisateur = utilisateurs.value.find(u => u.matricule === nv) ?? null
185
+ if (utilisateur) {
186
+ search.value = formatterUtilisateur(utilisateur)
187
+ selection.value = utilisateur
188
+ } else {
189
+ // Pas trouvé → reset “propre”
190
+ selection.value = null
191
+ search.value = nv // ou ''
192
+ }
193
+ }
194
+ },
195
+ { immediate: true },
196
+ )
197
+ </script>
package/index.ts CHANGED
@@ -1,74 +1,73 @@
1
- import csqcAlerteErreur from './composants/csqcAlerteErreur.vue'
2
- import csqcDialogue from './composants/csqcDialogue.vue'
3
- import csqcOptionSwitch from './composants/csqcOptionSwitch.vue'
4
- import csqcRecherche from './composants/csqcRecherche.vue'
5
- import csqcSnackbar from './composants/csqcSnackbar.vue'
6
- import csqcTiroir from './composants/csqcTiroir.vue'
7
- import pivEntete from './composants/gabarit/pivEntete.vue'
8
- import pivFooter from './composants/gabarit/pivPiedPage.vue'
9
- import csqcMenu from './composants/gabarit/csqcMenu.vue'
10
- import csqcConfirmation from './composants/csqcConfirmation.vue'
11
- import csqcSaisie from './composants/csqcModaleSaisie.vue'
12
- import csqcDate from './composants/csqcDate.vue'
13
- import csqcTable from './composants/csqcTable/csqcTable.vue'
14
- import csqcCodeBudgetaire from './composants/csqcCodeBudgetaireGenerique.vue'
15
- import csqcChaise from './composants/csqcChaise/chaiseConteneur.vue'
16
- import csqcAide from './composants/csqcAide.vue'
17
- import csqcEntete from './composants/csqcEntete.vue'
18
- import csqcTexteBilingue from './composants/csqcTexteBilingue.vue'
19
- // @ts-expect-error TS7016
20
- import csqcEditeurTexteRiche from './composants/csqcEditeurTexteRiche.vue'
21
- import csqcImportCSV from './composants/csqcImportCSV.vue'
22
- import csqcRechercheUtilisateur from './composants/csqcRechercheUtilisateur.vue'
23
- import validateurs from './composants/validateurs'
24
-
25
- // modèles
26
- import NotificationGabaritDefaut from './modeles/notificationGabaritDefaut'
27
- import modeleSnackbar from './modeles/composants/snackbar'
28
- import modeleDatatableColonne from './modeles/composants/datatableColonne'
29
- import apiReponse from './modeles/apiReponse'
30
- import data from './modeles/data'
31
- import response from './modeles/response'
32
-
33
- // i18n
34
- import csqcEn from './locales/en.json'
35
- import csqcFr from './locales/fr.json'
36
-
37
- // outils
38
- import csqcRafraichisseurToken from './outils/rafraichisseurToken'
39
- import csqcAppAxios from './outils/appAxios'
40
-
41
- export {
42
- csqcFr,
43
- csqcEn,
44
- csqcAlerteErreur,
45
- csqcDialogue,
46
- csqcConfirmation,
47
- csqcSaisie,
48
- csqcDate,
49
- csqcOptionSwitch,
50
- csqcRecherche,
51
- csqcSnackbar,
52
- csqcTable,
53
- csqcTiroir,
54
- csqcMenu,
55
- csqcCodeBudgetaire,
56
- csqcChaise,
57
- pivFooter,
58
- pivEntete,
59
- csqcAide,
60
- csqcEntete,
61
- csqcTexteBilingue,
62
- csqcEditeurTexteRiche,
63
- validateurs,
64
- csqcImportCSV,
65
- csqcRechercheUtilisateur,
66
- modeleSnackbar,
67
- modeleDatatableColonne as colonne,
68
- apiReponse,
69
- data,
70
- response,
71
- NotificationGabaritDefaut,
72
- csqcAppAxios,
73
- csqcRafraichisseurToken,
74
- }
1
+ import csqcAlerteErreur from './composants/csqcAlerteErreur.vue'
2
+ import csqcDialogue from './composants/csqcDialogue.vue'
3
+ import csqcOptionSwitch from './composants/csqcOptionSwitch.vue'
4
+ import csqcRecherche from './composants/csqcRecherche.vue'
5
+ import csqcSnackbar from './composants/csqcSnackbar.vue'
6
+ import csqcTiroir from './composants/csqcTiroir.vue'
7
+ import pivEntete from './composants/gabarit/pivEntete.vue'
8
+ import pivFooter from './composants/gabarit/pivPiedPage.vue'
9
+ import csqcMenu from './composants/gabarit/csqcMenu.vue'
10
+ import csqcConfirmation from './composants/csqcConfirmation.vue'
11
+ import csqcSaisie from './composants/csqcModaleSaisie.vue'
12
+ import csqcDate from './composants/csqcDate.vue'
13
+ import csqcTable from './composants/csqcTable/csqcTable.vue'
14
+ import csqcCodeBudgetaire from './composants/csqcCodeBudgetaireGenerique.vue'
15
+ import csqcChaise from './composants/csqcChaise/chaiseConteneur.vue'
16
+ import csqcAide from './composants/csqcAide.vue'
17
+ import csqcEntete from './composants/csqcEntete.vue'
18
+ import csqcTexteBilingue from './composants/csqcTexteBilingue.vue'
19
+ // @ts-expect-error TS7016
20
+ import csqcEditeurTexteRiche from './composants/csqcEditeurTexteRiche.vue'
21
+ import csqcImportCSV from './composants/csqcImportCSV.vue'
22
+ import csqcRechercheUtilisateur from './composants/csqcRechercheUtilisateur.vue'
23
+ import validateurs from './composants/validateurs'
24
+
25
+ // modèles
26
+ import NotificationGabaritDefaut from './modeles/notificationGabaritDefaut'
27
+ import modeleSnackbar from './modeles/composants/snackbar'
28
+ import modeleDatatableColonne from './modeles/composants/datatableColonne'
29
+ import apiReponse from './modeles/apiReponse'
30
+ import data from './modeles/data'
31
+ import response from './modeles/response'
32
+
33
+ // outils
34
+ import csqcRafraichisseurToken from './outils/rafraichisseurToken'
35
+
36
+ // i18n
37
+ import csqcEn from './locales/en.json'
38
+ import csqcFr from './locales/fr.json'
39
+
40
+
41
+ export {
42
+ csqcFr,
43
+ csqcEn,
44
+ csqcAlerteErreur,
45
+ csqcDialogue,
46
+ csqcConfirmation,
47
+ csqcSaisie,
48
+ csqcDate,
49
+ csqcOptionSwitch,
50
+ csqcRecherche,
51
+ csqcSnackbar,
52
+ csqcTable,
53
+ csqcTiroir,
54
+ csqcMenu,
55
+ csqcCodeBudgetaire,
56
+ csqcChaise,
57
+ pivFooter,
58
+ pivEntete,
59
+ csqcAide,
60
+ csqcEntete,
61
+ csqcTexteBilingue,
62
+ csqcEditeurTexteRiche,
63
+ validateurs,
64
+ csqcImportCSV,
65
+ csqcRechercheUtilisateur,
66
+ csqcRafraichisseurToken,
67
+ modeleSnackbar,
68
+ modeleDatatableColonne as colonne,
69
+ apiReponse,
70
+ data,
71
+ response,
72
+ NotificationGabaritDefaut,
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",
@@ -1,116 +0,0 @@
1
- import axios, { type AxiosInstance, type AxiosError, type AxiosResponse } from 'axios'
2
- import { useAppStore } from '@/store/appStore'
3
- import router from '@/router'
4
-
5
- type ApiReponse<T = unknown> =
6
- // Succès
7
- | {
8
- succes: true
9
- resultat: T
10
- status?: number
11
- message?: string
12
- location?: string
13
- parametres?: unknown
14
- [k: string]: unknown
15
- }
16
- // Échec (le backend peut quand même renvoyer resultat null/absent)
17
- | {
18
- succes: false
19
- resultat?: unknown
20
- status?: number
21
- message?: string
22
- location?: string
23
- parametres?: unknown
24
- [k: string]: unknown
25
- }
26
-
27
- let client: AxiosInstance | null = null
28
- let cachedBaseUrl = '' // pour éviter de régénérer une instance axios si rien n’a changé
29
-
30
- export default {
31
- clearCache: false,
32
-
33
- getAxios(): AxiosInstance {
34
- // Singleton + clearCache
35
- if (client && !this.clearCache) return client
36
-
37
- const appStore = useAppStore()
38
-
39
- const rawUrl = appStore.modeleCharger
40
- ? appStore.appModele!.urlBase
41
- : window.location.origin + import.meta.env.BASE_URL
42
-
43
- const urlBase = rawUrl.endsWith('/') ? rawUrl.slice(0, -1) : rawUrl
44
-
45
- // Si la base URL n'a pas changé et qu'on a déjà un client, on le renvoie
46
- if (client && cachedBaseUrl === urlBase && !this.clearCache) return client
47
- cachedBaseUrl = urlBase
48
-
49
- client = axios.create({
50
- baseURL: `${urlBase}/api`,
51
- withCredentials: true,
52
- timeout: 30_000,
53
- headers: {
54
- Accept: 'application/json',
55
- 'Content-Type': 'application/json',
56
- 'X-Requested-With': 'XMLHttpRequest',
57
- },
58
- // validateStatus: (s) => s >= 200 && s < 300, // défaut axios
59
- })
60
-
61
- client.interceptors.response.use(
62
- (response: AxiosResponse<any>) => {
63
- const data = response.data
64
-
65
- // Détection de la réponse { succes, resultat }
66
- if (data && typeof data === 'object' && 'succes' in data) {
67
- const env = data as ApiReponse
68
- return env.succes === true ? env.resultat : Promise.reject(env)
69
- }
70
-
71
- // Sinon, renvoyer data si présent, sinon la réponse complète
72
- return data ?? response
73
- },
74
-
75
- (error: AxiosError<any>) => {
76
- const status = error.response?.status
77
- const payload = error.response?.data?.resultat ?? { message: error.message }
78
-
79
- // 403 / 404
80
- if (status === 403 || status === 404) {
81
- try {
82
- appStore.lancerErreur(payload)
83
- if (router.currentRoute.value.name !== '403') {
84
- router.push({ name: '403' })
85
- }
86
- } catch {
87
- // no-op
88
- }
89
- return Promise.reject(payload)
90
- }
91
-
92
- // gérer les autres 4XX ici
93
-
94
- // Log minimal
95
- console.error('HTTP error', {
96
- status,
97
- url: error.config?.url,
98
- payload,
99
- })
100
-
101
- // Remonter l’erreur normalisée dans appstore
102
- try {
103
- if (payload?.resultat) appStore.lancerErreur(payload)
104
- else if (payload) appStore.lancerErreur(payload)
105
- } catch {
106
- // no-op
107
- }
108
- return Promise.reject(payload)
109
- },
110
- )
111
-
112
- // reset le flag si on l’avait utilisé pour forcer la recréation de l'instance
113
- this.clearCache = false
114
- return client
115
- },
116
- }