codevdesign 1.0.22 → 1.0.23
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.
- package/composants/csqcAide.vue +55 -55
- package/composants/csqcAlerteErreur.vue +87 -87
- package/composants/csqcChaise/chaiseConteneur.vue +363 -363
- package/composants/csqcChaise/chaiseItem.vue +54 -54
- package/composants/csqcCodeBudgetaireGenerique.vue +280 -280
- package/composants/csqcConfirmation.vue +75 -75
- package/composants/csqcDate.vue +57 -57
- package/composants/csqcDialogue.vue +118 -118
- package/composants/csqcEntete.vue +163 -163
- package/composants/csqcImportCSV.vue +125 -125
- package/composants/csqcOptionSwitch.vue +120 -120
- package/composants/csqcRecherche.vue +212 -212
- package/composants/csqcRechercheUtilisateur.vue +197 -197
- package/composants/csqcSnackbar.vue +88 -88
- package/composants/csqcTable/csqcTable.vue +383 -383
- package/composants/csqcTable/csqcTableModaleChoixColonnes.vue +586 -586
- package/composants/csqcTexteBilingue.vue +175 -175
- package/composants/csqcTiroir.vue +156 -156
- package/composants/gabarit/csqcMenu.vue +281 -281
- package/composants/gabarit/pivEntete.vue +203 -203
- package/modeles/composants/csqcMenuModele.ts +18 -18
- package/modeles/composants/datatableColonne.ts +31 -31
- package/outils/rafraichisseurToken.ts +27 -16
- package/package.json +1 -1
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<v-toolbar
|
|
3
|
-
color="primary"
|
|
4
|
-
height="72px"
|
|
5
|
-
elevation="1"
|
|
6
|
-
>
|
|
7
|
-
<v-row
|
|
8
|
-
class="pl-6 pr-6"
|
|
9
|
-
align="center"
|
|
10
|
-
no-gutters
|
|
11
|
-
>
|
|
12
|
-
<!-- Première colonne : Logo -->
|
|
13
|
-
<v-col cols="auto">
|
|
14
|
-
<a
|
|
15
|
-
:href="href"
|
|
16
|
-
:target="cssUrlValide ? '_blank' : undefined"
|
|
17
|
-
:rel="cssUrlValide ? 'noopener noreferrer' : undefined"
|
|
18
|
-
>
|
|
19
|
-
<!-- Placeholder (même taille) pendant la décision -->
|
|
20
|
-
<div
|
|
21
|
-
v-if="!ready"
|
|
22
|
-
class="logo-placeholder"
|
|
23
|
-
></div>
|
|
24
|
-
<!-- On ne rend l'image qu'une fois la source choisie -->
|
|
25
|
-
<img
|
|
26
|
-
v-else
|
|
27
|
-
class="logo-img"
|
|
28
|
-
id="pivImage"
|
|
29
|
-
:src="currentSrc!"
|
|
30
|
-
:alt="$t('csqc.pivFooter.logoAlt')"
|
|
31
|
-
decoding="async"
|
|
32
|
-
loading="eager"
|
|
33
|
-
@error="ErreurLogo"
|
|
34
|
-
/>
|
|
35
|
-
</a>
|
|
36
|
-
</v-col>
|
|
37
|
-
|
|
38
|
-
<!-- Colonne pour le nom de l'application (Pour le mode desktop) -->
|
|
39
|
-
<v-col
|
|
40
|
-
v-if="!estMobile"
|
|
41
|
-
class="d-flex justify-center"
|
|
42
|
-
>
|
|
43
|
-
<v-app-bar-title
|
|
44
|
-
class="pl-12 ml-12"
|
|
45
|
-
style="font-size: 16px !important"
|
|
46
|
-
>
|
|
47
|
-
{{ formulaireNom }}
|
|
48
|
-
</v-app-bar-title>
|
|
49
|
-
</v-col>
|
|
50
|
-
|
|
51
|
-
<!-- Colonne pour le bouton de langue et icône d'aide -->
|
|
52
|
-
<v-col class="d-none d-flex justify-end">
|
|
53
|
-
<!-- langue -->
|
|
54
|
-
<v-btn
|
|
55
|
-
variant="text"
|
|
56
|
-
@click="enregistrerLangue()"
|
|
57
|
-
>{{ $t('csqc.pivEntete.langue') }}
|
|
58
|
-
</v-btn>
|
|
59
|
-
|
|
60
|
-
<!-- icône d'aide si dispo -->
|
|
61
|
-
<v-btn
|
|
62
|
-
v-if="aideUrl"
|
|
63
|
-
icon="mdi-help-circle-outline"
|
|
64
|
-
:href="aideUrl"
|
|
65
|
-
target="_blank"
|
|
66
|
-
rel="noopener noreferrer"
|
|
67
|
-
style="margin-top: -6px"
|
|
68
|
-
>
|
|
69
|
-
</v-btn>
|
|
70
|
-
</v-col>
|
|
71
|
-
|
|
72
|
-
<!-- Colonne pour le nom de l'application (Pour le mode mobile) -->
|
|
73
|
-
<v-col
|
|
74
|
-
v-if="props.estMobile"
|
|
75
|
-
cols="12"
|
|
76
|
-
>
|
|
77
|
-
<v-app-bar-title style="font-size: 16px !important">
|
|
78
|
-
{{ formulaireNom }}
|
|
79
|
-
</v-app-bar-title>
|
|
80
|
-
</v-col>
|
|
81
|
-
</v-row>
|
|
82
|
-
</v-toolbar>
|
|
83
|
-
</template>
|
|
84
|
-
|
|
85
|
-
<script setup lang="ts">
|
|
86
|
-
import { ref, watch, computed } from 'vue'
|
|
87
|
-
import { useLocale } from 'vuetify'
|
|
88
|
-
import { useI18n } from 'vue-i18n'
|
|
89
|
-
|
|
90
|
-
const { current } = useLocale()
|
|
91
|
-
const props = defineProps({
|
|
92
|
-
estMobile: { type: Boolean, default: false },
|
|
93
|
-
urlBase: { type: String, required: true },
|
|
94
|
-
aideUrl: { type: String, default: '' },
|
|
95
|
-
cssUrl: { type: String, default: '' },
|
|
96
|
-
logoUrl: { type: String, default: '' },
|
|
97
|
-
lienLogo: { type: String, default: '' },
|
|
98
|
-
formulaireId: { type: Number, default: 0 },
|
|
99
|
-
formulaireNom: { type: String, default: '' },
|
|
100
|
-
})
|
|
101
|
-
const emit = defineEmits(['changementLangue'])
|
|
102
|
-
const { t } = useI18n()
|
|
103
|
-
|
|
104
|
-
const FALLBACK = '/images/QUEBEC_blanc.svg'
|
|
105
|
-
const currentSrc = ref<string | null>(null) // pas d'image tant que null
|
|
106
|
-
const ready = ref(false)
|
|
107
|
-
|
|
108
|
-
const formulaireNom = computed(() => {
|
|
109
|
-
return props.formulaireNom || t('nom_application')
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const href = computed(() => (props.cssUrl?.trim() ? props.cssUrl.trim() : '/'))
|
|
113
|
-
const cssUrlValide = computed(() => /^https?:\/\//i.test(href.value))
|
|
114
|
-
|
|
115
|
-
let loadToken = 0 // ← identifiant de “requête” pour annuler logiquement
|
|
116
|
-
|
|
117
|
-
// Sur changement de l’URL du logo, on tente de le charger, sinon fallback apres 3.5 secs
|
|
118
|
-
watch(
|
|
119
|
-
() => props.logoUrl,
|
|
120
|
-
async (nouvelle, ancienne) => {
|
|
121
|
-
const url = (nouvelle ?? '').trim()
|
|
122
|
-
|
|
123
|
-
// même URL → ne rien faire
|
|
124
|
-
if (url === (ancienne ?? '').trim() && currentSrc.value !== null) return
|
|
125
|
-
|
|
126
|
-
// pas d’URL → fallback immédiat
|
|
127
|
-
if (!url) {
|
|
128
|
-
currentSrc.value = FALLBACK
|
|
129
|
-
ready.value = true
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// nouvelle tentative (avec “annulation logique”)
|
|
134
|
-
const token = ++loadToken
|
|
135
|
-
ready.value = false
|
|
136
|
-
const ok = await loadWithTimeout(url, 3500)
|
|
137
|
-
if (token !== loadToken) return // une nouvelle tentative a démarré entre-temps
|
|
138
|
-
|
|
139
|
-
currentSrc.value = ok ? url : FALLBACK
|
|
140
|
-
ready.value = true
|
|
141
|
-
},
|
|
142
|
-
{ immediate: true },
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
// Charge une image avec un timeout
|
|
146
|
-
function loadWithTimeout(url: string, timeoutMs: number): Promise<boolean> {
|
|
147
|
-
return new Promise<boolean>(resolve => {
|
|
148
|
-
const img = new Image()
|
|
149
|
-
const timer = setTimeout(() => {
|
|
150
|
-
// trop long → on abandonne
|
|
151
|
-
img.src = '' // stoppe le chargement
|
|
152
|
-
resolve(false)
|
|
153
|
-
}, timeoutMs)
|
|
154
|
-
|
|
155
|
-
img.onload = () => {
|
|
156
|
-
clearTimeout(timer)
|
|
157
|
-
resolve(true)
|
|
158
|
-
}
|
|
159
|
-
img.onerror = () => {
|
|
160
|
-
clearTimeout(timer)
|
|
161
|
-
resolve(false)
|
|
162
|
-
}
|
|
163
|
-
img.src = url
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Si l'image choisie a un problème, switch sur le fallback
|
|
168
|
-
function ErreurLogo() {
|
|
169
|
-
if (currentSrc.value !== FALLBACK) currentSrc.value = FALLBACK
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const enregistrerLangue = (): void => {
|
|
173
|
-
const langueDispo: string = current.value === 'fr' ? 'en' : 'fr'
|
|
174
|
-
let returnUrl = window.location.pathname + window.location.search
|
|
175
|
-
if (import.meta.env.MODE === 'development') {
|
|
176
|
-
returnUrl = '/'
|
|
177
|
-
}
|
|
178
|
-
window.location.href =
|
|
179
|
-
props.urlBase + `/Traducteur/SetLanguage?culture=${langueDispo}&returnUrl=${encodeURIComponent(returnUrl)}`
|
|
180
|
-
emit('changementLangue')
|
|
181
|
-
}
|
|
182
|
-
</script>
|
|
183
|
-
|
|
184
|
-
<style scoped>
|
|
185
|
-
.container {
|
|
186
|
-
max-width: none !important;
|
|
187
|
-
}
|
|
188
|
-
.theme--light.v-app-bar.v-toolbar.v-sheet {
|
|
189
|
-
background: #095797;
|
|
190
|
-
|
|
191
|
-
color: #fff;
|
|
192
|
-
}
|
|
193
|
-
.logo-placeholder {
|
|
194
|
-
height: 72px;
|
|
195
|
-
width: 180px;
|
|
196
|
-
}
|
|
197
|
-
.logo-img {
|
|
198
|
-
height: 72px;
|
|
199
|
-
transition: opacity 0.15s;
|
|
200
|
-
opacity: 1;
|
|
201
|
-
display: block;
|
|
202
|
-
}
|
|
203
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<v-toolbar
|
|
3
|
+
color="primary"
|
|
4
|
+
height="72px"
|
|
5
|
+
elevation="1"
|
|
6
|
+
>
|
|
7
|
+
<v-row
|
|
8
|
+
class="pl-6 pr-6"
|
|
9
|
+
align="center"
|
|
10
|
+
no-gutters
|
|
11
|
+
>
|
|
12
|
+
<!-- Première colonne : Logo -->
|
|
13
|
+
<v-col cols="auto">
|
|
14
|
+
<a
|
|
15
|
+
:href="href"
|
|
16
|
+
:target="cssUrlValide ? '_blank' : undefined"
|
|
17
|
+
:rel="cssUrlValide ? 'noopener noreferrer' : undefined"
|
|
18
|
+
>
|
|
19
|
+
<!-- Placeholder (même taille) pendant la décision -->
|
|
20
|
+
<div
|
|
21
|
+
v-if="!ready"
|
|
22
|
+
class="logo-placeholder"
|
|
23
|
+
></div>
|
|
24
|
+
<!-- On ne rend l'image qu'une fois la source choisie -->
|
|
25
|
+
<img
|
|
26
|
+
v-else
|
|
27
|
+
class="logo-img"
|
|
28
|
+
id="pivImage"
|
|
29
|
+
:src="currentSrc!"
|
|
30
|
+
:alt="$t('csqc.pivFooter.logoAlt')"
|
|
31
|
+
decoding="async"
|
|
32
|
+
loading="eager"
|
|
33
|
+
@error="ErreurLogo"
|
|
34
|
+
/>
|
|
35
|
+
</a>
|
|
36
|
+
</v-col>
|
|
37
|
+
|
|
38
|
+
<!-- Colonne pour le nom de l'application (Pour le mode desktop) -->
|
|
39
|
+
<v-col
|
|
40
|
+
v-if="!estMobile"
|
|
41
|
+
class="d-flex justify-center"
|
|
42
|
+
>
|
|
43
|
+
<v-app-bar-title
|
|
44
|
+
class="pl-12 ml-12"
|
|
45
|
+
style="font-size: 16px !important"
|
|
46
|
+
>
|
|
47
|
+
{{ formulaireNom }}
|
|
48
|
+
</v-app-bar-title>
|
|
49
|
+
</v-col>
|
|
50
|
+
|
|
51
|
+
<!-- Colonne pour le bouton de langue et icône d'aide -->
|
|
52
|
+
<v-col class="d-none d-flex justify-end">
|
|
53
|
+
<!-- langue -->
|
|
54
|
+
<v-btn
|
|
55
|
+
variant="text"
|
|
56
|
+
@click="enregistrerLangue()"
|
|
57
|
+
>{{ $t('csqc.pivEntete.langue') }}
|
|
58
|
+
</v-btn>
|
|
59
|
+
|
|
60
|
+
<!-- icône d'aide si dispo -->
|
|
61
|
+
<v-btn
|
|
62
|
+
v-if="aideUrl"
|
|
63
|
+
icon="mdi-help-circle-outline"
|
|
64
|
+
:href="aideUrl"
|
|
65
|
+
target="_blank"
|
|
66
|
+
rel="noopener noreferrer"
|
|
67
|
+
style="margin-top: -6px"
|
|
68
|
+
>
|
|
69
|
+
</v-btn>
|
|
70
|
+
</v-col>
|
|
71
|
+
|
|
72
|
+
<!-- Colonne pour le nom de l'application (Pour le mode mobile) -->
|
|
73
|
+
<v-col
|
|
74
|
+
v-if="props.estMobile"
|
|
75
|
+
cols="12"
|
|
76
|
+
>
|
|
77
|
+
<v-app-bar-title style="font-size: 16px !important">
|
|
78
|
+
{{ formulaireNom }}
|
|
79
|
+
</v-app-bar-title>
|
|
80
|
+
</v-col>
|
|
81
|
+
</v-row>
|
|
82
|
+
</v-toolbar>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<script setup lang="ts">
|
|
86
|
+
import { ref, watch, computed } from 'vue'
|
|
87
|
+
import { useLocale } from 'vuetify'
|
|
88
|
+
import { useI18n } from 'vue-i18n'
|
|
89
|
+
|
|
90
|
+
const { current } = useLocale()
|
|
91
|
+
const props = defineProps({
|
|
92
|
+
estMobile: { type: Boolean, default: false },
|
|
93
|
+
urlBase: { type: String, required: true },
|
|
94
|
+
aideUrl: { type: String, default: '' },
|
|
95
|
+
cssUrl: { type: String, default: '' },
|
|
96
|
+
logoUrl: { type: String, default: '' },
|
|
97
|
+
lienLogo: { type: String, default: '' },
|
|
98
|
+
formulaireId: { type: Number, default: 0 },
|
|
99
|
+
formulaireNom: { type: String, default: '' },
|
|
100
|
+
})
|
|
101
|
+
const emit = defineEmits(['changementLangue'])
|
|
102
|
+
const { t } = useI18n()
|
|
103
|
+
|
|
104
|
+
const FALLBACK = '/images/QUEBEC_blanc.svg'
|
|
105
|
+
const currentSrc = ref<string | null>(null) // pas d'image tant que null
|
|
106
|
+
const ready = ref(false)
|
|
107
|
+
|
|
108
|
+
const formulaireNom = computed(() => {
|
|
109
|
+
return props.formulaireNom || t('nom_application')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const href = computed(() => (props.cssUrl?.trim() ? props.cssUrl.trim() : '/'))
|
|
113
|
+
const cssUrlValide = computed(() => /^https?:\/\//i.test(href.value))
|
|
114
|
+
|
|
115
|
+
let loadToken = 0 // ← identifiant de “requête” pour annuler logiquement
|
|
116
|
+
|
|
117
|
+
// Sur changement de l’URL du logo, on tente de le charger, sinon fallback apres 3.5 secs
|
|
118
|
+
watch(
|
|
119
|
+
() => props.logoUrl,
|
|
120
|
+
async (nouvelle, ancienne) => {
|
|
121
|
+
const url = (nouvelle ?? '').trim()
|
|
122
|
+
|
|
123
|
+
// même URL → ne rien faire
|
|
124
|
+
if (url === (ancienne ?? '').trim() && currentSrc.value !== null) return
|
|
125
|
+
|
|
126
|
+
// pas d’URL → fallback immédiat
|
|
127
|
+
if (!url) {
|
|
128
|
+
currentSrc.value = FALLBACK
|
|
129
|
+
ready.value = true
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// nouvelle tentative (avec “annulation logique”)
|
|
134
|
+
const token = ++loadToken
|
|
135
|
+
ready.value = false
|
|
136
|
+
const ok = await loadWithTimeout(url, 3500)
|
|
137
|
+
if (token !== loadToken) return // une nouvelle tentative a démarré entre-temps
|
|
138
|
+
|
|
139
|
+
currentSrc.value = ok ? url : FALLBACK
|
|
140
|
+
ready.value = true
|
|
141
|
+
},
|
|
142
|
+
{ immediate: true },
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
// Charge une image avec un timeout
|
|
146
|
+
function loadWithTimeout(url: string, timeoutMs: number): Promise<boolean> {
|
|
147
|
+
return new Promise<boolean>(resolve => {
|
|
148
|
+
const img = new Image()
|
|
149
|
+
const timer = setTimeout(() => {
|
|
150
|
+
// trop long → on abandonne
|
|
151
|
+
img.src = '' // stoppe le chargement
|
|
152
|
+
resolve(false)
|
|
153
|
+
}, timeoutMs)
|
|
154
|
+
|
|
155
|
+
img.onload = () => {
|
|
156
|
+
clearTimeout(timer)
|
|
157
|
+
resolve(true)
|
|
158
|
+
}
|
|
159
|
+
img.onerror = () => {
|
|
160
|
+
clearTimeout(timer)
|
|
161
|
+
resolve(false)
|
|
162
|
+
}
|
|
163
|
+
img.src = url
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Si l'image choisie a un problème, switch sur le fallback
|
|
168
|
+
function ErreurLogo() {
|
|
169
|
+
if (currentSrc.value !== FALLBACK) currentSrc.value = FALLBACK
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const enregistrerLangue = (): void => {
|
|
173
|
+
const langueDispo: string = current.value === 'fr' ? 'en' : 'fr'
|
|
174
|
+
let returnUrl = window.location.pathname + window.location.search
|
|
175
|
+
if (import.meta.env.MODE === 'development') {
|
|
176
|
+
returnUrl = '/'
|
|
177
|
+
}
|
|
178
|
+
window.location.href =
|
|
179
|
+
props.urlBase + `/Traducteur/SetLanguage?culture=${langueDispo}&returnUrl=${encodeURIComponent(returnUrl)}`
|
|
180
|
+
emit('changementLangue')
|
|
181
|
+
}
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<style scoped>
|
|
185
|
+
.container {
|
|
186
|
+
max-width: none !important;
|
|
187
|
+
}
|
|
188
|
+
.theme--light.v-app-bar.v-toolbar.v-sheet {
|
|
189
|
+
background: #095797;
|
|
190
|
+
|
|
191
|
+
color: #fff;
|
|
192
|
+
}
|
|
193
|
+
.logo-placeholder {
|
|
194
|
+
height: 72px;
|
|
195
|
+
width: 180px;
|
|
196
|
+
}
|
|
197
|
+
.logo-img {
|
|
198
|
+
height: 72px;
|
|
199
|
+
transition: opacity 0.15s;
|
|
200
|
+
opacity: 1;
|
|
201
|
+
display: block;
|
|
202
|
+
}
|
|
203
|
+
</style>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
export interface MenuItem {
|
|
2
|
-
nom: string
|
|
3
|
-
path: string
|
|
4
|
-
droit: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface SousListe {
|
|
8
|
-
droit: string
|
|
9
|
-
id: number
|
|
10
|
-
nom: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface SousListeItems {
|
|
14
|
-
parentId: number
|
|
15
|
-
nom: string
|
|
16
|
-
path: string
|
|
17
|
-
droit: string
|
|
18
|
-
}
|
|
1
|
+
export interface MenuItem {
|
|
2
|
+
nom: string
|
|
3
|
+
path: string
|
|
4
|
+
droit: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface SousListe {
|
|
8
|
+
droit: string
|
|
9
|
+
id: number
|
|
10
|
+
nom: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SousListeItems {
|
|
14
|
+
parentId: number
|
|
15
|
+
nom: string
|
|
16
|
+
path: string
|
|
17
|
+
droit: string
|
|
18
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import type { DataTableCompareFunction, DataTableHeader, FilterFunction } from 'vuetify'
|
|
2
|
-
import { HeaderCellProps } from 'vuetify/lib/components/VDataTable/types.mjs';
|
|
3
|
-
import { SelectItemKey } from 'vuetify/lib/util/helpers.mjs';
|
|
4
|
-
|
|
5
|
-
class datatableColonne<T = any> implements DataTableHeader<T> {
|
|
6
|
-
key?: 'data-table-group' | 'data-table-select' | 'data-table-expand' | (string & {});
|
|
7
|
-
value?: SelectItemKey<T>;
|
|
8
|
-
title?: string;
|
|
9
|
-
fixed?: boolean | 'start' | 'end';
|
|
10
|
-
align?: 'start' | 'end' | 'center';
|
|
11
|
-
width?: number | string;
|
|
12
|
-
minWidth?: number | string;
|
|
13
|
-
maxWidth?: number | string;
|
|
14
|
-
nowrap?: boolean;
|
|
15
|
-
intent?: number;
|
|
16
|
-
headerProps?: Record<string, any>;
|
|
17
|
-
cellProps?: HeaderCellProps;
|
|
18
|
-
sortable?: boolean;
|
|
19
|
-
sort?: DataTableCompareFunction;
|
|
20
|
-
sortRaw?: DataTableCompareFunction;
|
|
21
|
-
filter?: FilterFunction;
|
|
22
|
-
children?: DataTableHeader<T>[];
|
|
23
|
-
|
|
24
|
-
constructor(title: string, key: string) {
|
|
25
|
-
this.title = title
|
|
26
|
-
this.key = key
|
|
27
|
-
this.align = key === 'action' ? 'end' : key === 'estActif' ? 'center' : 'start'
|
|
28
|
-
this.sortable = key !== 'action'
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
export default datatableColonne
|
|
1
|
+
import type { DataTableCompareFunction, DataTableHeader, FilterFunction } from 'vuetify'
|
|
2
|
+
import { HeaderCellProps } from 'vuetify/lib/components/VDataTable/types.mjs';
|
|
3
|
+
import { SelectItemKey } from 'vuetify/lib/util/helpers.mjs';
|
|
4
|
+
|
|
5
|
+
class datatableColonne<T = any> implements DataTableHeader<T> {
|
|
6
|
+
key?: 'data-table-group' | 'data-table-select' | 'data-table-expand' | (string & {});
|
|
7
|
+
value?: SelectItemKey<T>;
|
|
8
|
+
title?: string;
|
|
9
|
+
fixed?: boolean | 'start' | 'end';
|
|
10
|
+
align?: 'start' | 'end' | 'center';
|
|
11
|
+
width?: number | string;
|
|
12
|
+
minWidth?: number | string;
|
|
13
|
+
maxWidth?: number | string;
|
|
14
|
+
nowrap?: boolean;
|
|
15
|
+
intent?: number;
|
|
16
|
+
headerProps?: Record<string, any>;
|
|
17
|
+
cellProps?: HeaderCellProps;
|
|
18
|
+
sortable?: boolean;
|
|
19
|
+
sort?: DataTableCompareFunction;
|
|
20
|
+
sortRaw?: DataTableCompareFunction;
|
|
21
|
+
filter?: FilterFunction;
|
|
22
|
+
children?: DataTableHeader<T>[];
|
|
23
|
+
|
|
24
|
+
constructor(title: string, key: string) {
|
|
25
|
+
this.title = title
|
|
26
|
+
this.key = key
|
|
27
|
+
this.align = key === 'action' ? 'end' : key === 'estActif' ? 'center' : 'start'
|
|
28
|
+
this.sortable = key !== 'action'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export default datatableColonne
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
class RafraichisseurToken {
|
|
3
2
|
private intervalleEnSecondes = 15
|
|
4
3
|
private secondesAvantExpirationTokenPourRafraichir = 20
|
|
@@ -7,12 +6,12 @@ class RafraichisseurToken {
|
|
|
7
6
|
private timerId: number | null = null
|
|
8
7
|
|
|
9
8
|
// Lance une seule fois
|
|
10
|
-
public async demarrer(
|
|
11
|
-
if (
|
|
12
|
-
await this.
|
|
9
|
+
public async demarrer(nomTemoin: string | null, urlPortail: string): Promise<void> {
|
|
10
|
+
if (nomTemoin == null || nomTemoin === '') nomTemoin = 'csqc_jeton_secure_expiration'
|
|
11
|
+
await this.verifierJeton(nomTemoin, urlPortail)
|
|
13
12
|
if (this.timerId != null) return
|
|
14
13
|
this.timerId = window.setInterval(() => {
|
|
15
|
-
this.
|
|
14
|
+
this.verifierJeton(nomTemoin, urlPortail)
|
|
16
15
|
}, this.intervalleEnSecondes * 1000)
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -24,35 +23,48 @@ class RafraichisseurToken {
|
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
public existeJeton = (nomTemoin: string) => {
|
|
27
|
+
return this.estJetonValide(nomTemoin)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async verifierJeton(nomTemoin: string, urlPortail: string): Promise<void> {
|
|
31
|
+
if (this.popupAffiche) return
|
|
28
32
|
|
|
29
|
-
if (this.
|
|
30
|
-
|
|
31
|
-
if (!tokenEncode) {
|
|
32
|
-
await this.rafraichir(nomCookie, urlPortail)
|
|
33
|
+
if (!this.estJetonValide(nomTemoin)) {
|
|
34
|
+
this.rafraichir(nomTemoin, urlPortail)
|
|
33
35
|
return
|
|
34
36
|
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private estJetonValide(nomTemoin: string): boolean {
|
|
40
|
+
if (this.popupAffiche || !nomTemoin) return true //On fait semblant que c'est valide pour ne pas provoquer un autre affichage du popup.
|
|
41
|
+
|
|
42
|
+
const tokenEncode = this.lireCookie(nomTemoin)
|
|
43
|
+
if (!tokenEncode) {
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
35
46
|
|
|
36
47
|
let token: any
|
|
37
48
|
try {
|
|
38
49
|
token = this.parseJwt(tokenEncode)
|
|
39
50
|
} catch {
|
|
40
|
-
|
|
41
|
-
return
|
|
51
|
+
return false
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
const exp = Number(token?.exp)
|
|
45
55
|
if (!Number.isFinite(exp)) {
|
|
46
56
|
// exp manquant/invalide → tente refresh
|
|
47
|
-
|
|
48
|
-
return
|
|
57
|
+
|
|
58
|
+
return false
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
const now = Math.floor(Date.now() / 1000)
|
|
52
62
|
const refreshAt = exp - this.secondesAvantExpirationTokenPourRafraichir - this.skewSeconds
|
|
53
63
|
if (now >= refreshAt) {
|
|
54
|
-
|
|
64
|
+
return false
|
|
55
65
|
}
|
|
66
|
+
|
|
67
|
+
return true
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
private async rafraichir(nomCookie: string, urlPortail: string): Promise<void> {
|
|
@@ -132,7 +144,6 @@ class RafraichisseurToken {
|
|
|
132
144
|
window.location.replace(`${urlPortail}deconnecte?urlRetour=${encodeURIComponent(window.location.href)}`)
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
|
|
136
147
|
private lireCookie(nom: string): string | null {
|
|
137
148
|
if (!document.cookie) return null
|
|
138
149
|
const cookies = document.cookie.split(';').map(c => c.trim())
|