codevdesign 1.0.25 → 1.0.27

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,363 +1,367 @@
1
- <template>
2
- <div class="pa-2 mt-2 mb-1 mr-1">
3
- <!-- Affiche la carte récap si activée et qu'il y a des unités -->
4
- <div v-if="activerDivPreferences && unites && unites.length > 0">
5
- <v-card
6
- width="375"
7
- variant="outlined"
8
- >
9
- <v-card-text class="pt-0 mt-0">
10
- <v-progress-linear
11
- v-if="chargementEnCours"
12
- indeterminate
13
- />
14
-
15
- <div
16
- v-else
17
- class="pt-2"
18
- >
19
- <div class="text-overline text-h6 mb-2">{{ texteTitre }}</div>
20
-
21
- <div>
22
- <v-list-item
23
- v-for="uniteId in Object.keys(dictChaisesReleve)"
24
- :key="uniteId"
25
- class="mb-1"
26
- >
27
- <ChaisePreferenceItem
28
- :uniteId="Number(uniteId)"
29
- :preferences="preferences"
30
- :dictChaisesReleve="dictChaisesReleve"
31
- :unites="unites"
32
- />
33
- </v-list-item>
34
- </div>
35
- </div>
36
- </v-card-text>
37
-
38
- <v-card-actions>
39
- <div class="flex-grow-1" />
40
- <v-btn
41
- v-if="!chargementEnCours"
42
- rounded
43
- variant="outlined"
44
- size="small"
45
- @click.stop="modifier"
46
- >
47
- {{ texteBoutonModifier }}
48
- </v-btn>
49
- </v-card-actions>
50
- </v-card>
51
- </div>
52
-
53
- <csqcDialogue
54
- ref="modale"
55
- :operation-en-cours="chargementEnCours"
56
- :btn-ok-desactiver="chargementEnCours"
57
- :titre="texteTitre"
58
- :btn-annuler-texte="texteBoutonAnnuler"
59
- @annuler="annuler"
60
- @ok="ok"
61
- ><template #content>
62
- <div class="pt-2"></div>
63
- <hr class="pt-0 mt-0 pl-0" />
64
- <div class="pt-4">
65
- <i>{{ info }}</i>
66
-
67
- <v-progress-linear
68
- v-if="chargementEnCours"
69
- indeterminate
70
- />
71
-
72
- <div
73
- v-else
74
- class="pt-4"
75
- >
76
- <v-row v-if="!toutesUnitesPreferencesSelectionnees">
77
- <v-col cols="12">
78
- <v-alert
79
- v-model="afficherErreur"
80
- type="error"
81
- variant="tonal"
82
- dismissible
83
- >
84
- {{ texteMessageErreur }}
85
- </v-alert>
86
- </v-col>
87
- </v-row>
88
-
89
- <v-row>
90
- <v-col
91
- v-for="uniteId in Object.keys(dictChaisesReleve)"
92
- :key="uniteId"
93
- cols="12"
94
- sm="6"
95
- md="4"
96
- >
97
- <label>{{ getUnite(uniteId)?.nom ?? '' }}</label>
98
- <br />
99
- <v-radio-group v-model="selection[uniteId]">
100
- <v-radio
101
- v-for="chaise in dictChaisesReleve[uniteId]"
102
- :key="chaise.id"
103
- :label="chaise.nom"
104
- :value="chaise.id"
105
- />
106
- </v-radio-group>
107
- </v-col>
108
- </v-row>
109
- </div>
110
- </div>
111
- </template>
112
- </csqcDialogue>
113
- </div>
114
- </template>
115
-
116
- <script setup lang="ts">
117
- import { ref, computed, onMounted, nextTick, watch, toRefs } from 'vue'
118
- import { useDisplay } from 'vuetify'
119
- import ChaisePreferenceItem from './chaiseItem.vue'
120
- import csqcDialogue from '../csqcDialogue.vue'
121
- import axios from '../../outils/appAxios'
122
- import type { Unite } from '@/codev/modeles/unite'
123
- import { useI18n } from 'vue-i18n'
124
- const modale = ref<InstanceType<typeof csqcDialogue> | null>(null)
125
-
126
- interface Chaise {
127
- id: number
128
- nom: string
129
- }
130
- interface Preference {
131
- uniteId: number
132
- chaiseId: number
133
- }
134
-
135
- /** Props */
136
- const props = defineProps<{
137
- activerDivPreferences?: boolean
138
- typeIntervenant: number
139
- demandeId: number
140
- formulaireId: number
141
- urlBase: string
142
- texteTitre?: string
143
- texteInfo?: string
144
- largeurModale?: number
145
- }>()
146
- const { typeIntervenant, demandeId, formulaireId, urlBase } = toRefs(props)
147
- /** Emits */
148
- const emit = defineEmits<{
149
- (e: 'annuler'): void
150
- (e: 'confirmer'): void
151
- }>()
152
-
153
- const { t } = useI18n({ useScope: 'global' })
154
- const { xs } = useDisplay()
155
- const isXs = computed(() => xs.value)
156
-
157
- /** State */
158
- const visible = ref(false)
159
- const chargementEnCours = ref(false)
160
- const afficherErreur = ref(false)
161
- const unites = ref<Unite[]>([])
162
- const preferences = ref<Preference[]>([])
163
- const dictChaisesReleve = ref<Record<string, Chaise[]>>({})
164
- const selection = ref<Record<string, number>>({})
165
- const modeModifier = ref(false)
166
-
167
- /** Textes */
168
- const texteMessageErreur = computed(() => t('csqc.csqcChaise.erreur'))
169
- const texteBoutonAnnuler = computed(() => t('csqc.bouton.annuler'))
170
- const texteBoutonModifier = computed(() => t('csqc.bouton.modifier'))
171
- const texteTitre = computed(() =>
172
- props.texteTitre && props.texteTitre.length > 0 ? props.texteTitre : t('csqc.csqcChaise.titre'),
173
- )
174
- const info = computed(() =>
175
- props.texteInfo && props.texteInfo.length > 0 ? props.texteInfo : t('csqc.csqcChaise.info'),
176
- )
177
-
178
- // Option A — ne recharger que si typeIntervenant change
179
-
180
- const largeur = computed(() => props.largeurModale ?? 1200)
181
- const activerDivPreferences = computed(() => props.activerDivPreferences ?? true)
182
-
183
- function getUnite(uniteId: string) {
184
- return unites.value.find((u: Unite) => String(u.id) === String(uniteId))
185
- }
186
-
187
- /*
188
- function estPrefere(uniteId: string | number, chaiseId: number) {
189
- return preferences.value.some(p => String(p.uniteId) === String(uniteId) && p.chaiseId === chaiseId)
190
- }*/
191
-
192
- /** Sélection par défaut à partir des préférences existantes */
193
- function definitionSelectionDepart() {
194
- for (const uniteId in dictChaisesReleve.value) {
195
- const chaises = dictChaisesReleve.value[uniteId] ?? []
196
- const pref = preferences.value.find(
197
- p => String(p.uniteId) === String(uniteId) && chaises.some(chaise => chaise.id === p.chaiseId),
198
- )
199
- const chaiseIdDefaut = pref?.chaiseId ?? (chaises.length === 1 ? (chaises[0]?.id ?? -1) : -1)
200
- selection.value[uniteId] = chaiseIdDefaut
201
- }
202
- }
203
-
204
- /** Toutes les unités ont-elles une préférence sélectionnée ? */
205
- const toutesUnitesPreferencesSelectionnees = computed(() => {
206
- for (const uniteId in dictChaisesReleve.value) {
207
- const chaises = dictChaisesReleve.value[uniteId] ?? []
208
- const uniteOk = chaises.some(c => preferences.value.some(p => p.chaiseId === c.id))
209
- if (!uniteOk) return false
210
- }
211
- return true
212
- })
213
-
214
- async function chargerUnites() {
215
- const url = `${props.urlBase}/api/ComposantUI/Unites`
216
- const unitesData = (await axios.getAxios().get<Unite[]>(url)) as unknown as Unite[]
217
- unites.value = unitesData ?? []
218
- }
219
-
220
- async function chargementPreferences() {
221
- chargementEnCours.value = true
222
-
223
- // Chaises par unité
224
- {
225
- const data = (await axios
226
- .getAxios()
227
- .get<
228
- Record<string, Chaise[]>
229
- >(`${props.urlBase}/api/ComposantUI/ReleveDe/${props.typeIntervenant}/Demande/${props.demandeId}`)) as unknown as Record<
230
- string,
231
- Chaise[]
232
- >
233
- dictChaisesReleve.value = data ?? {}
234
- }
235
-
236
- // Préférences de l'usager
237
- {
238
- const data = (await axios
239
- .getAxios()
240
- .get<
241
- Preference[]
242
- >(`${props.urlBase}/api/ComposantUI/Preferences/${props.formulaireId}/TypeIntervenant/${props.typeIntervenant}`)) as unknown as Preference[]
243
- preferences.value = (data ?? []).filter(Boolean)
244
- }
245
-
246
- definitionSelectionDepart()
247
-
248
- // Sauvegarde immédiate pour les choix uniques (si demandé par ton flux)
249
- await sauvegarder()
250
-
251
- chargementEnCours.value = false
252
- }
253
-
254
- async function ouvrir() {
255
- if (!activerDivPreferences.value) {
256
- await chargementPreferences()
257
- visible.value = true
258
- return
259
- }
260
-
261
- if (!toutesUnitesPreferencesSelectionnees.value) {
262
- visible.value = true
263
- return
264
- }
265
-
266
- await ok()
267
- }
268
-
269
- function annuler() {
270
- visible.value = false
271
- modeModifier.value = false
272
- emit('annuler')
273
- }
274
-
275
- function modifier() {
276
- modeModifier.value = true
277
- visible.value = true
278
- modale.value?.ouvrir()
279
- }
280
-
281
- async function sauvegarder() {
282
- for (const uniteId in dictChaisesReleve.value) {
283
- const chaiseId = selection.value[uniteId]
284
- if (!chaiseId || chaiseId <= 0) continue
285
-
286
- const chaises = dictChaisesReleve.value[uniteId] ?? []
287
- if (chaises.length === 0) continue
288
-
289
- // Préférence existante pour CETTE unité ?
290
- const prefIndexDeUnite = preferences.value.findIndex(p => chaises.some(c => c.id === p.chaiseId))
291
-
292
- try {
293
- const data = (await axios
294
- .getAxios()
295
- .put<Preference>(
296
- `${props.urlBase}/api/ComposantUI/Preferences/${props.formulaireId}` +
297
- `/Unite/${encodeURIComponent(uniteId)}` +
298
- `/Chaise/${chaiseId}` +
299
- `/TypeIntervenant/${props.typeIntervenant}`,
300
- )) as unknown as Preference
301
- const itemRecu = data as Preference
302
-
303
- if (prefIndexDeUnite >= 0) {
304
- // remplace l'élément à l'index
305
- preferences.value.splice(prefIndexDeUnite, 1, itemRecu)
306
- } else {
307
- // ajoute un nouvel élément
308
- preferences.value.push(itemRecu)
309
- }
310
- } catch (e) {
311
- console.error(e)
312
- }
313
- }
314
- }
315
- async function soumettre() {
316
- modeModifier.value = false
317
- await ouvrir()
318
- }
319
- /** Validation finale */
320
- async function ok() {
321
- await sauvegarder()
322
-
323
- if (toutesUnitesPreferencesSelectionnees.value) {
324
- if (!modeModifier.value) emit('confirmer')
325
- visible.value = false
326
- modeModifier.value = false
327
- } else {
328
- afficherErreur.value = true
329
- await nextTick()
330
- }
331
- modale.value?.fermer()
332
- }
333
-
334
- onMounted(async () => {
335
- chargementEnCours.value = true
336
- await chargerUnites()
337
- await chargementPreferences()
338
- chargementEnCours.value = false
339
- })
340
-
341
- // si le type d'intervenant change
342
- watch(typeIntervenant, async (nv, ov) => {
343
- if (nv === ov) return
344
- await rechargerPourTypeIntervenant()
345
- })
346
-
347
- async function rechargerPourTypeIntervenant() {
348
- try {
349
- chargementEnCours.value = true
350
- preferences.value = []
351
- dictChaisesReleve.value = {}
352
- selection.value = {}
353
- afficherErreur.value = false
354
-
355
- // recharge les données dépendantes
356
- await chargementPreferences()
357
- } finally {
358
- chargementEnCours.value = false
359
- }
360
- }
361
-
362
- defineExpose({ soumettre })
363
- </script>
1
+ <template>
2
+ <div class="pa-2 mt-2 mb-1 mr-1">
3
+ <!-- Affiche la carte récap si activée et qu'il y a des unités -->
4
+ <div v-if="activerDivPreferences && unites && unites.length > 0">
5
+ <v-card
6
+ width="375"
7
+ variant="outlined"
8
+ >
9
+ <v-card-text class="pt-0 mt-0">
10
+ <v-progress-linear
11
+ v-if="chargementEnCours"
12
+ indeterminate
13
+ />
14
+
15
+ <div
16
+ v-else
17
+ class="pt-2"
18
+ >
19
+ <div class="text-overline text-h6 mb-2">{{ texteTitre }}</div>
20
+
21
+ <div>
22
+ <v-list-item
23
+ v-for="uniteId in Object.keys(dictChaisesReleve)"
24
+ :key="uniteId"
25
+ class="mb-1"
26
+ >
27
+ <ChaisePreferenceItem
28
+ :uniteId="Number(uniteId)"
29
+ :preferences="preferences"
30
+ :dictChaisesReleve="dictChaisesReleve"
31
+ :unites="unites"
32
+ />
33
+ </v-list-item>
34
+ </div>
35
+ </div>
36
+ </v-card-text>
37
+
38
+ <v-card-actions>
39
+ <div class="flex-grow-1" />
40
+ <v-btn
41
+ v-if="!chargementEnCours"
42
+ rounded
43
+ variant="outlined"
44
+ size="small"
45
+ @click.stop="modifier"
46
+ >
47
+ {{ texteBoutonModifier }}
48
+ </v-btn>
49
+ </v-card-actions>
50
+ </v-card>
51
+ </div>
52
+
53
+ <csqcDialogue
54
+ ref="modale"
55
+ :operation-en-cours="chargementEnCours"
56
+ :btn-ok-desactiver="chargementEnCours"
57
+ :titre="texteTitre"
58
+ :btn-annuler-texte="texteBoutonAnnuler"
59
+ @annuler="annuler"
60
+ @ok="ok"
61
+ ><template #content>
62
+ <div class="pt-2"></div>
63
+ <hr class="pt-0 mt-0 pl-0" />
64
+ <div class="pt-4">
65
+ <i>{{ info }}</i>
66
+
67
+ <v-progress-linear
68
+ v-if="chargementEnCours"
69
+ indeterminate
70
+ />
71
+
72
+ <div
73
+ v-else
74
+ class="pt-4"
75
+ >
76
+ <v-row v-if="!toutesUnitesPreferencesSelectionnees">
77
+ <v-col cols="12">
78
+ <v-alert
79
+ v-model="afficherErreur"
80
+ type="error"
81
+ variant="tonal"
82
+ dismissible
83
+ >
84
+ {{ texteMessageErreur }}
85
+ </v-alert>
86
+ </v-col>
87
+ </v-row>
88
+
89
+ <v-row>
90
+ <v-col
91
+ v-for="uniteId in Object.keys(dictChaisesReleve)"
92
+ :key="uniteId"
93
+ cols="12"
94
+ sm="6"
95
+ md="4"
96
+ >
97
+ <label>{{ getUnite(uniteId)?.nom ?? '' }}</label>
98
+ <br />
99
+ <v-radio-group v-model="selection[uniteId]">
100
+ <v-radio
101
+ v-for="chaise in dictChaisesReleve[uniteId]"
102
+ :key="chaise.id"
103
+ :label="chaise.nom"
104
+ :value="chaise.id"
105
+ />
106
+ </v-radio-group>
107
+ </v-col>
108
+ </v-row>
109
+ </div>
110
+ </div>
111
+ </template>
112
+ </csqcDialogue>
113
+ </div>
114
+ </template>
115
+
116
+ <script setup lang="ts">
117
+ import { ref, computed, onMounted, nextTick, watch, toRefs } from 'vue'
118
+ import { useDisplay } from 'vuetify'
119
+ import ChaisePreferenceItem from './chaiseItem.vue'
120
+ import csqcDialogue from '../csqcDialogue.vue'
121
+ import axios from '../../outils/appAxios'
122
+ import type { Unite } from '@/codev/modeles/unite'
123
+ import { useI18n } from 'vue-i18n'
124
+ const modale = ref<InstanceType<typeof csqcDialogue> | null>(null)
125
+
126
+ interface Chaise {
127
+ id: number
128
+ nom: string
129
+ }
130
+ interface Preference {
131
+ uniteId: number
132
+ chaiseId: number
133
+ }
134
+
135
+ /** Props */
136
+ const props = defineProps<{
137
+ activerDivPreferences?: boolean
138
+ typeIntervenant: number
139
+ demandeId: number
140
+ formulaireId: number
141
+ urlBase: string
142
+ texteTitre?: string
143
+ texteInfo?: string
144
+ largeurModale?: number
145
+ }>()
146
+ const { typeIntervenant, demandeId, formulaireId, urlBase } = toRefs(props)
147
+ /** Emits */
148
+ const emit = defineEmits<{
149
+ (e: 'annuler'): void
150
+ (e: 'confirmer'): void
151
+ }>()
152
+
153
+ const { t } = useI18n({ useScope: 'global' })
154
+ const { xs } = useDisplay()
155
+ const isXs = computed(() => xs.value)
156
+
157
+ /** State */
158
+ const visible = ref(false)
159
+ const chargementEnCours = ref(false)
160
+ const afficherErreur = ref(false)
161
+ const unites = ref<Unite[]>([])
162
+ const preferences = ref<Preference[]>([])
163
+ const dictChaisesReleve = ref<Record<string, Chaise[]>>({})
164
+ const selection = ref<Record<string, number>>({})
165
+ const modeModifier = ref(false)
166
+
167
+ /** Textes */
168
+ const texteMessageErreur = computed(() => t('csqc.csqcChaise.erreur'))
169
+ const texteBoutonAnnuler = computed(() => t('csqc.bouton.annuler'))
170
+ const texteBoutonModifier = computed(() => t('csqc.bouton.modifier'))
171
+ const texteTitre = computed(() =>
172
+ props.texteTitre && props.texteTitre.length > 0 ? props.texteTitre : t('csqc.csqcChaise.titre'),
173
+ )
174
+ const info = computed(() =>
175
+ props.texteInfo && props.texteInfo.length > 0 ? props.texteInfo : t('csqc.csqcChaise.info'),
176
+ )
177
+
178
+ // Option A — ne recharger que si typeIntervenant change
179
+
180
+ const largeur = computed(() => props.largeurModale ?? 1200)
181
+ const activerDivPreferences = computed(() => props.activerDivPreferences ?? true)
182
+
183
+ function getUnite(uniteId: string) {
184
+ return unites.value.find((u: Unite) => String(u.id) === String(uniteId))
185
+ }
186
+
187
+ /*
188
+ function estPrefere(uniteId: string | number, chaiseId: number) {
189
+ return preferences.value.some(p => String(p.uniteId) === String(uniteId) && p.chaiseId === chaiseId)
190
+ }*/
191
+
192
+ /** Sélection par défaut à partir des préférences existantes */
193
+ function definitionSelectionDepart() {
194
+ for (const uniteId in dictChaisesReleve.value) {
195
+ const chaises = dictChaisesReleve.value[uniteId] ?? []
196
+ const pref = preferences.value.find(
197
+ p => String(p.uniteId) === String(uniteId) && chaises.some(chaise => chaise.id === p.chaiseId),
198
+ )
199
+ const chaiseIdDefaut = pref?.chaiseId ?? (chaises.length === 1 ? (chaises[0]?.id ?? -1) : -1)
200
+ selection.value[uniteId] = chaiseIdDefaut
201
+ }
202
+ }
203
+
204
+ /** Toutes les unités ont-elles une préférence sélectionnée ? */
205
+ const toutesUnitesPreferencesSelectionnees = computed(() => {
206
+ for (const uniteId in dictChaisesReleve.value) {
207
+ const chaises = dictChaisesReleve.value[uniteId] ?? []
208
+ const uniteOk = chaises.some(c => preferences.value.some(p => p.chaiseId === c.id))
209
+ if (!uniteOk) return false
210
+ }
211
+ return true
212
+ })
213
+
214
+ async function charger() {
215
+ await chargerUnites()
216
+ await chargementPreferences()
217
+ }
218
+
219
+ async function chargerUnites() {
220
+ const url = `${props.urlBase}/api/ComposantUI/Unites`
221
+ const unitesData = (await axios.getAxios().get<Unite[]>(url)) as unknown as Unite[]
222
+ unites.value = unitesData ?? []
223
+ }
224
+
225
+ async function chargementPreferences() {
226
+ chargementEnCours.value = true
227
+
228
+ // Chaises par unité
229
+ {
230
+ const data = (await axios
231
+ .getAxios()
232
+ .get<
233
+ Record<string, Chaise[]>
234
+ >(`${props.urlBase}/api/ComposantUI/ReleveDe/${props.typeIntervenant}/Demande/${props.demandeId}`)) as unknown as Record<
235
+ string,
236
+ Chaise[]
237
+ >
238
+ dictChaisesReleve.value = data ?? {}
239
+ }
240
+
241
+ // Préférences de l'usager
242
+ {
243
+ const data = (await axios
244
+ .getAxios()
245
+ .get<
246
+ Preference[]
247
+ >(`${props.urlBase}/api/ComposantUI/Preferences/${props.formulaireId}/TypeIntervenant/${props.typeIntervenant}`)) as unknown as Preference[]
248
+ preferences.value = (data ?? []).filter(Boolean)
249
+ }
250
+
251
+ definitionSelectionDepart()
252
+
253
+ // Sauvegarde immédiate pour les choix uniques (si demandé par ton flux)
254
+ await sauvegarder()
255
+
256
+ chargementEnCours.value = false
257
+ }
258
+
259
+ async function ouvrir() {
260
+ if (!activerDivPreferences.value) {
261
+ await chargementPreferences()
262
+ visible.value = true
263
+ return
264
+ }
265
+
266
+ if (!toutesUnitesPreferencesSelectionnees.value) {
267
+ visible.value = true
268
+ return
269
+ }
270
+
271
+ await ok()
272
+ }
273
+
274
+ function annuler() {
275
+ visible.value = false
276
+ modeModifier.value = false
277
+ emit('annuler')
278
+ }
279
+
280
+ function modifier() {
281
+ modeModifier.value = true
282
+ visible.value = true
283
+ modale.value?.ouvrir()
284
+ }
285
+
286
+ async function sauvegarder() {
287
+ for (const uniteId in dictChaisesReleve.value) {
288
+ const chaiseId = selection.value[uniteId]
289
+ if (!chaiseId || chaiseId <= 0) continue
290
+
291
+ const chaises = dictChaisesReleve.value[uniteId] ?? []
292
+ if (chaises.length === 0) continue
293
+
294
+ // Préférence existante pour CETTE unité ?
295
+ const prefIndexDeUnite = preferences.value.findIndex(p => chaises.some(c => c.id === p.chaiseId))
296
+
297
+ try {
298
+ const data = (await axios
299
+ .getAxios()
300
+ .put<Preference>(
301
+ `${props.urlBase}/api/ComposantUI/Preferences/${props.formulaireId}` +
302
+ `/Unite/${encodeURIComponent(uniteId)}` +
303
+ `/Chaise/${chaiseId}` +
304
+ `/TypeIntervenant/${props.typeIntervenant}`,
305
+ )) as unknown as Preference
306
+ const itemRecu = data as Preference
307
+
308
+ if (prefIndexDeUnite >= 0) {
309
+ // remplace l'élément à l'index
310
+ preferences.value.splice(prefIndexDeUnite, 1, itemRecu)
311
+ } else {
312
+ // ajoute un nouvel élément
313
+ preferences.value.push(itemRecu)
314
+ }
315
+ } catch (e) {
316
+ console.error(e)
317
+ }
318
+ }
319
+ }
320
+ async function soumettre() {
321
+ modeModifier.value = false
322
+ await ouvrir()
323
+ }
324
+ /** Validation finale */
325
+ async function ok() {
326
+ await sauvegarder()
327
+
328
+ if (toutesUnitesPreferencesSelectionnees.value) {
329
+ if (!modeModifier.value) emit('confirmer')
330
+ visible.value = false
331
+ modeModifier.value = false
332
+ } else {
333
+ afficherErreur.value = true
334
+ await nextTick()
335
+ }
336
+ modale.value?.fermer()
337
+ }
338
+
339
+ onMounted(async () => {
340
+ chargementEnCours.value = true
341
+ await charger()
342
+ chargementEnCours.value = false
343
+ })
344
+
345
+ // si le type d'intervenant change
346
+ watch(typeIntervenant, async (nv, ov) => {
347
+ if (nv === ov) return
348
+ await rechargerPourTypeIntervenant()
349
+ })
350
+
351
+ async function rechargerPourTypeIntervenant() {
352
+ try {
353
+ chargementEnCours.value = true
354
+ preferences.value = []
355
+ dictChaisesReleve.value = {}
356
+ selection.value = {}
357
+ afficherErreur.value = false
358
+
359
+ // recharge les données dépendantes
360
+ await chargementPreferences()
361
+ } finally {
362
+ chargementEnCours.value = false
363
+ }
364
+ }
365
+
366
+ defineExpose({ charger, soumettre })
367
+ </script>
@@ -37,296 +37,300 @@
37
37
  </template>
38
38
 
39
39
  <script setup lang="ts">
40
- import { ref, computed, onMounted, nextTick, watch } from 'vue'
41
- import type { VForm } from 'vuetify/components'
42
-
43
- const emit = defineEmits<{
44
- 'update:modelValue': [string | null]
45
- 'update:codeBudgetairesProp': [CodeBudgetaireItem[]]
46
- 'update:valide': [boolean]
47
- }>()
48
- type CodeBudgetaireItem = string | [string, string]
49
-
50
- const props = withDefaults(
51
- defineProps<{
52
- codeBudgetairesProp: CodeBudgetaireItem[]
53
- modelValue: string | null
54
- afficherHint?: boolean
55
- regleMessageErreur: string
56
- format?: string
57
- activerExtension?: boolean
58
- reglesSupp?: ((v: string) => true | string)[]
59
- }>(),
60
- {
61
- afficherHint: false,
62
- format: '999-9-99999-999',
63
- activerExtension: false,
64
- reglesSupp: () => [],
65
- },
66
- )
40
+ import { ref, computed, onMounted, nextTick, watch } from 'vue'
41
+ import type { VForm } from 'vuetify/components'
42
+
43
+ const emit = defineEmits<{
44
+ 'update:modelValue': [string | null]
45
+ 'update:codeBudgetairesProp': [CodeBudgetaireItem[]]
46
+ 'update:valide': [boolean]
47
+ }>()
48
+ type CodeBudgetaireItem = string | [string, string]
49
+
50
+ const props = withDefaults(
51
+ defineProps<{
52
+ codeBudgetairesProp: CodeBudgetaireItem[]
53
+ modelValue: string | null
54
+ afficherHint?: boolean
55
+ regleMessageErreur: string
56
+ format?: string
57
+ activerExtension?: boolean
58
+ reglesSupp?: ((v: string) => true | string)[]
59
+ }>(),
60
+ {
61
+ afficherHint: false,
62
+ format: '999-9-99999-999',
63
+ activerExtension: false,
64
+ reglesSupp: () => [],
65
+ },
66
+ )
67
+
68
+ const formValide = ref(false)
69
+ const form = ref<VForm | null>(null)
70
+ const codeBudgetaire = ref(props.modelValue ?? '')
71
+ const derniereValeurSauvegardee = ref<string | null>(null)
72
+ const format = props.format
73
+ const activerExtension = props.activerExtension
74
+
75
+ onMounted(() => {
76
+ derniereValeurSauvegardee.value = codeBudgetaire.value
77
+ })
67
78
 
68
- const formValide = ref(false)
69
- const form = ref<VForm | null>(null)
70
- const codeBudgetaire = ref(props.modelValue ?? '')
71
- const derniereValeurSauvegardee = ref<string | null>(null)
72
- const format = props.format
73
- const activerExtension = props.activerExtension
74
-
75
- onMounted(() => {
76
- derniereValeurSauvegardee.value = codeBudgetaire.value
77
- })
78
-
79
- const itemsCombobox = computed(() => {
80
- return props.codeBudgetairesProp.map(item => {
81
- if (typeof item === 'string') {
82
- return {
83
- code: item,
84
- nom: '', // pas de nom
85
- }
86
- }
79
+ const itemsCombobox = computed(() => {
80
+ return props.codeBudgetairesProp.map(item => {
81
+ if (typeof item === 'string') {
82
+ return {
83
+ code: item,
84
+ nom: '', // pas de nom
85
+ }
86
+ }
87
87
 
88
- const [code, nomBrut] = item
89
- const nom = (nomBrut ?? '').toString().trim()
88
+ const [code, nomBrut] = item
89
+ const nom = (nomBrut ?? '').toString().trim()
90
90
 
91
- return { code, nom }
92
- })
93
- })
91
+ return { code, nom }
92
+ })
93
+ })
94
94
 
95
- const placeholder = computed(() => {
96
- const base = format.replace(/9/g, '0')
97
- const extension = activerExtension ? '-XXX/XXX' : ''
98
- return base + extension
99
- })
95
+ const placeholder = computed(() => {
96
+ const base = format.replace(/9/g, '0')
97
+ const extension = activerExtension ? '-XXX/XXX' : ''
98
+ return base + extension
99
+ })
100
100
 
101
- const estValide = computed(() => {
102
- const val = codeBudgetaire.value?.toUpperCase().trim() || ''
103
- const base = val.slice(0, 15)
104
- const extension = val.slice(15)
101
+ const estValide = computed(() => {
102
+ const val = codeBudgetaire.value?.toUpperCase().trim() || ''
103
+ const base = val.slice(0, 15)
104
+ const extension = val.slice(15)
105
105
 
106
- if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
106
+ if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
107
107
 
108
- if (!activerExtension) return val.length === 15
108
+ if (!activerExtension) return val.length === 15
109
+
110
+ if (val.length === 15) return true
111
+ if (val.length !== 22) return false
112
+ if (extension.length !== 7) return false
113
+
114
+ if (extension[3] !== '/') return false
115
+ if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
116
+
117
+ const alphanumAt = [1, 2, 4, 5, 6]
118
+ return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
119
+ })
120
+
121
+ // Règles Vuetify combinées (interne + supplémentaires)
122
+ const reglesVuetify = computed(() => [
123
+ // règle interne
124
+ () => (estValide.value ? true : props.regleMessageErreur),
125
+
126
+ // règles supplémentaires venant du parent
127
+ ...props.reglesSupp.map(rule => {
128
+ return () => rule(codeBudgetaire.value) // on passe la valeur formatée
129
+ }),
130
+ ])
131
+
132
+ // Validité globale du composant (interne + toutes les règles supp)
133
+ const estValideComplet = computed(() => {
134
+ if (!estValide.value) return false
135
+ return props.reglesSupp.every(rule => rule(codeBudgetaire.value) === true)
136
+ })
109
137
 
110
- if (val.length === 15) return true
111
- if (val.length !== 22) return false
112
- if (extension.length !== 7) return false
138
+ const caractereAutorises = (e: KeyboardEvent) => {
139
+ if (e.ctrlKey || e.metaKey) return
113
140
 
114
- if (extension[3] !== '/') return false
115
- if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
141
+ const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
142
+ if (touchesSpecifiques.includes(e.key)) return
116
143
 
117
- const alphanumAt = [1, 2, 4, 5, 6]
118
- return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
119
- })
144
+ const input = e.target as HTMLInputElement
145
+ let position = input.selectionStart ?? 0
120
146
 
121
- // Règles Vuetify combinées (interne + supplémentaires)
122
- const reglesVuetify = computed(() => [
123
- // règle interne
124
- () => (estValide.value ? true : props.regleMessageErreur),
147
+ // Gestion de la partie de base (15 premiers caractères)
148
+ if (position < 15) {
149
+ if (!/^\d$/.test(e.key)) {
150
+ e.preventDefault()
151
+ return
152
+ }
125
153
 
126
- // règles supplémentaires venant du parent
127
- ...props.reglesSupp.map(rule => {
128
- return () => rule(codeBudgetaire.value) // on passe la valeur formatée
129
- }),
130
- ])
154
+ // Insérer chiffre et auto-ajout des tirets
155
+ e.preventDefault()
131
156
 
132
- // Validité globale du composant (interne + toutes les règles supp)
133
- const estValideComplet = computed(() => {
134
- if (!estValide.value) return false
135
- return props.reglesSupp.every(rule => rule(codeBudgetaire.value) === true)
136
- })
157
+ const value = codeBudgetaire.value.replace(/-/g, '')
158
+ const clean = value.slice(0, 12) + e.key
137
159
 
138
- const caractereAutorises = (e: KeyboardEvent) => {
139
- if (e.ctrlKey || e.metaKey) return
160
+ let formatted = ''
161
+ if (clean.length > 0) formatted += clean.slice(0, 3)
162
+ if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
163
+ if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
164
+ if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
140
165
 
141
- const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
142
- if (touchesSpecifiques.includes(e.key)) return
166
+ codeBudgetaire.value = formatted.slice(0, 15)
143
167
 
144
- const input = e.target as HTMLInputElement
145
- let position = input.selectionStart ?? 0
168
+ nextTick(() => {
169
+ const newPos = codeBudgetaire.value.length
170
+ input.selectionStart = input.selectionEnd = newPos
171
+ })
146
172
 
147
- // Gestion de la partie de base (15 premiers caractères)
148
- if (position < 15) {
149
- if (!/^\d$/.test(e.key)) {
173
+ return
174
+ }
175
+
176
+ // --- Gestion de l'extension ---
177
+ if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
178
+ e.preventDefault()
179
+ return
180
+ }
181
+
182
+ const extensionPos = position - 15
183
+
184
+ // Règle 1 : extension[0] = alphanum ou /
185
+ if (extensionPos === 0) {
186
+ if (!/^[A-Z0-9/]$/i.test(e.key)) {
187
+ e.preventDefault()
188
+ return
189
+ }
190
+ return
191
+ }
192
+
193
+ // Règle 2 : extension[1,2,4,5,6] = alphanum
194
+ if ([1, 2, 4, 5, 6].includes(extensionPos)) {
195
+ if (!/^[A-Z0-9]$/i.test(e.key)) {
196
+ e.preventDefault()
197
+ return
198
+ }
199
+ return
200
+ }
201
+
202
+ // Règle 3 : slash automatique à position 3 (index 18)
203
+ if (extensionPos === 3) {
204
+ e.preventDefault()
205
+ const before = codeBudgetaire.value.slice(0, position)
206
+ const after = codeBudgetaire.value.slice(position)
207
+ codeBudgetaire.value = before + '/' + after
208
+ nextTick(() => {
209
+ input.selectionStart = input.selectionEnd = position + 1
210
+ })
211
+ return
212
+ }
213
+
214
+ // Tout autre cas = bloqué
150
215
  e.preventDefault()
151
- return
152
216
  }
153
217
 
154
- // Insérer chiffre et auto-ajout des tirets
155
- e.preventDefault()
218
+ const formaterCodeBudgetaire = (valeur: string): string => {
219
+ if (!valeur) return ''
156
220
 
157
- const value = codeBudgetaire.value.replace(/-/g, '')
158
- const clean = value.slice(0, 12) + e.key
221
+ const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
222
+ const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
159
223
 
160
- let formatted = ''
161
- if (clean.length > 0) formatted += clean.slice(0, 3)
162
- if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
163
- if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
164
- if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
224
+ let base = ''
225
+ if (chiffres.length > 0) base += chiffres.slice(0, 3)
226
+ if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
227
+ if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
228
+ if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
165
229
 
166
- codeBudgetaire.value = formatted.slice(0, 15)
230
+ if (!activerExtension || base.length < 15) return base
167
231
 
168
- nextTick(() => {
169
- const newPos = codeBudgetaire.value.length
170
- input.selectionStart = input.selectionEnd = newPos
171
- })
232
+ const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
172
233
 
173
- return
174
- }
234
+ // Extraire les 7 premiers caractères restants pour l’extension
235
+ let ext = reste.slice(0, 7).split('')
175
236
 
176
- // --- Gestion de l'extension ---
177
- if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
178
- e.preventDefault()
179
- return
180
- }
237
+ // Ne garder que le premier slash s’il est à l’index 0 ou 3
238
+ ext = ext.filter((c, i) => {
239
+ if (c !== '/') return true
240
+ return i === 0 || i === 3
241
+ })
181
242
 
182
- const extensionPos = position - 15
243
+ // Enlever les slash non autorisés
244
+ ext = ext.map((c, i) => {
245
+ if (c === '/' && i !== 0 && i !== 3) return ''
246
+ if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
247
+ return c
248
+ })
183
249
 
184
- // Règle 1 : extension[0] = alphanum ou /
185
- if (extensionPos === 0) {
186
- if (!/^[A-Z0-9/]$/i.test(e.key)) {
187
- e.preventDefault()
188
- return
250
+ // Forcer le slash à la 4e position
251
+ if (ext.length > 3) {
252
+ ext[3] = '/'
253
+ }
254
+
255
+ // Réduire à 7 caractères
256
+ ext = ext.slice(0, 7)
257
+
258
+ return (base + ext.join('')).slice(0, 22)
189
259
  }
190
- return
191
- }
192
260
 
193
- // Règle 2 : extension[1,2,4,5,6] = alphanum
194
- if ([1, 2, 4, 5, 6].includes(extensionPos)) {
195
- if (!/^[A-Z0-9]$/i.test(e.key)) {
261
+ const gererPaste = (e: ClipboardEvent) => {
196
262
  e.preventDefault()
197
- return
263
+ const clipboardData = e.clipboardData
264
+ if (!clipboardData) return
265
+ let pasted = clipboardData.getData('text') || ''
266
+ codeBudgetaire.value = formaterCodeBudgetaire(pasted)
267
+
268
+ setTimeout(() => {
269
+ const input = e.target as HTMLInputElement
270
+ input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
271
+ }, 0)
272
+ }
273
+
274
+ const sauvegarder = () => {
275
+ codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
276
+
277
+ if (!estValideComplet.value) return
278
+ if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
279
+
280
+ const codeNormalise = codeBudgetaire.value.trim().toUpperCase()
281
+
282
+ const existe = props.codeBudgetairesProp.some(item => {
283
+ const code = typeof item === 'string' ? item : item[0]
284
+ return code.trim().toUpperCase() === codeNormalise
285
+ })
286
+
287
+ if (!existe) {
288
+ const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
289
+ emit('update:codeBudgetairesProp', nouvelleListe)
290
+ }
291
+
292
+ derniereValeurSauvegardee.value = codeBudgetaire.value
293
+ emit('update:modelValue', codeBudgetaire.value)
294
+ }
295
+
296
+ const extraireCode = (val: unknown): string => {
297
+ if (val == null) return ''
298
+ if (typeof val === 'string') return val
299
+
300
+ // Cas où Vuetify enverrait { code, label }
301
+ if (typeof val === 'object' && 'code' in (val as Record<string, unknown>)) {
302
+ const obj = val as { code?: unknown }
303
+ return typeof obj.code === 'string' ? obj.code : String(obj.code ?? '')
304
+ }
305
+
306
+ return String(val)
198
307
  }
199
- return
200
- }
201
-
202
- // Règle 3 : slash automatique à position 3 (index 18)
203
- if (extensionPos === 3) {
204
- e.preventDefault()
205
- const before = codeBudgetaire.value.slice(0, position)
206
- const after = codeBudgetaire.value.slice(position)
207
- codeBudgetaire.value = before + '/' + after
208
- nextTick(() => {
209
- input.selectionStart = input.selectionEnd = position + 1
210
- })
211
- return
212
- }
213
308
 
214
- // Tout autre cas = bloqué
215
- e.preventDefault()
216
- }
309
+ const gererChangement = (val: unknown) => {
310
+ const code = extraireCode(val)
311
+ codeBudgetaire.value = formaterCodeBudgetaire(code)
217
312
 
218
- const formaterCodeBudgetaire = (valeur: string): string => {
219
- if (!valeur) return ''
313
+ const valeurFormatee = codeBudgetaire.value.trim().toUpperCase()
220
314
 
221
- const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
222
- const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
223
-
224
- let base = ''
225
- if (chiffres.length > 0) base += chiffres.slice(0, 3)
226
- if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
227
- if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
228
- if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
229
-
230
- if (!activerExtension || base.length < 15) return base
231
-
232
- const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
233
-
234
- // Extraire les 7 premiers caractères restants pour l’extension
235
- let ext = reste.slice(0, 7).split('')
236
-
237
- // Ne garder que le premier slash s’il est à l’index 0 ou 3
238
- ext = ext.filter((c, i) => {
239
- if (c !== '/') return true
240
- return i === 0 || i === 3
241
- })
242
-
243
- // Enlever les slash non autorisés
244
- ext = ext.map((c, i) => {
245
- if (c === '/' && i !== 0 && i !== 3) return ''
246
- if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
247
- return c
248
- })
249
-
250
- // Forcer le slash à la 4e position
251
- if (ext.length > 3) {
252
- ext[3] = '/'
253
- }
254
-
255
- // Réduire à 7 caractères
256
- ext = ext.slice(0, 7)
257
-
258
- return (base + ext.join('')).slice(0, 22)
259
- }
260
-
261
- const gererPaste = (e: ClipboardEvent) => {
262
- e.preventDefault()
263
- const clipboardData = e.clipboardData
264
- if (!clipboardData) return
265
- let pasted = clipboardData.getData('text') || ''
266
- codeBudgetaire.value = formaterCodeBudgetaire(pasted)
267
-
268
- setTimeout(() => {
269
- const input = e.target as HTMLInputElement
270
- input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
271
- }, 0)
272
- }
273
-
274
- const sauvegarder = () => {
275
- codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
276
-
277
- if (!estValideComplet.value) return
278
- if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
279
-
280
- const codeNormalise = codeBudgetaire.value.trim().toUpperCase()
281
-
282
- const existe = props.codeBudgetairesProp.some(item => {
283
- const code = typeof item === 'string' ? item : item[0]
284
- return code.trim().toUpperCase() === codeNormalise
285
- })
286
-
287
- if (!existe) {
288
- const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
289
- emit('update:codeBudgetairesProp', nouvelleListe)
290
- }
291
-
292
- derniereValeurSauvegardee.value = codeBudgetaire.value
293
- emit('update:modelValue', codeBudgetaire.value)
294
- }
295
-
296
- const extraireCode = (val: unknown): string => {
297
- if (val == null) return ''
298
- if (typeof val === 'string') return val
299
-
300
- // Cas où Vuetify enverrait { code, label }
301
- if (typeof val === 'object' && 'code' in (val as Record<string, unknown>)) {
302
- const obj = val as { code?: unknown }
303
- return typeof obj.code === 'string' ? obj.code : String(obj.code ?? '')
304
- }
305
-
306
- return String(val)
307
- }
308
-
309
- const gererChangement = (val: unknown) => {
310
- const code = extraireCode(val)
311
- codeBudgetaire.value = formaterCodeBudgetaire(code)
312
-
313
- const valeurFormatee = codeBudgetaire.value.trim().toUpperCase()
314
-
315
- const estDansListe = props.codeBudgetairesProp.some(item => {
316
- const codeItem = typeof item === 'string' ? item : item[0]
317
- return codeItem.trim().toUpperCase() === valeurFormatee
318
- })
319
-
320
- if (
321
- estDansListe &&
322
- valeurFormatee !== (derniereValeurSauvegardee.value ?? '').toUpperCase() &&
323
- estValideComplet.value
324
- ) {
325
- sauvegarder()
326
- }
327
- }
328
-
329
- watch(estValideComplet, value => {
330
- emit('update:valide', value)
331
- })
315
+ const estDansListe = props.codeBudgetairesProp.some(item => {
316
+ const codeItem = typeof item === 'string' ? item : item[0]
317
+ return codeItem.trim().toUpperCase() === valeurFormatee
318
+ })
319
+
320
+ if (
321
+ estDansListe &&
322
+ valeurFormatee !== (derniereValeurSauvegardee.value ?? '').toUpperCase() &&
323
+ estValideComplet.value
324
+ ) {
325
+ sauvegarder()
326
+ }
327
+ }
328
+
329
+ watch(
330
+ () => codeBudgetaire.value,
331
+ () => {
332
+ emit('update:valide', estValideComplet.value)
333
+ },
334
+ { immediate: true },
335
+ )
332
336
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",