codevdesign 1.0.54 → 1.0.56

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,55 +1,55 @@
1
- <template>
2
- <v-menu
3
- v-if="!isXs"
4
- :open-on-hover="hover"
5
- location="top start"
6
- >
7
- <template #activator="{ props: activatorProps }">
8
- <slot name="icone">
9
- <v-icon
10
- v-bind="activatorProps"
11
- :style="styleCss"
12
- :size="grosseurEffective"
13
- color="grisMoyen"
14
- icon="mdi-help-circle"
15
- @click.stop.prevent
16
- />
17
- </slot>
18
- </template>
19
-
20
- <slot name="carte">
21
- <v-expand-transition>
22
- <v-card style="max-width: 1000px">
23
- <v-card-text class="pa-6">
24
- <span v-html="props.aide"></span>
25
- </v-card-text> </v-card
26
- ></v-expand-transition>
27
- </slot>
28
- </v-menu>
29
- </template>
30
-
31
- <script setup lang="ts">
32
- import { computed } from 'vue'
33
- import { useDisplay } from 'vuetify'
34
-
35
- const props = withDefaults(
36
- defineProps<{
37
- aide: string
38
- grosseur?: 'default' | 'small' | 'large' | 'x-large' | 'x-small'
39
- hover?: boolean
40
- styleCss?: string
41
- }>(),
42
- {
43
- grosseur: 'default',
44
- hover: false,
45
- styleCss: '',
46
- },
47
- )
48
- const grosseurEffective = computed(() => {
49
- const val = props.grosseur
50
- return val && val.trim() !== '' ? val : 'default'
51
- })
52
-
53
- const { name } = useDisplay()
54
- const isXs = computed(() => name.value === 'xs')
55
- </script>
1
+ <template>
2
+ <v-menu
3
+ v-if="!isXs"
4
+ :open-on-hover="hover"
5
+ location="top start"
6
+ >
7
+ <template #activator="{ props: activatorProps }">
8
+ <slot name="icone">
9
+ <v-icon
10
+ v-bind="activatorProps"
11
+ :style="styleCss"
12
+ :size="grosseurEffective"
13
+ color="grisMoyen"
14
+ icon="mdi-help-circle"
15
+ @click.stop.prevent
16
+ />
17
+ </slot>
18
+ </template>
19
+
20
+ <slot name="carte">
21
+ <v-expand-transition>
22
+ <v-card style="max-width: 1000px">
23
+ <v-card-text class="pa-6">
24
+ <span v-html="props.aide"></span>
25
+ </v-card-text> </v-card
26
+ ></v-expand-transition>
27
+ </slot>
28
+ </v-menu>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import { computed } from 'vue'
33
+ import { useDisplay } from 'vuetify'
34
+
35
+ const props = withDefaults(
36
+ defineProps<{
37
+ aide: string
38
+ grosseur?: 'default' | 'small' | 'large' | 'x-large' | 'x-small'
39
+ hover?: boolean
40
+ styleCss?: string
41
+ }>(),
42
+ {
43
+ grosseur: 'default',
44
+ hover: false,
45
+ styleCss: '',
46
+ },
47
+ )
48
+ const grosseurEffective = computed(() => {
49
+ const val = props.grosseur
50
+ return val && val.trim() !== '' ? val : 'default'
51
+ })
52
+
53
+ const { name } = useDisplay()
54
+ const isXs = computed(() => name.value === 'xs')
55
+ </script>
@@ -1,87 +1,87 @@
1
- <template>
2
- <div
3
- v-if="afficher && erreur"
4
- :style="retournerStyle"
5
- >
6
- <v-alert
7
- v-model="afficher"
8
- class="mb-0 text-center hyphen_auto"
9
- color="#CB381F"
10
- closable
11
- v-bind="$attrs"
12
- @click:close="fermer"
13
- >
14
- <span v-html="erreur" /><br />
15
- <span
16
- id="messageSoutien"
17
- style="color: darkgray"
18
- >{{ messageSoutien }}</span
19
- >
20
- </v-alert>
21
- </div>
22
- </template>
23
-
24
- <script lang="ts" setup>
25
- import { computed, ref, watch } from 'vue'
26
- import { useDisplay } from 'vuetify'
27
- import { useI18n } from 'vue-i18n'
28
-
29
- // Définir les props avec les types
30
- const props = defineProps({
31
- message: {
32
- type: String,
33
- required: true,
34
- },
35
- messageSoutien: {
36
- type: String,
37
- required: true,
38
- },
39
- })
40
- const { name: displayName } = useDisplay()
41
- const emit = defineEmits(['fermer:alerte'])
42
- const { t } = useI18n({ useScope: 'global' })
43
-
44
- const fermer = (): void => {
45
- afficher.value = false
46
- emit('fermer:alerte')
47
- }
48
-
49
- const afficher = ref(false)
50
-
51
- // Watcher pour réagir aux changements du message
52
- watch(
53
- () => props.message,
54
- nouveauMessage => {
55
- if (nouveauMessage && nouveauMessage !== '') {
56
- afficher.value = true
57
- }
58
- },
59
- )
60
-
61
- // Computed pour l'erreur, venant du store
62
- const erreur = computed(() => {
63
- if (props.message == null || props.message == '') return null
64
-
65
- if (props.message.indexOf('erreurs.') === 0) return t(props.message)
66
-
67
- return props.message
68
- })
69
-
70
- // Méthode computed pour le style en fonction du breakpoint
71
- const retournerStyle = computed(() => {
72
- if (displayName.value === 'xs') {
73
- return 'top: 0px; right:1%;width: 98%;position: fixed; z-index: 2000;'
74
- }
75
- return 'top: 20px; right: 10%; width: 80%; position: fixed; z-index: 2000; margin: 0 auto;'
76
- })
77
- </script>
78
-
79
- <style lang="css" scoped>
80
- .error {
81
- padding-top: 0.7rem;
82
- padding-bottom: 0.7rem;
83
- }
84
- :deep(.blanc) {
85
- color: white;
86
- }
87
- </style>
1
+ <template>
2
+ <div
3
+ v-if="afficher && erreur"
4
+ :style="retournerStyle"
5
+ >
6
+ <v-alert
7
+ v-model="afficher"
8
+ class="mb-0 text-center hyphen_auto"
9
+ color="#CB381F"
10
+ closable
11
+ v-bind="$attrs"
12
+ @click:close="fermer"
13
+ >
14
+ <span v-html="erreur" /><br />
15
+ <span
16
+ id="messageSoutien"
17
+ style="color: darkgray"
18
+ >{{ messageSoutien }}</span
19
+ >
20
+ </v-alert>
21
+ </div>
22
+ </template>
23
+
24
+ <script lang="ts" setup>
25
+ import { computed, ref, watch } from 'vue'
26
+ import { useDisplay } from 'vuetify'
27
+ import { useI18n } from 'vue-i18n'
28
+
29
+ // Définir les props avec les types
30
+ const props = defineProps({
31
+ message: {
32
+ type: String,
33
+ required: true,
34
+ },
35
+ messageSoutien: {
36
+ type: String,
37
+ required: true,
38
+ },
39
+ })
40
+ const { name: displayName } = useDisplay()
41
+ const emit = defineEmits(['fermer:alerte'])
42
+ const { t } = useI18n({ useScope: 'global' })
43
+
44
+ const fermer = (): void => {
45
+ afficher.value = false
46
+ emit('fermer:alerte')
47
+ }
48
+
49
+ const afficher = ref(false)
50
+
51
+ // Watcher pour réagir aux changements du message
52
+ watch(
53
+ () => props.message,
54
+ nouveauMessage => {
55
+ if (nouveauMessage && nouveauMessage !== '') {
56
+ afficher.value = true
57
+ }
58
+ },
59
+ )
60
+
61
+ // Computed pour l'erreur, venant du store
62
+ const erreur = computed(() => {
63
+ if (props.message == null || props.message == '') return null
64
+
65
+ if (props.message.indexOf('erreurs.') === 0) return t(props.message)
66
+
67
+ return props.message
68
+ })
69
+
70
+ // Méthode computed pour le style en fonction du breakpoint
71
+ const retournerStyle = computed(() => {
72
+ if (displayName.value === 'xs') {
73
+ return 'top: 0px; right:1%;width: 98%;position: fixed; z-index: 2000;'
74
+ }
75
+ return 'top: 20px; right: 10%; width: 80%; position: fixed; z-index: 2000; margin: 0 auto;'
76
+ })
77
+ </script>
78
+
79
+ <style lang="css" scoped>
80
+ .error {
81
+ padding-top: 0.7rem;
82
+ padding-bottom: 0.7rem;
83
+ }
84
+ :deep(.blanc) {
85
+ color: white;
86
+ }
87
+ </style>
@@ -1,54 +1,54 @@
1
- <template>
2
- <div>
3
- <v-list-item-title>{{ nomUnite }}</v-list-item-title>
4
- <v-list-item-subtitle v-if="getPreference != ''">
5
- {{ getPreference }}
6
- </v-list-item-subtitle>
7
- <v-list-item-subtitle
8
- v-if="getPreference == ''"
9
- class="messageErreurChaiseItem"
10
- >
11
- {{ $t('csqc.message.chaiseSelection') }}
12
- </v-list-item-subtitle>
13
- </div>
14
- </template>
15
- <script lang="ts" setup>
16
- import { computed } from 'vue'
17
-
18
- // Définition des props
19
- const props = defineProps<{
20
- uniteId: number
21
- preferences: { chaiseId: number }[]
22
- dictChaisesReleve: Record<number, { id: number; nom: string }[]>
23
- unites: { id: number; nom: string }[]
24
- }>()
25
-
26
- const nomUnite = computed(() => {
27
- if (!props.uniteId || !props.unites?.length) {
28
- return ''
29
- }
30
-
31
- const unite = props.unites.find(u => u.id == props.uniteId)
32
- return unite?.nom || ''
33
- })
34
-
35
- const getPreference = computed(() => {
36
- const chaises = props.dictChaisesReleve?.[props.uniteId] ?? []
37
- const prefs = props.preferences ?? []
38
-
39
- for (const chaise of chaises) {
40
- if (prefs.some(p => p.chaiseId === chaise.id)) {
41
- return chaise.nom
42
- }
43
- }
44
-
45
- return ''
46
- })
47
- </script>
48
-
49
- <style scoped>
50
- .messageErreurChaiseItem {
51
- color: red !important;
52
- font-style: italic;
53
- }
54
- </style>
1
+ <template>
2
+ <div>
3
+ <v-list-item-title>{{ nomUnite }}</v-list-item-title>
4
+ <v-list-item-subtitle v-if="getPreference != ''">
5
+ {{ getPreference }}
6
+ </v-list-item-subtitle>
7
+ <v-list-item-subtitle
8
+ v-if="getPreference == ''"
9
+ class="messageErreurChaiseItem"
10
+ >
11
+ {{ $t('csqc.message.chaiseSelection') }}
12
+ </v-list-item-subtitle>
13
+ </div>
14
+ </template>
15
+ <script lang="ts" setup>
16
+ import { computed } from 'vue'
17
+
18
+ // Définition des props
19
+ const props = defineProps<{
20
+ uniteId: number
21
+ preferences: { chaiseId: number }[]
22
+ dictChaisesReleve: Record<number, { id: number; nom: string }[]>
23
+ unites: { id: number; nom: string }[]
24
+ }>()
25
+
26
+ const nomUnite = computed(() => {
27
+ if (!props.uniteId || !props.unites?.length) {
28
+ return ''
29
+ }
30
+
31
+ const unite = props.unites.find(u => u.id == props.uniteId)
32
+ return unite?.nom || ''
33
+ })
34
+
35
+ const getPreference = computed(() => {
36
+ const chaises = props.dictChaisesReleve?.[props.uniteId] ?? []
37
+ const prefs = props.preferences ?? []
38
+
39
+ for (const chaise of chaises) {
40
+ if (prefs.some(p => p.chaiseId === chaise.id)) {
41
+ return chaise.nom
42
+ }
43
+ }
44
+
45
+ return ''
46
+ })
47
+ </script>
48
+
49
+ <style scoped>
50
+ .messageErreurChaiseItem {
51
+ color: red !important;
52
+ font-style: italic;
53
+ }
54
+ </style>
@@ -1,75 +1,75 @@
1
- <template>
2
- <csqcDialogue
3
- ref="modale"
4
- :titre="props.titre"
5
- :operation-en-cours="operationEnCours"
6
- activator="supprimer"
7
- v-bind="$attrs"
8
- :largeur="props.largeur"
9
- @ok="confirmer"
10
- @annuler="annuler"
11
- >
12
- <v-form
13
- ref="form"
14
- @submit.prevent
15
- >
16
- <v-row>
17
- <v-col
18
- cols="12"
19
- class="pa-0 ma-0"
20
- >
21
- <span v-html="texte"></span>
22
- </v-col>
23
- </v-row>
24
- </v-form>
25
- </csqcDialogue>
26
- </template>
27
-
28
- <script lang="ts" setup>
29
- import { ref } from 'vue'
30
- import csqcDialogue from './csqcDialogue.vue'
31
-
32
- const modale = ref<InstanceType<typeof csqcDialogue> | null>(null)
33
- const utilisateurATermine = ref(false)
34
- const reponse = ref(false)
35
- const texte = ref('')
36
- const modeAlerte = ref(false)
37
-
38
- const props = defineProps({
39
- operationEnCours: { type: Boolean, default: false },
40
- titre: { type: String, default: '' },
41
- largeur: { type: String, default: '525px' },
42
- })
43
-
44
- const ouvrir = async (message: string, modeAlerteParam: boolean = false) => {
45
- texte.value = message
46
- modeAlerte.value = modeAlerteParam
47
- utilisateurATermine.value = false
48
- modale.value?.ouvrir()
49
-
50
- while (!utilisateurATermine.value) {
51
- await new Promise(resolve => setTimeout(resolve, 100)) // Attendre 100ms
52
- }
53
-
54
- return reponse.value
55
- }
56
-
57
- const confirmer = (): void => {
58
- reponse.value = true
59
- utilisateurATermine.value = true
60
-
61
- if (modeAlerte.value) fermer()
62
- }
63
-
64
- const annuler = (): void => {
65
- reponse.value = false
66
- utilisateurATermine.value = true
67
- fermer()
68
- }
69
-
70
- const fermer = (): void => {
71
- modale.value?.fermer()
72
- }
73
-
74
- defineExpose({ ouvrir, fermer })
75
- </script>
1
+ <template>
2
+ <csqcDialogue
3
+ ref="modale"
4
+ :titre="props.titre"
5
+ :operation-en-cours="operationEnCours"
6
+ activator="supprimer"
7
+ v-bind="$attrs"
8
+ :largeur="props.largeur"
9
+ @ok="confirmer"
10
+ @annuler="annuler"
11
+ >
12
+ <v-form
13
+ ref="form"
14
+ @submit.prevent
15
+ >
16
+ <v-row>
17
+ <v-col
18
+ cols="12"
19
+ class="pa-0 ma-0"
20
+ >
21
+ <span v-html="texte"></span>
22
+ </v-col>
23
+ </v-row>
24
+ </v-form>
25
+ </csqcDialogue>
26
+ </template>
27
+
28
+ <script lang="ts" setup>
29
+ import { ref } from 'vue'
30
+ import csqcDialogue from './csqcDialogue.vue'
31
+
32
+ const modale = ref<InstanceType<typeof csqcDialogue> | null>(null)
33
+ const utilisateurATermine = ref(false)
34
+ const reponse = ref(false)
35
+ const texte = ref('')
36
+ const modeAlerte = ref(false)
37
+
38
+ const props = defineProps({
39
+ operationEnCours: { type: Boolean, default: false },
40
+ titre: { type: String, default: '' },
41
+ largeur: { type: String, default: '525px' },
42
+ })
43
+
44
+ const ouvrir = async (message: string, modeAlerteParam: boolean = false) => {
45
+ texte.value = message
46
+ modeAlerte.value = modeAlerteParam
47
+ utilisateurATermine.value = false
48
+ modale.value?.ouvrir()
49
+
50
+ while (!utilisateurATermine.value) {
51
+ await new Promise(resolve => setTimeout(resolve, 100)) // Attendre 100ms
52
+ }
53
+
54
+ return reponse.value
55
+ }
56
+
57
+ const confirmer = (): void => {
58
+ reponse.value = true
59
+ utilisateurATermine.value = true
60
+
61
+ if (modeAlerte.value) fermer()
62
+ }
63
+
64
+ const annuler = (): void => {
65
+ reponse.value = false
66
+ utilisateurATermine.value = true
67
+ fermer()
68
+ }
69
+
70
+ const fermer = (): void => {
71
+ modale.value?.fermer()
72
+ }
73
+
74
+ defineExpose({ ouvrir, fermer })
75
+ </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <v-date-input
3
- v-model="dateInterne"
3
+ :model-value="dateInterne"
4
4
  v-bind="$attrs"
5
5
  input-format="yyyy-MM-dd"
6
6
  variant="outlined"
@@ -12,7 +12,7 @@
12
12
  </template>
13
13
 
14
14
  <script setup lang="ts">
15
- import { computed } from 'vue'
15
+ import { computed, ref } from 'vue'
16
16
  import { VDateInput } from 'vuetify/labs/VDateInput'
17
17
 
18
18
  type Model = string | Date | null
@@ -20,7 +20,7 @@
20
20
  const props = defineProps<{ modelValue: Model }>()
21
21
 
22
22
  const emit = defineEmits<{
23
- (e: 'update:model-value', v: string | null): void
23
+ (e: 'update:modelValue', v: string | null): void
24
24
  (e: 'change', v: string | null): void
25
25
  }>()
26
26
 
@@ -28,6 +28,7 @@
28
28
  const normalizeToYmd = (v: Date | string | null | undefined): string | null => {
29
29
  if (v == null || v === '') return null
30
30
 
31
+ // Date -> YYYY-MM-DD (local)
31
32
  if (v instanceof Date) {
32
33
  if (isNaN(v.getTime())) return null
33
34
  const yyyy = v.getFullYear()
@@ -36,44 +37,50 @@
36
37
  return `${yyyy}-${mm}-${dd}`
37
38
  }
38
39
 
40
+ // String -> accepte seulement YYYY-MM-DD ISO8601 (optionnellement suivi de l'heure, qui est ignorée)
39
41
  const s = String(v).trim()
42
+ const m = s.match(
43
+ /^(?<date>(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}))(?:T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|(?:[-+]\d{2}:\d{2}?))?)?$/,
44
+ )
45
+ if (!m) return null
40
46
 
41
- // si ça commence par YYYY-MM-DD, on prend les 10 premiers chars
42
- const m = s.match(/^(\d{4}-\d{2}-\d{2})/)
43
- if (m) return m[1]
47
+ // Optionnel: validation simple (évite 2026-99-99)
48
+ const y = Number(m.groups!.year),
49
+ mo = Number(m.groups!.month),
50
+ d = Number(m.groups!.day)
51
+ if (mo < 1 || mo > 12 || d < 1 || d > 31) return null
52
+ if (mo === 2) {
53
+ const isLeap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0
54
+ if (d > (isLeap ? 29 : 28)) return null
55
+ }
56
+ if ([4, 6, 9, 11].includes(mo) && d > 30) return null
44
57
 
45
- // Sinon, on tente un fallback
46
- const d = new Date(s)
47
- if (isNaN(d.getTime())) return null
48
- const yyyy = d.getFullYear()
49
- const mm = String(d.getMonth() + 1).padStart(2, '0')
50
- const dd = String(d.getDate()).padStart(2, '0')
51
- return `${yyyy}-${mm}-${dd}`
58
+ return `${m.groups!.date}`
52
59
  }
53
60
 
54
61
  // UI: VDateInput stable avec Date|null
55
- const dateInterne = computed<Date | null>({
56
- get() {
57
- const ymd = normalizeToYmd(props.modelValue as any)
58
- if (!ymd) return null
59
-
60
- // Construire une Date locale sans décalage timezone (évite UTC shift)
61
- const [y, m, d] = ymd.split('-').map(Number)
62
- return new Date(y, m - 1, d)
63
- },
64
- set() {
65
- // rien: on émet seulement dans choixUtilisateur
66
- },
62
+ const dateInterne = computed<Date | null>(() => {
63
+ const ymd = normalizeToYmd(props.modelValue as any)
64
+ if (!ymd) return null
65
+ const [y, m, d] = ymd.split('-').map(Number)
66
+ return new Date(y, m - 1, d) // local, stable
67
67
  })
68
68
 
69
+ const prevBeforeEmit = ref<string | null>(null)
70
+
69
71
  const choixUtilisateur = (v: Date | string | null) => {
70
72
  const next = normalizeToYmd(v)
71
73
  const current = normalizeToYmd(props.modelValue as any)
72
74
 
73
- // Bloque la 2e émission au blur (même si type diffère Date/string)
75
+ // rollback: on reçoit l'ancienne valeur (celle qu'on avait avant)
76
+ if (prevBeforeEmit.value && next === prevBeforeEmit.value && current !== prevBeforeEmit.value) {
77
+ return
78
+ }
79
+
74
80
  if (next === current) return
75
81
 
76
- emit('update:model-value', next)
82
+ prevBeforeEmit.value = current
83
+ emit('update:modelValue', next)
77
84
  emit('change', next)
78
85
  }
79
86
  </script>