codevdesign 0.0.57 → 0.0.59

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.
@@ -0,0 +1,261 @@
1
+ <template>
2
+ <div>
3
+ <v-form
4
+ ref="form"
5
+ v-model="formValide"
6
+ @submit.prevent
7
+ >
8
+ <v-combobox
9
+ v-model="codeBudgetaire"
10
+ :items="codeBudgetairesProp"
11
+ :label="label"
12
+ persistent-hint
13
+ variant="outlined"
14
+ hide-details="auto"
15
+ :error="!estValide"
16
+ :rules="[v => (estValide ? true : regleMessageErreur)]"
17
+ :disabled="disable"
18
+ :density="density"
19
+ :placeholder="placeholder"
20
+ :max-width="maxWidth || '100%'"
21
+ :min-width="minWidth || '100%'"
22
+ @blur="sauvegarder"
23
+ @keydown.enter="sauvegarder"
24
+ @keydown="caractereAutorises"
25
+ @update:modelValue="gererChangement"
26
+ @paste="gererPaste"
27
+ />
28
+ </v-form>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup lang="ts">
33
+ import { ref, computed, onMounted, nextTick } from 'vue'
34
+ import { VForm } from 'vuetify/components'
35
+
36
+ const emit = defineEmits(['update:modelValue', 'update:codeBudgetairesProp'])
37
+
38
+ const props = withDefaults(
39
+ defineProps<{
40
+ codeBudgetairesProp: string[]
41
+ modelValue: string | null
42
+ label: string
43
+ disable: boolean
44
+ regleMessageErreur: string
45
+ density?: 'default' | 'comfortable' | 'compact'
46
+ maxWidth?: string | number
47
+ minWidth?: string | number
48
+ format?: string
49
+ activerExtension?: boolean
50
+ }>(),
51
+ {
52
+ format: '999-9-99999-999',
53
+ activerExtension: false,
54
+ },
55
+ )
56
+
57
+ const formValide = ref(false)
58
+ const form = ref<VForm | null>(null)
59
+ const codeBudgetaire = ref(props.modelValue ?? '')
60
+ const derniereValeurSauvegardee = ref<string | null>(null)
61
+ const format = props.format
62
+ const activerExtension = props.activerExtension
63
+
64
+ const positionsTirets = [3, 5, 11]
65
+
66
+ const estValide = computed(() => {
67
+ const val = codeBudgetaire.value?.toUpperCase().trim() || ''
68
+ const base = val.slice(0, 15)
69
+ const extension = val.slice(15)
70
+
71
+ if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
72
+
73
+ if (!activerExtension) return val.length === 15
74
+
75
+ if (val.length === 15) return true
76
+ if (val.length !== 22) return false
77
+ if (extension.length !== 7) return false
78
+
79
+ if (extension[3] !== '/') return false
80
+ if (!/^[A-Z0-9/]$/i.test(extension[0])) return false
81
+
82
+ const alphanumAt = [1, 2, 4, 5, 6]
83
+ return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]))
84
+ })
85
+
86
+ const caractereAutorises = (e: KeyboardEvent) => {
87
+ if (e.ctrlKey || e.metaKey) return
88
+
89
+ const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
90
+ if (touchesSpecifiques.includes(e.key)) return
91
+
92
+ const input = e.target as HTMLInputElement
93
+ let position = input.selectionStart ?? 0
94
+
95
+ // Gestion de la partie de base (15 premiers caractères)
96
+ if (position < 15) {
97
+ if (!/^\d$/.test(e.key)) {
98
+ e.preventDefault()
99
+ return
100
+ }
101
+
102
+ // Insérer chiffre et auto-ajout des tirets
103
+ e.preventDefault()
104
+
105
+ const value = codeBudgetaire.value.replace(/-/g, '')
106
+ const clean = value.slice(0, 12) + e.key
107
+
108
+ let formatted = ''
109
+ if (clean.length > 0) formatted += clean.slice(0, 3)
110
+ if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
111
+ if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
112
+ if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
113
+
114
+ codeBudgetaire.value = formatted.slice(0, 15)
115
+
116
+ nextTick(() => {
117
+ const newPos = codeBudgetaire.value.length
118
+ input.selectionStart = input.selectionEnd = newPos
119
+ })
120
+
121
+ return
122
+ }
123
+
124
+ // --- Gestion de l'extension ---
125
+ if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
126
+ e.preventDefault()
127
+ return
128
+ }
129
+
130
+ const extensionPos = position - 15
131
+
132
+ // Règle 1 : extension[0] = alphanum ou /
133
+ if (extensionPos === 0) {
134
+ if (!/^[A-Z0-9/]$/i.test(e.key)) {
135
+ e.preventDefault()
136
+ return
137
+ }
138
+ return
139
+ }
140
+
141
+ // Règle 2 : extension[1,2,4,5,6] = alphanum
142
+ if ([1, 2, 4, 5, 6].includes(extensionPos)) {
143
+ if (!/^[A-Z0-9]$/i.test(e.key)) {
144
+ e.preventDefault()
145
+ return
146
+ }
147
+ return
148
+ }
149
+
150
+ // Règle 3 : slash automatique à position 3 (index 18)
151
+ if (extensionPos === 3) {
152
+ e.preventDefault()
153
+ const before = codeBudgetaire.value.slice(0, position)
154
+ const after = codeBudgetaire.value.slice(position)
155
+ codeBudgetaire.value = before + '/' + after
156
+ nextTick(() => {
157
+ input.selectionStart = input.selectionEnd = position + 1
158
+ })
159
+ return
160
+ }
161
+
162
+ // Tout autre cas = bloqué
163
+ e.preventDefault()
164
+ }
165
+
166
+ const formaterCodeBudgetaire = (valeur: string): string => {
167
+ if (!valeur) return ''
168
+
169
+ const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
170
+ const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
171
+
172
+ let base = ''
173
+ if (chiffres.length > 0) base += chiffres.slice(0, 3)
174
+ if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
175
+ if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
176
+ if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
177
+
178
+ if (!activerExtension || base.length < 15) return base
179
+
180
+ const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
181
+
182
+ // Extraire les 7 premiers caractères restants pour l’extension
183
+ let ext = reste.slice(0, 7).split('')
184
+
185
+ // Ne garder que le premier slash s’il est à l’index 0 ou 3
186
+ const slashIndices = ext.map((c, i) => (c === '/' ? i : -1)).filter(i => i !== -1)
187
+ ext = ext.filter((c, i) => {
188
+ if (c !== '/') return true
189
+ return i === 0 || i === 3
190
+ })
191
+
192
+ // Enlever les slash non autorisés
193
+ ext = ext.map((c, i) => {
194
+ if (c === '/' && i !== 0 && i !== 3) return ''
195
+ if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
196
+ return c
197
+ })
198
+
199
+ // Forcer le slash à la 4e position
200
+ if (ext.length > 3) {
201
+ ext[3] = '/'
202
+ }
203
+
204
+ // Réduire à 7 caractères
205
+ ext = ext.slice(0, 7)
206
+
207
+ return (base + ext.join('')).slice(0, 22)
208
+ }
209
+
210
+ const gererPaste = (e: ClipboardEvent) => {
211
+ e.preventDefault()
212
+ const clipboardData = e.clipboardData
213
+ if (!clipboardData) return
214
+ let pasted = clipboardData.getData('text') || ''
215
+ codeBudgetaire.value = formaterCodeBudgetaire(pasted)
216
+
217
+ setTimeout(() => {
218
+ const input = e.target as HTMLInputElement
219
+ input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
220
+ }, 0)
221
+ }
222
+
223
+ const sauvegarder = () => {
224
+ codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
225
+ if (!estValide.value) return
226
+ if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
227
+
228
+ const existe = props.codeBudgetairesProp.some(
229
+ item => item.trim().toUpperCase() === codeBudgetaire.value.trim().toUpperCase(),
230
+ )
231
+
232
+ if (!existe) {
233
+ const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
234
+ emit('update:codeBudgetairesProp', nouvelleListe)
235
+ }
236
+
237
+ derniereValeurSauvegardee.value = codeBudgetaire.value
238
+ emit('update:modelValue', codeBudgetaire.value)
239
+ }
240
+
241
+ const gererChangement = (val: string) => {
242
+ codeBudgetaire.value = formaterCodeBudgetaire(val)
243
+
244
+ const valeurFormatee = codeBudgetaire.value
245
+ const estDansListe = props.codeBudgetairesProp.includes(valeurFormatee)
246
+
247
+ if (estDansListe && valeurFormatee !== derniereValeurSauvegardee.value && estValide.value) {
248
+ sauvegarder()
249
+ }
250
+ }
251
+
252
+ onMounted(() => {
253
+ derniereValeurSauvegardee.value = codeBudgetaire.value
254
+ })
255
+
256
+ const placeholder = computed(() => {
257
+ const base = format.replace(/9/g, '0')
258
+ const extension = activerExtension ? '-XXXXXXX' : ''
259
+ return base + extension
260
+ })
261
+ </script>
package/index.ts CHANGED
@@ -9,6 +9,7 @@ import pivFooter from './composants/gabarit/pivPiedPage.vue'
9
9
  import csqcMenu from './composants/gabarit/csqcMenu.vue'
10
10
  import csqcConfirmation from './composants/csqcConfirmation.vue'
11
11
  import csqcTable from './composants/csqcTable/csqcTable.vue'
12
+ import csqcCodeBudgetaire from './composants/codeBudgetaireGenerique.vue'
12
13
  //import csqcChaise from './composants/csqcChaise/chaiseConteneur.vue'
13
14
  import csqcAide from './composants/csqcAide.vue'
14
15
  import csqcEntete from './composants/csqcEntete.vue'
@@ -45,6 +46,7 @@ export {
45
46
  csqcTable,
46
47
  csqcTiroir,
47
48
  csqcMenu,
49
+ csqcCodeBudgetaire,
48
50
  //csqcChaise,
49
51
  pivFooter,
50
52
  pivEntete,
package/locales/en.json CHANGED
@@ -25,6 +25,19 @@
25
25
  "supprimer": "Delete",
26
26
  "televerser": "Upload"
27
27
  },
28
+ "csqcMenu": {
29
+ "deconnexion": "Logout"
30
+ },
31
+ "csqcRechercheUtilisateur": {
32
+ "ajouterUtilisateurInfo": "Vous pouvez entrer ici l'identifiant Office 365 ou le matricule de l'employé. Puis cliquez sur la loupe. Si cette information se trouve dans le système de paie, l'utilisateur sera ajouté. Notez que l'utilisateur doit avoir une adresse électronique du portail valide dans le système de paie.",
33
+ "aucunEmploi": "Aucun emploi trouvé",
34
+ "corpsEmploi": "Corps d'emploi",
35
+ "identifiantOuMatricule": "Identifiant Office 365 ou matricule",
36
+ "placeholderRechercheUtilisateur": "Rechercher un utilisateur",
37
+ "revenirRecherche": "Revenir à la recherche",
38
+ "utilisateurIntrouvable": "L'employé recherché est introuvable",
39
+ "utilisateurs": "Employé"
40
+ },
28
41
  "label": {
29
42
  "actif": "Active",
30
43
  "fermer": "Close",
@@ -34,10 +47,10 @@
34
47
  "rechercher": "Search"
35
48
  },
36
49
  "message": {
37
- "supprimerMessage": "Are you sure you want to delete {nom}?",
38
- "supprimerTitre": "Delete confirmation!",
39
50
  "chaiseSelection": "No selection for this unit",
40
- "chaiseSelectionToutes": "Please make a selection for all units."
51
+ "chaiseSelectionToutes": "Please make a selection for all units.",
52
+ "supprimerMessage": "Are you sure you want to delete {nom}?",
53
+ "supprimerTitre": "Delete confirmation!"
41
54
  },
42
55
  "pivEntete": {
43
56
  "langue": "Français"
@@ -50,4 +63,4 @@
50
63
  "selectionner": "Select"
51
64
  }
52
65
  }
53
- }
66
+ }
package/locales/fr.json CHANGED
@@ -25,15 +25,18 @@
25
25
  "supprimer": "Supprimer",
26
26
  "televerser": "Téléverser"
27
27
  },
28
+ "csqcMenu": {
29
+ "deconnexion": "Déconnexion"
30
+ },
28
31
  "csqcRechercheUtilisateur": {
29
- "ajouterUtilisateurInfo": "Vous pouvez entrer ici l'identifiant Office 365 ou le matricule de l'employé. Puis cliquez sur la loupe. Si cette information se trouve dans le système de paie, l'utilisateur sera ajouté. Notez que l'utilisateur doit avoir une adresse électronique du portail valide dans le système de paie.",
30
- "aucunEmploi": "Aucun emploi trouvé",
31
- "corpsEmploi": "Corps d'emploi",
32
- "identifiantOuMatricule": "Identifiant Office 365 ou matricule",
33
- "placeholderRechercheUtilisateur": "Rechercher un utilisateur",
34
- "revenirRecherche": "Revenir à la recherche",
35
- "utilisateurIntrouvable": "L'employé recherché est introuvable",
36
- "utilisateurs": "Employé"
32
+ "ajouterUtilisateurInfo": "You can enter the Office 365 ID or the employee number here, then click the magnifying glass. If this information exists in the payroll system, the user will be added. Note that the user must have a valid portal email address in the payroll system.",
33
+ "aucunEmploi": "No job found",
34
+ "corpsEmploi": "Job title",
35
+ "identifiantOuMatricule": "Office 365 ID or employee number",
36
+ "placeholderRechercheUtilisateur": "Search for a user",
37
+ "revenirRecherche": "Back to search",
38
+ "utilisateurIntrouvable": "The employee could not be found",
39
+ "utilisateurs": "Employee"
37
40
  },
38
41
  "label": {
39
42
  "actif": "Actif",
@@ -52,9 +55,6 @@
52
55
  "pivEntete": {
53
56
  "langue": "English"
54
57
  },
55
- "csqcMenu": {
56
- "deconnexion": "Déconnexion"
57
- },
58
58
  "pivFooter": {
59
59
  "logoAlt": ""
60
60
  },
@@ -63,4 +63,4 @@
63
63
  "selectionner": "Sélectionner"
64
64
  }
65
65
  }
66
- }
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",