codevdesign 1.0.43 → 1.0.44
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/csqcDialogue.vue +118 -118
- package/composants/csqcTable/csqcTable.vue +223 -70
- package/composants/csqcTable/csqcTableModaleChoixColonnes.vue +728 -483
- package/composants/csqcTable/sortableDataTable.ts +24 -0
- package/composants/csqcTiroir.vue +156 -156
- package/locales/fr.json +14 -0
- package/package.json +2 -2
|
@@ -1,565 +1,791 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<csqc-modale
|
|
3
3
|
ref="modale"
|
|
4
|
-
:titre="
|
|
5
|
-
:
|
|
6
|
-
:
|
|
7
|
-
width="
|
|
8
|
-
|
|
9
|
-
@
|
|
4
|
+
:titre="t('csqc.csqcDatatable.choixColonnes.titre')"
|
|
5
|
+
:btn-annuler="!sauvegardeEnCours"
|
|
6
|
+
:largeur="'80vw'"
|
|
7
|
+
:max-width="'1000px'"
|
|
8
|
+
@annuler="fermer"
|
|
9
|
+
@ok="sauvegarder"
|
|
10
10
|
>
|
|
11
|
+
<!-- Erreurs -->
|
|
11
12
|
<v-alert
|
|
12
13
|
v-model="afficherErreur"
|
|
13
14
|
type="error"
|
|
14
15
|
closable
|
|
15
|
-
>{{ erreur }}</v-alert
|
|
16
16
|
>
|
|
17
|
+
{{ erreur }}
|
|
18
|
+
</v-alert>
|
|
19
|
+
|
|
17
20
|
<v-row>
|
|
18
21
|
<v-col
|
|
19
22
|
cols="12"
|
|
20
23
|
class="pa-0 relative"
|
|
21
24
|
>
|
|
25
|
+
<!-- Retour en haut (table des vues) -->
|
|
22
26
|
<v-btn
|
|
23
27
|
v-show="choixRetourEnHaut"
|
|
24
28
|
class="BarreRechercheBackIcone retourHautChoix"
|
|
25
|
-
|
|
26
|
-
absolute
|
|
29
|
+
icon
|
|
27
30
|
color="primary"
|
|
31
|
+
style="position: absolute"
|
|
28
32
|
@click="retourEnHaut('#choixColonnes-choix > .v-data-table__wrapper')"
|
|
29
33
|
>
|
|
30
34
|
<v-icon>mdi-arrow-up</v-icon>
|
|
31
35
|
</v-btn>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class="
|
|
36
|
-
:headers="colonnesChoix"
|
|
37
|
-
item-key="nomVue"
|
|
38
|
-
:items="choix"
|
|
39
|
-
single-expand
|
|
40
|
-
disable-pagination
|
|
41
|
-
hide-default-footer
|
|
42
|
-
fixed-header
|
|
43
|
-
@click:row="cliquer"
|
|
36
|
+
|
|
37
|
+
<v-card
|
|
38
|
+
rounded
|
|
39
|
+
class="pr-0"
|
|
44
40
|
>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
>
|
|
62
|
-
<v-
|
|
63
|
-
|
|
64
|
-
|
|
41
|
+
<!-- TABLE 1 : Liste des vues -->
|
|
42
|
+
<v-data-table
|
|
43
|
+
id="choixColonnes-choix"
|
|
44
|
+
class="limiteHauteurChoix"
|
|
45
|
+
:headers="colonnesChoix"
|
|
46
|
+
:items="choix"
|
|
47
|
+
:item-value="row => choixOf(row)._id"
|
|
48
|
+
v-model:expanded="expandedChoixIds"
|
|
49
|
+
show-expand
|
|
50
|
+
:single-expand="true"
|
|
51
|
+
:items-per-page="-1"
|
|
52
|
+
hide-default-footer
|
|
53
|
+
fixed-header
|
|
54
|
+
@click:row="onClickRowChoix"
|
|
55
|
+
>
|
|
56
|
+
<!-- Bouton ajouter une vue -->
|
|
57
|
+
<template #header.action>
|
|
58
|
+
<v-btn
|
|
59
|
+
color="primary"
|
|
60
|
+
class="float-right"
|
|
61
|
+
size="small"
|
|
62
|
+
:disabled="desactiverAjout"
|
|
63
|
+
@click.stop="ajouter"
|
|
65
64
|
>
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
{{ t('csqc.bouton.ajouter') }}
|
|
66
|
+
</v-btn>
|
|
67
|
+
</template>
|
|
68
|
+
<template v-slot:item.data-table-expand="{ internalItem, isExpanded, toggleExpand }">
|
|
69
|
+
<v-btn
|
|
70
|
+
:append-icon="isExpanded(internalItem) ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
|
71
|
+
:text="isExpanded(internalItem) ? 'Fermer' : 'Ouvrir'"
|
|
72
|
+
class="text-none"
|
|
73
|
+
color="medium-emphasis"
|
|
74
|
+
size="small"
|
|
75
|
+
variant="text"
|
|
76
|
+
width="105"
|
|
77
|
+
border
|
|
78
|
+
slim
|
|
79
|
+
data-expander
|
|
80
|
+
@click.stop="() => toggleExpand(internalItem)"
|
|
81
|
+
></v-btn>
|
|
82
|
+
</template>
|
|
83
|
+
<!-- Expansion : Table des colonnes -->
|
|
84
|
+
<template #expanded-row="{ columns }">
|
|
85
|
+
<tr>
|
|
86
|
+
<td
|
|
87
|
+
:colspan="columns.length"
|
|
68
88
|
class="pa-0 ma-0"
|
|
69
89
|
>
|
|
70
|
-
<v-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
location="bottom right"
|
|
74
|
-
absolute
|
|
75
|
-
color="primary"
|
|
76
|
-
@click="retourEnHaut('#choixColonnes-vue > .v-data-table__wrapper')"
|
|
90
|
+
<v-row
|
|
91
|
+
class="pa-0 ma-0"
|
|
92
|
+
no-gutters
|
|
77
93
|
>
|
|
78
|
-
<v-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
id="choixColonnes-vue"
|
|
82
|
-
v-model:sort-by="triColonnesChoix"
|
|
83
|
-
v-sortable-data-table
|
|
84
|
-
class="limiteHauteurChoix colonnes mt-1 mb-4 ml-2 ordonable"
|
|
85
|
-
:headers="colonnesChoixColonne"
|
|
86
|
-
:items="colonnesEnCours"
|
|
87
|
-
item-key="value"
|
|
88
|
-
hide-default-footer
|
|
89
|
-
disable-pagination
|
|
90
|
-
fixed-header
|
|
91
|
-
:style="`margin-right: ${90 - $vuetify.breakpoint.scrollBarWidth}px;`"
|
|
92
|
-
@sorted="changeOrdre"
|
|
93
|
-
>
|
|
94
|
-
<template
|
|
95
|
-
v-if="choixEnCours[0]?.colonnes.length <= 0"
|
|
96
|
-
#header.action
|
|
94
|
+
<v-col
|
|
95
|
+
cols="12"
|
|
96
|
+
class="pa-0 ma-0"
|
|
97
97
|
>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
<!-- Retour en haut (table des colonnes) -->
|
|
99
|
+
<v-btn
|
|
100
|
+
v-show="colonnesRetourEnHaut"
|
|
101
|
+
class="BarreRechercheBackIcone retourHautChoix colonnes"
|
|
102
|
+
icon
|
|
103
|
+
color="primary"
|
|
104
|
+
style="position: absolute"
|
|
105
|
+
@click="retourEnHaut('#choixColonnes-vue > .v-data-table__wrapper')"
|
|
101
106
|
>
|
|
102
|
-
<
|
|
103
|
-
|
|
107
|
+
<v-icon>mdi-arrow-up</v-icon>
|
|
108
|
+
</v-btn>
|
|
109
|
+
|
|
110
|
+
<!-- TABLE 2 : Colonnes de la vue sélectionnée -->
|
|
111
|
+
<v-data-table
|
|
112
|
+
id="choixColonnes-vue"
|
|
113
|
+
class="limiteHauteurChoix colonnes mt-1 mb-4 ordonable"
|
|
114
|
+
:headers="colonnesChoixColonne"
|
|
115
|
+
:items="colonnesEnCours"
|
|
116
|
+
hover
|
|
117
|
+
item-value="value"
|
|
118
|
+
hide-default-footer
|
|
119
|
+
:items-per-page="-1"
|
|
120
|
+
fixed-header
|
|
121
|
+
:sort-by="triColonnesChoix"
|
|
122
|
+
v-sortable-data-table
|
|
123
|
+
@sorted="changeOrdre"
|
|
124
|
+
>
|
|
125
|
+
<!-- Alerte dans l'entête si aucune colonne n'est sélectionnée -->
|
|
126
|
+
<template #header.action>
|
|
127
|
+
<v-tooltip
|
|
128
|
+
v-if="nbColonnesSelectionnees <= 0"
|
|
129
|
+
location="start"
|
|
104
130
|
color="warning"
|
|
105
|
-
v-bind="props"
|
|
106
|
-
>mdi-alert</v-icon
|
|
107
131
|
>
|
|
132
|
+
<template #activator="{ props }">
|
|
133
|
+
<v-icon
|
|
134
|
+
color="warning"
|
|
135
|
+
v-bind="props"
|
|
136
|
+
>
|
|
137
|
+
mdi-alert
|
|
138
|
+
</v-icon>
|
|
139
|
+
</template>
|
|
140
|
+
{{ t('csqc.csqcDatatable.choixColonnes.activezUneColonne') }}
|
|
141
|
+
</v-tooltip>
|
|
108
142
|
</template>
|
|
109
|
-
{{ $tc('csqc-table.choixColonnes.activezUneColonne') }}
|
|
110
|
-
</v-tooltip>
|
|
111
|
-
</template>
|
|
112
143
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<v-text-field
|
|
133
|
-
v-if="item.nomVue === vueEnEdition"
|
|
134
|
-
v-model.trim="nomVueEnCours"
|
|
135
|
-
:label="$tc('csqc-table.choixColonnes.nomVue')"
|
|
136
|
-
single-line
|
|
137
|
-
density="compact"
|
|
138
|
-
autofocus
|
|
139
|
-
class="py-0 my-0"
|
|
140
|
-
:rules="regles.nomVue"
|
|
141
|
-
hide-details
|
|
142
|
-
@keydown.enter="accepterEdition"
|
|
143
|
-
@keydown.esc.stop="annulerEdition"
|
|
144
|
-
></v-text-field>
|
|
145
|
-
<span
|
|
146
|
-
v-else
|
|
147
|
-
:class="{ 'error--text': !item.nomVue }"
|
|
148
|
-
>
|
|
149
|
-
{{ item.nomVue || $tc('csqc-table.choixColonnes.nomVueRequis') }}
|
|
150
|
-
</span>
|
|
151
|
-
</template>
|
|
152
|
-
<template #item.action="{ item }">
|
|
153
|
-
<template v-if="item.nomVue === vueEnEdition">
|
|
154
|
-
<v-btn
|
|
155
|
-
icon
|
|
156
|
-
@click.stop.prevent="annulerEdition"
|
|
157
|
-
><v-icon class="iconeSupprimer">mdi-window-close</v-icon></v-btn
|
|
158
|
-
>
|
|
159
|
-
<v-btn
|
|
160
|
-
icon
|
|
161
|
-
@click.stop.prevent="accepterEdition"
|
|
162
|
-
><v-icon class="couleurHover">mdi-check</v-icon></v-btn
|
|
163
|
-
>
|
|
144
|
+
<!-- Toggle (oeil) -->
|
|
145
|
+
<template #item.action="{ item }">
|
|
146
|
+
<v-icon
|
|
147
|
+
:color="couleurColonneCliquee(colonneOf(item))"
|
|
148
|
+
@click.stop="basculeColonneClique(colonneOf(item))"
|
|
149
|
+
>
|
|
150
|
+
{{ colonneEstClique(colonneOf(item)) ? 'mdi-eye' : 'mdi-eye-off' }}
|
|
151
|
+
</v-icon>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<!-- Label de colonne -->
|
|
155
|
+
<template #item.title="{ value, item }">
|
|
156
|
+
{{ value ?? colonneOf(item).value }}
|
|
157
|
+
</template>
|
|
158
|
+
</v-data-table>
|
|
159
|
+
</v-col>
|
|
160
|
+
</v-row>
|
|
161
|
+
</td>
|
|
162
|
+
</tr>
|
|
164
163
|
</template>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
164
|
+
|
|
165
|
+
<!-- Nom de vue (édition inline) -->
|
|
166
|
+
<template #item.nomVue="{ item }">
|
|
167
|
+
<v-text-field
|
|
168
|
+
v-if="choixOf(item).nomVue === vueEnEdition"
|
|
169
|
+
v-model.trim="nomVueEnCours"
|
|
170
|
+
:label="t('csqc.csqcDatatable.choixColonnes.nomVue')"
|
|
171
|
+
single-line
|
|
172
|
+
density="compact"
|
|
173
|
+
autofocus
|
|
174
|
+
class="py-0 my-0"
|
|
175
|
+
:rules="regles.nomVue"
|
|
176
|
+
hide-details
|
|
177
|
+
@keydown.enter="accepterEdition"
|
|
178
|
+
@keydown.esc.stop="annulerEdition"
|
|
179
|
+
/>
|
|
180
|
+
<span
|
|
181
|
+
v-else
|
|
182
|
+
:class="{ 'error--text': !choixOf(item).nomVue }"
|
|
174
183
|
>
|
|
184
|
+
{{ choixOf(item).nomVue || t('csqc.csqcDatatable.choixColonnes.nomVueRequis') }}
|
|
185
|
+
</span>
|
|
186
|
+
</template>
|
|
187
|
+
|
|
188
|
+
<!-- Actions (vue) -->
|
|
189
|
+
<template #item.action="{ item }">
|
|
190
|
+
<template v-if="choixOf(item).nomVue === vueEnEdition">
|
|
175
191
|
<v-icon
|
|
176
|
-
class="
|
|
177
|
-
|
|
178
|
-
>mdi-star</v-icon
|
|
192
|
+
class="iconeSupprimer GouttiereSmall"
|
|
193
|
+
@click.stop.prevent="annulerEdition"
|
|
179
194
|
>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
mdi-window-close
|
|
196
|
+
</v-icon>
|
|
197
|
+
|
|
198
|
+
<v-icon
|
|
199
|
+
class="iconeHover GouttiereSmall"
|
|
200
|
+
@click.stop.prevent="accepterEdition"
|
|
201
|
+
>
|
|
202
|
+
mdi-check
|
|
203
|
+
</v-icon>
|
|
204
|
+
</template>
|
|
205
|
+
|
|
206
|
+
<template v-else>
|
|
207
|
+
<v-tooltip
|
|
208
|
+
location="start"
|
|
209
|
+
color="warning"
|
|
210
|
+
>
|
|
211
|
+
<template #activator="{ props }">
|
|
212
|
+
<v-icon
|
|
213
|
+
class="iconeHover GouttiereSmall"
|
|
214
|
+
v-bind="props"
|
|
215
|
+
@click.stop.prevent="selectionner(choixOf(item))"
|
|
216
|
+
>
|
|
217
|
+
mdi-eye
|
|
218
|
+
</v-icon>
|
|
219
|
+
</template>
|
|
220
|
+
{{ t('csqc.csqcDatatable.choixColonnes.activerTooltip') }}
|
|
221
|
+
</v-tooltip>
|
|
222
|
+
<v-tooltip
|
|
223
|
+
location="start"
|
|
224
|
+
color="warning"
|
|
225
|
+
>
|
|
226
|
+
<template #activator="{ props }">
|
|
227
|
+
<v-icon
|
|
228
|
+
v-bind="props"
|
|
229
|
+
class="iconeHover GouttiereSmall"
|
|
230
|
+
:color="couleurDefaut(choixOf(item))"
|
|
231
|
+
@click.stop.prevent="mettreDefaut(choixOf(item))"
|
|
232
|
+
>
|
|
233
|
+
mdi-star
|
|
234
|
+
</v-icon>
|
|
235
|
+
</template>
|
|
236
|
+
{{ t('csqc.csqcDatatable.choixColonnes.defautTooltip') }}
|
|
237
|
+
</v-tooltip>
|
|
238
|
+
<!-- <v-icon
|
|
239
|
+
class="iconeHover GouttiereSmall"
|
|
240
|
+
@click.stop.prevent="editer(choixOf(item))"
|
|
241
|
+
>
|
|
242
|
+
mdi-pencil
|
|
243
|
+
</v-icon> -->
|
|
244
|
+
|
|
245
|
+
<v-icon
|
|
246
|
+
class="iconeSupprimer GouttiereSmall"
|
|
247
|
+
@click.stop.prevent="supprimer(choixOf(item))"
|
|
248
|
+
>
|
|
249
|
+
mdi-delete
|
|
250
|
+
</v-icon>
|
|
251
|
+
</template>
|
|
191
252
|
</template>
|
|
192
|
-
</
|
|
193
|
-
</v-
|
|
253
|
+
</v-data-table>
|
|
254
|
+
</v-card>
|
|
194
255
|
</v-col>
|
|
195
256
|
</v-row>
|
|
196
|
-
|
|
197
|
-
<template #actions>
|
|
198
|
-
<v-btn
|
|
199
|
-
color="primary"
|
|
200
|
-
class="elevation-0"
|
|
201
|
-
:disabled="!formValide"
|
|
202
|
-
:loading="sauvegardeEnCours"
|
|
203
|
-
@click.stop="sauvegarder"
|
|
204
|
-
>
|
|
205
|
-
{{ $tc('csqc-table.choixColonnes.ok') }}
|
|
206
|
-
</v-btn>
|
|
207
|
-
</template>
|
|
208
257
|
</csqc-modale>
|
|
209
258
|
</template>
|
|
210
259
|
|
|
211
|
-
<script>
|
|
260
|
+
<script setup lang="ts">
|
|
261
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* csqcTableModaleChoixColonnes.vue
|
|
265
|
+
* - Permet de créer/éditer des "vues" de colonnes (ensembles de colonnes visibles)
|
|
266
|
+
* - Drag & drop pour réordonner les colonnes
|
|
267
|
+
* - Persistance via endpoint ComposantUI/Colonnes/...
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
import axios from '../../outils/appAxios'
|
|
212
271
|
import Sortable from 'sortablejs'
|
|
213
|
-
import
|
|
214
|
-
import
|
|
272
|
+
import { computed, nextTick, reactive, ref, watch } from 'vue'
|
|
273
|
+
import { useGoTo } from 'vuetify'
|
|
274
|
+
import { useI18n } from 'vue-i18n'
|
|
275
|
+
import CsqcModale from '../csqcDialogue.vue'
|
|
276
|
+
|
|
277
|
+
type SortableEvent = import('sortablejs').SortableEvent
|
|
278
|
+
|
|
279
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
280
|
+
// Types
|
|
281
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
282
|
+
type ColonneChoix = {
|
|
283
|
+
value: string
|
|
284
|
+
text?: string
|
|
285
|
+
ordre?: number
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
type Colonne = {
|
|
289
|
+
value: string
|
|
290
|
+
title?: string
|
|
291
|
+
ordre?: number
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
type ChoixVue = {
|
|
295
|
+
nomVue: string
|
|
296
|
+
colonnes: string[]
|
|
297
|
+
defaut: boolean
|
|
298
|
+
_id: string // interne (clé stable si nomVue est vide)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
type DataTableItemLike<T> = { raw: T }
|
|
302
|
+
|
|
303
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
304
|
+
// Helpers "unwrap" (évite item.raw dans le template + évite génériques TS dans template)
|
|
305
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
306
|
+
function asRaw<T>(item: T | DataTableItemLike<T>): T {
|
|
307
|
+
return (item as any)?.raw ?? (item as T)
|
|
308
|
+
}
|
|
215
309
|
|
|
216
|
-
|
|
310
|
+
const choixOf = (x: any) => asRaw<ChoixVue>(x)
|
|
311
|
+
const colonneOf = (x: any) => asRaw<Colonne>(x)
|
|
312
|
+
|
|
313
|
+
// Génère des ids pour stabiliser l’expansion / l’édition même si nomVue = ''
|
|
314
|
+
function assurerIds(vues: ChoixVue[]) {
|
|
315
|
+
for (const v of vues) {
|
|
316
|
+
if (!v._id) v._id = crypto.randomUUID()
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Clone profond simple (OK ici car données sérialisables)
|
|
321
|
+
function deepClone<T>(x: T): T {
|
|
322
|
+
return JSON.parse(JSON.stringify(x)) as T
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
326
|
+
// Props / Emits / Expose
|
|
327
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
328
|
+
type ModaleExpose = { ouvrir: () => void; fermer: () => void }
|
|
329
|
+
|
|
330
|
+
const props = defineProps<{
|
|
331
|
+
urlbase: string
|
|
332
|
+
formulaireId: number
|
|
333
|
+
identifiant: string
|
|
334
|
+
colonnes: ColonneChoix[]
|
|
335
|
+
choixOrigine: ChoixVue[]
|
|
336
|
+
}>()
|
|
337
|
+
|
|
338
|
+
const emit = defineEmits<{
|
|
339
|
+
(e: 'selection', choix: ChoixVue): void
|
|
340
|
+
(e: 'sauvegarde', choix: ChoixVue[]): void
|
|
341
|
+
}>()
|
|
342
|
+
|
|
343
|
+
const modale = ref<ModaleExpose | null>(null)
|
|
344
|
+
|
|
345
|
+
defineExpose({ ouvrir, fermer })
|
|
346
|
+
|
|
347
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
348
|
+
// Directive: drag & drop (sortablejs)
|
|
349
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
350
|
+
defineOptions({
|
|
217
351
|
directives: {
|
|
218
352
|
sortableDataTable: {
|
|
219
|
-
|
|
220
|
-
const
|
|
353
|
+
mounted(el: HTMLElement, _binding: any, vnode: any) {
|
|
354
|
+
const tbody = el.getElementsByTagName('tbody')?.[0]
|
|
355
|
+
if (!tbody) return
|
|
356
|
+
|
|
357
|
+
Sortable.create(tbody, {
|
|
221
358
|
animation: 150,
|
|
222
|
-
onUpdate(event) {
|
|
223
|
-
|
|
359
|
+
onUpdate(event: SortableEvent) {
|
|
360
|
+
if (event.oldIndex == null || event.newIndex == null) return
|
|
361
|
+
vnode?.component?.emit?.('sorted', event)
|
|
224
362
|
},
|
|
225
|
-
}
|
|
226
|
-
Sortable.create(el.getElementsByTagName('tbody')[0], options)
|
|
363
|
+
})
|
|
227
364
|
},
|
|
228
365
|
},
|
|
229
366
|
},
|
|
230
|
-
|
|
231
|
-
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
370
|
+
// UI / i18n / scroll helpers
|
|
371
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
372
|
+
const { t } = useI18n()
|
|
373
|
+
const goTo = useGoTo()
|
|
374
|
+
|
|
375
|
+
const erreur = ref('')
|
|
376
|
+
const afficherErreur = ref(false)
|
|
377
|
+
|
|
378
|
+
const choixRetourEnHaut = ref(false)
|
|
379
|
+
const colonnesRetourEnHaut = ref(false)
|
|
380
|
+
|
|
381
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
382
|
+
// State
|
|
383
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
384
|
+
const choix = ref<ChoixVue[]>([])
|
|
385
|
+
const choixEnCours = ref<ChoixVue | null>(null)
|
|
386
|
+
const colonnesEnCours = ref<Colonne[]>([])
|
|
387
|
+
|
|
388
|
+
const sauvegardeEnCours = ref(false)
|
|
389
|
+
|
|
390
|
+
// Edition du nom de vue
|
|
391
|
+
const nomVueEnCours = ref('')
|
|
392
|
+
const vueEnEdition = ref('')
|
|
393
|
+
const desactiverAjout = ref(false)
|
|
394
|
+
|
|
395
|
+
// Tri visuel (on veut ordre asc)
|
|
396
|
+
const triColonnesChoix = ref<Array<{ key: string; order?: 'asc' | 'desc' }>>([{ key: 'ordre', order: 'asc' }])
|
|
397
|
+
|
|
398
|
+
// Règles validation
|
|
399
|
+
const regles = reactive({
|
|
400
|
+
nomVue: [
|
|
401
|
+
(v: string) => (v && v.trim().length >= 1) || t('csqc.csqcDatatable.choixColonnes.nomVueRequis'),
|
|
402
|
+
(v: string) =>
|
|
403
|
+
!choix.value.some(({ nomVue }) => v.trim() === nomVue) || t('csqc.csqcDatatable.choixColonnes.nomVueExiste'),
|
|
404
|
+
],
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
// Affiche l'alerte si aucune colonne sélectionnée dans la vue courante
|
|
408
|
+
const nbColonnesSelectionnees = computed(() => choixEnCours.value?.colonnes?.length ?? 0)
|
|
409
|
+
|
|
410
|
+
function ouvrirVue(ch: ChoixVue) {
|
|
411
|
+
// remonte en haut la table des colonnes quand on change
|
|
412
|
+
if (choixEnCours.value) retourEnHaut('#choixColonnes-vue > .v-data-table__wrapper')
|
|
413
|
+
|
|
414
|
+
// Build colonnesEnCours à partir des colonnes disponibles
|
|
415
|
+
colonnesEnCours.value = deepClone(props.colonnes)
|
|
416
|
+
|
|
417
|
+
// Référence réactive dans la liste
|
|
418
|
+
const refDansListe = choix.value.find(x => x._id === ch._id) ?? ch
|
|
419
|
+
choixEnCours.value = refDansListe
|
|
420
|
+
|
|
421
|
+
// Colonnes sélectionnées d'abord, puis le reste
|
|
422
|
+
const selected: Colonne[] = []
|
|
423
|
+
const missing: string[] = []
|
|
424
|
+
|
|
425
|
+
for (const key of choixEnCours.value.colonnes) {
|
|
426
|
+
const col = colonnesEnCours.value.find(c => c.value === key)
|
|
427
|
+
if (!col) missing.push(key)
|
|
428
|
+
else selected.push(col)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Nettoyage des colonnes disparues
|
|
432
|
+
if (missing.length) {
|
|
433
|
+
choixEnCours.value.colonnes = choixEnCours.value.colonnes.filter(k => !missing.includes(k))
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// selected puis disponibles non sélectionnées
|
|
437
|
+
colonnesEnCours.value = selected.concat(
|
|
438
|
+
colonnesEnCours.value.filter(c => !choixEnCours.value!.colonnes.includes(c.value)),
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
calculOrdreColonnes()
|
|
442
|
+
ecouteDefilerColonnes()
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const expandedChoixIds = ref<string[]>([])
|
|
446
|
+
|
|
447
|
+
// colonnes
|
|
448
|
+
const colonnesChoixColonne = computed(() => [
|
|
449
|
+
{
|
|
450
|
+
title: t('csqc.csqcDatatable.choixColonnes.ordre'),
|
|
451
|
+
key: 'ordre',
|
|
452
|
+
align: 'start' as const,
|
|
453
|
+
sortable: false,
|
|
454
|
+
width: '5%',
|
|
232
455
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
required: true,
|
|
240
|
-
type: Number,
|
|
241
|
-
},
|
|
242
|
-
identifiant: {
|
|
243
|
-
required: true,
|
|
244
|
-
type: String,
|
|
245
|
-
},
|
|
246
|
-
colonnes: {
|
|
247
|
-
required: true,
|
|
248
|
-
type: Array,
|
|
249
|
-
},
|
|
250
|
-
choixOrigine: {
|
|
251
|
-
required: true,
|
|
252
|
-
type: Array,
|
|
253
|
-
},
|
|
456
|
+
{
|
|
457
|
+
title: t('csqc.csqcDatatable.choixColonnes.nomColonne', colonnesEnCours.value.length),
|
|
458
|
+
key: 'title',
|
|
459
|
+
align: 'start' as const,
|
|
460
|
+
sortable: false,
|
|
461
|
+
width: '85%',
|
|
254
462
|
},
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
choixRetourEnHaut: false,
|
|
265
|
-
colonnesRetourEnHaut: false,
|
|
266
|
-
nomVueEnCours: '',
|
|
267
|
-
vueEnEdition: '',
|
|
268
|
-
desactiverAjout: false,
|
|
269
|
-
regles: {
|
|
270
|
-
nomVue: [
|
|
271
|
-
v => (v && v.trim().length >= 1) || this.$tc('csqc-table.choixColonnes.nomVueRequis'),
|
|
272
|
-
v =>
|
|
273
|
-
!this.choix.some(({ nomVue }) => v.trim() === nomVue) ||
|
|
274
|
-
this.$tc('csqc-table.choixColonnes.nomVueExiste'),
|
|
275
|
-
],
|
|
276
|
-
},
|
|
277
|
-
}
|
|
463
|
+
{ title: '', key: 'action', align: 'end' as const, sortable: false, width: '10%' },
|
|
464
|
+
])
|
|
465
|
+
|
|
466
|
+
const colonnesChoix = computed(() => [
|
|
467
|
+
{
|
|
468
|
+
title: t('csqc.csqcDatatable.choixColonnes.vues', choix.value.length),
|
|
469
|
+
key: 'nomVue',
|
|
470
|
+
width: '70%',
|
|
471
|
+
align: 'start' as const,
|
|
278
472
|
},
|
|
473
|
+
{ title: '', key: 'action', width: '30%', align: 'end' as const, sortable: false },
|
|
474
|
+
])
|
|
475
|
+
|
|
476
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
477
|
+
// actions
|
|
478
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
479
|
+
function ouvrir() {
|
|
480
|
+
choix.value = deepClone(props.choixOrigine)
|
|
481
|
+
assurerIds(choix.value) // obligatoire
|
|
482
|
+
expandedChoixIds.value = [] // reset
|
|
483
|
+
modale.value?.ouvrir()
|
|
484
|
+
}
|
|
485
|
+
function fermer() {
|
|
486
|
+
// Reset UI
|
|
487
|
+
sauvegardeEnCours.value = false
|
|
488
|
+
desactiverAjout.value = false
|
|
489
|
+
vueEnEdition.value = ''
|
|
490
|
+
nomVueEnCours.value = ''
|
|
491
|
+
|
|
492
|
+
// Reset sélection
|
|
493
|
+
choixEnCours.value = null
|
|
494
|
+
colonnesEnCours.value = []
|
|
495
|
+
|
|
496
|
+
// Reset erreurs
|
|
497
|
+
afficherErreur.value = false
|
|
498
|
+
erreur.value = ''
|
|
499
|
+
|
|
500
|
+
modale.value?.fermer()
|
|
501
|
+
}
|
|
279
502
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
503
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
504
|
+
// Actions : vues
|
|
505
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
506
|
+
function ajouter() {
|
|
507
|
+
const ch: ChoixVue = {
|
|
508
|
+
_id: crypto.randomUUID(),
|
|
509
|
+
nomVue: '',
|
|
510
|
+
colonnes: [],
|
|
511
|
+
defaut: choix.value.length === 0,
|
|
512
|
+
}
|
|
513
|
+
choix.value.push(ch)
|
|
514
|
+
expandedChoixIds.value = [ch._id] // ouvre celle-là
|
|
515
|
+
editer(ch)
|
|
516
|
+
}
|
|
283
517
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
.map(c => c.value)
|
|
287
|
-
return retour
|
|
288
|
-
},
|
|
289
|
-
colonnesChoixColonne() {
|
|
290
|
-
const cols = [
|
|
291
|
-
{
|
|
292
|
-
text: this.$tc('csqc-table.choixColonnes.ordre'),
|
|
293
|
-
align: 'start',
|
|
294
|
-
sortable: false,
|
|
295
|
-
value: 'ordre',
|
|
296
|
-
width: '5%',
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
text: this.$tc('csqc-table.choixColonnes.nomColonne', this.colonnesEnCours.length),
|
|
300
|
-
align: 'start',
|
|
301
|
-
sortable: false,
|
|
302
|
-
value: 'text',
|
|
303
|
-
width: '85%',
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
value: 'action',
|
|
307
|
-
sortable: false,
|
|
308
|
-
width: '10%',
|
|
309
|
-
align: 'right',
|
|
310
|
-
},
|
|
311
|
-
]
|
|
312
|
-
return cols
|
|
313
|
-
},
|
|
314
|
-
colonnesChoix() {
|
|
315
|
-
return [
|
|
316
|
-
{
|
|
317
|
-
text: this.$tc('csqc-table.choixColonnes.vues', this.choix.length),
|
|
318
|
-
value: 'nomVue',
|
|
319
|
-
width: '70%',
|
|
320
|
-
align: 'start',
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
value: 'action',
|
|
324
|
-
width: '30%',
|
|
325
|
-
align: 'right',
|
|
326
|
-
sortable: false,
|
|
327
|
-
},
|
|
328
|
-
]
|
|
329
|
-
},
|
|
330
|
-
formValide() {
|
|
331
|
-
return !this.choix.some(vue => vue.nomVue === '' || vue.colonnes.length <= 0)
|
|
332
|
-
},
|
|
333
|
-
},
|
|
518
|
+
function editer(ch: ChoixVue) {
|
|
519
|
+
desactiverAjout.value = true
|
|
334
520
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
this.afficherErreur = this.erreur !== ''
|
|
338
|
-
},
|
|
339
|
-
afficherErreur() {
|
|
340
|
-
if (this.afficherErreur === false) {
|
|
341
|
-
this.erreur = ''
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
},
|
|
345
|
-
methods: {
|
|
346
|
-
ouvrir() {
|
|
347
|
-
this.choix = JSON.parse(JSON.stringify(this.choixOrigine))
|
|
348
|
-
this.$refs.modale.ouvrir()
|
|
349
|
-
this.ecouteDefilerChoix()
|
|
350
|
-
},
|
|
521
|
+
// Nettoyage d’un placeholder vide précédent (si on relance edit)
|
|
522
|
+
if (ch.nomVue !== '') supprimer({ _id: 'placeholder', nomVue: '', colonnes: [], defaut: false })
|
|
351
523
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.choixEnCours = []
|
|
357
|
-
this.colonnesEnCours = []
|
|
358
|
-
this.erreur = ''
|
|
359
|
-
this.$refs.modale.fermer()
|
|
360
|
-
},
|
|
524
|
+
erreur.value = ''
|
|
525
|
+
nomVueEnCours.value = ch.nomVue
|
|
526
|
+
vueEnEdition.value = ch.nomVue
|
|
527
|
+
}
|
|
361
528
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
nomVue: '',
|
|
365
|
-
colonnes: [],
|
|
366
|
-
defaut: this.choix.length === 0,
|
|
367
|
-
}
|
|
368
|
-
this.choix.push(ch)
|
|
529
|
+
function accepterEdition() {
|
|
530
|
+
const nom = nomVueEnCours.value?.trim()
|
|
369
531
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
this.desactiverAjout = true
|
|
375
|
-
if (ch.nomVue !== '') this.supprimer({ nomVue: '' })
|
|
532
|
+
if (!nom) {
|
|
533
|
+
erreur.value = t('csqc.csqcDatatable.choixColonnes.nomVueRequis')
|
|
534
|
+
return
|
|
535
|
+
}
|
|
376
536
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
accepterEdition() {
|
|
382
|
-
if (!this.nomVueEnCours) {
|
|
383
|
-
this.erreur = this.$tc('csqc-table.choixColonnes.nomVueRequis')
|
|
384
|
-
return
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (this.choix.some(({ nomVue }) => nomVue === this.nomVueEnCours)) {
|
|
388
|
-
this.erreur = this.$tc('csqc-table.choixColonnes.nomVueExiste')
|
|
389
|
-
return
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const ch = this.choix.find(({ nomVue }) => nomVue === this.vueEnEdition)
|
|
393
|
-
const choixClique = this.choixEstClique(ch)
|
|
394
|
-
|
|
395
|
-
ch.nomVue = this.nomVueEnCours
|
|
396
|
-
if (choixClique) this.choixEnCours = [ch]
|
|
397
|
-
this.vueEnEdition = ''
|
|
398
|
-
this.nomVueEnCours = ''
|
|
399
|
-
this.desactiverAjout = false
|
|
400
|
-
},
|
|
401
|
-
annulerEdition() {
|
|
402
|
-
const ch = this.choix.find(({ nomVue }) => nomVue === this.vueEnEdition)
|
|
403
|
-
if (ch.nomVue === '') this.supprimer(ch)
|
|
404
|
-
this.vueEnEdition = ''
|
|
405
|
-
this.nomVueEnCours = ''
|
|
406
|
-
this.desactiverAjout = false
|
|
407
|
-
},
|
|
537
|
+
if (choix.value.some(({ nomVue }) => nomVue === nom)) {
|
|
538
|
+
erreur.value = t('csqc.csqcDatatable.choixColonnes.nomVueExiste')
|
|
539
|
+
return
|
|
540
|
+
}
|
|
408
541
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.fermer()
|
|
412
|
-
},
|
|
542
|
+
const ch = choix.value.find(({ nomVue }) => nomVue === vueEnEdition.value)
|
|
543
|
+
if (!ch) return
|
|
413
544
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (this.choixEstClique(ch)) {
|
|
417
|
-
this.choixEnCours = []
|
|
418
|
-
this.colonnesEnCours = []
|
|
419
|
-
return
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
this.colonnesEnCours = JSON.parse(JSON.stringify(this.colonnes)) // Toujours commencer avec les colonnes par défaut
|
|
423
|
-
this.choixEnCours = [ch]
|
|
424
|
-
const tempCols = []
|
|
425
|
-
const colsPlusDispo = []
|
|
426
|
-
|
|
427
|
-
this.choixEnCours[0].colonnes.forEach(colonne => {
|
|
428
|
-
const tCol = this.colonnesEnCours.find(c => c.value === colonne)
|
|
429
|
-
if (tCol == null)
|
|
430
|
-
colsPlusDispo.push(colonne) // Ne supprime pas dans la collection lors son itération
|
|
431
|
-
else tempCols.push(tCol)
|
|
432
|
-
})
|
|
545
|
+
const etaitSelectionnee = choixEstClique(ch)
|
|
546
|
+
ch.nomVue = nom
|
|
433
547
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
this.colonnesEnCours = tempCols.concat(
|
|
442
|
-
this.colonnesEnCours.filter(c => !this.choixEnCours[0].colonnes.some(cch => cch === c.value)),
|
|
443
|
-
)
|
|
444
|
-
this.calculOrdreColonnes()
|
|
445
|
-
this.ecouteDefilerColonnes()
|
|
446
|
-
},
|
|
548
|
+
// Si c’était la vue active, on s’assure que la ref active reste valide
|
|
549
|
+
if (etaitSelectionnee) {
|
|
550
|
+
choixEnCours.value = ch
|
|
551
|
+
}
|
|
447
552
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
553
|
+
// Reset édition
|
|
554
|
+
vueEnEdition.value = ''
|
|
555
|
+
nomVueEnCours.value = ''
|
|
556
|
+
desactiverAjout.value = false
|
|
557
|
+
}
|
|
451
558
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
},
|
|
456
|
-
couleurDefaut(ch) {
|
|
457
|
-
if (ch.defaut === true) {
|
|
458
|
-
return '#daa520'
|
|
459
|
-
}
|
|
559
|
+
function annulerEdition() {
|
|
560
|
+
const ch = choix.value.find(({ nomVue }) => nomVue === vueEnEdition.value)
|
|
561
|
+
if (!ch) return
|
|
460
562
|
|
|
461
|
-
|
|
462
|
-
|
|
563
|
+
// Si c'était une nouvelle vue vide, on la retire
|
|
564
|
+
if (ch.nomVue === '') supprimer(ch)
|
|
463
565
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
566
|
+
vueEnEdition.value = ''
|
|
567
|
+
nomVueEnCours.value = ''
|
|
568
|
+
desactiverAjout.value = false
|
|
569
|
+
}
|
|
468
570
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return 'gray'
|
|
474
|
-
},
|
|
475
|
-
basculeColonneClique(colonne) {
|
|
476
|
-
if (this.colonneEstClique(colonne)) {
|
|
477
|
-
this.choixEnCours[0].colonnes = this.choixEnCours[0].colonnes.filter(x => x !== colonne.value)
|
|
478
|
-
return
|
|
479
|
-
}
|
|
571
|
+
function selectionner(ch: ChoixVue) {
|
|
572
|
+
emit('selection', ch)
|
|
573
|
+
fermer()
|
|
574
|
+
}
|
|
480
575
|
|
|
481
|
-
|
|
482
|
-
|
|
576
|
+
function supprimer(ch: ChoixVue) {
|
|
577
|
+
if (choixEstClique(ch)) choixEnCours.value = null
|
|
483
578
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
this.colonnesEnCours.splice(event.newIndex, 0, movedItem)
|
|
487
|
-
this.calculOrdreColonnes()
|
|
488
|
-
this.choixEnCours[0].colonnes = this.colonnesCliquees
|
|
489
|
-
},
|
|
490
|
-
calculOrdreColonnes() {
|
|
491
|
-
for (let i = 0; i < this.colonnesEnCours.length; i += 1) {
|
|
492
|
-
this.colonnesEnCours[i].ordre = 1 + i
|
|
493
|
-
}
|
|
494
|
-
},
|
|
579
|
+
const index = choix.value.findIndex(({ nomVue }) => nomVue === ch.nomVue)
|
|
580
|
+
if (index === -1) return
|
|
495
581
|
|
|
496
|
-
|
|
497
|
-
|
|
582
|
+
// Si on supprime le défaut, on transfère le défaut ailleurs
|
|
583
|
+
if (choix.value[index].defaut) {
|
|
584
|
+
const autre = choix.value.find(({ nomVue }) => nomVue !== ch.nomVue)
|
|
585
|
+
if (autre) autre.defaut = true
|
|
586
|
+
}
|
|
498
587
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
return
|
|
502
|
-
}
|
|
588
|
+
choix.value.splice(index, 1)
|
|
589
|
+
}
|
|
503
590
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
nonDefaut.defaut = true
|
|
508
|
-
}
|
|
509
|
-
}
|
|
591
|
+
function mettreDefaut(ch: ChoixVue) {
|
|
592
|
+
const actuel = choix.value.find(v => v.defaut)
|
|
593
|
+
if (actuel) actuel.defaut = false
|
|
510
594
|
|
|
511
|
-
|
|
512
|
-
|
|
595
|
+
const cible = choix.value.find(v => v.nomVue === ch.nomVue)
|
|
596
|
+
if (cible) cible.defaut = true
|
|
597
|
+
}
|
|
513
598
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
.post(`${this.urlbase}/api/ComposantUI/Colonnes/${this.formulaireId}/Identifiant/${this.identifiant}`, {
|
|
518
|
-
valeur: JSON.stringify(this.choix),
|
|
519
|
-
})
|
|
520
|
-
.then(({ data }) => {
|
|
521
|
-
this.choix = JSON.parse(data.resultat)
|
|
522
|
-
this.$emit('sauvegarde', this.choix)
|
|
523
|
-
if (this.choixEnCours.length > 0) this.$emit('selection', this.choixEnCours[0])
|
|
524
|
-
this.fermer()
|
|
525
|
-
})
|
|
526
|
-
.catch(e => {
|
|
527
|
-
this.erreur = e
|
|
528
|
-
})
|
|
529
|
-
},
|
|
599
|
+
function couleurDefaut(ch: ChoixVue) {
|
|
600
|
+
return ch.defaut ? '#daa520' : 'gray'
|
|
601
|
+
}
|
|
530
602
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
603
|
+
function choixEstClique(ch: ChoixVue) {
|
|
604
|
+
return ch.nomVue === choixEnCours.value?.nomVue
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Click row Vuetify (payload = { item })
|
|
608
|
+
function onClickRowChoix(_e: Event, payload: any) {
|
|
609
|
+
cliquer(choixOf(payload?.item ?? payload))
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Sélection d’une vue :
|
|
614
|
+
* - recalcul de colonnesEnCours (selected d'abord, puis le reste)
|
|
615
|
+
* - nettoyage des colonnes "non disponibles" (si props.colonnes a changé)
|
|
616
|
+
*/
|
|
617
|
+
function cliquer(ch: ChoixVue) {
|
|
618
|
+
const id = ch._id
|
|
619
|
+
if (!id) return
|
|
620
|
+
|
|
621
|
+
expandedChoixIds.value = expandedChoixIds.value[0] === id ? [] : [id]
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
625
|
+
// Actions : colonnes
|
|
626
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
627
|
+
function colonneEstClique(colonne: Colonne) {
|
|
628
|
+
if (!choixEnCours.value) return false
|
|
629
|
+
return choixEnCours.value.colonnes.includes(colonne.value)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function couleurColonneCliquee(colonne: Colonne) {
|
|
633
|
+
return colonneEstClique(colonne) ? 'primary' : 'gray'
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function basculeColonneClique(colonne: Colonne) {
|
|
637
|
+
if (!choixEnCours.value) return
|
|
638
|
+
|
|
639
|
+
if (colonneEstClique(colonne)) {
|
|
640
|
+
choixEnCours.value.colonnes = choixEnCours.value.colonnes.filter(x => x !== colonne.value)
|
|
641
|
+
return
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
choixEnCours.value.colonnes.push(colonne.value)
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Liste des colonnes actuellement "cliquées" selon l’ordre affiché
|
|
648
|
+
const colonnesCliquees = computed(() => {
|
|
649
|
+
if (!choixEnCours.value) return []
|
|
650
|
+
return colonnesEnCours.value.filter(c => choixEnCours.value!.colonnes.includes(c.value)).map(c => c.value)
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
function changeOrdre(event: SortableEvent) {
|
|
654
|
+
if (event.oldIndex == null || event.newIndex == null) return
|
|
655
|
+
|
|
656
|
+
const moved = colonnesEnCours.value.splice(event.oldIndex, 1)[0]
|
|
657
|
+
colonnesEnCours.value.splice(event.newIndex, 0, moved)
|
|
658
|
+
|
|
659
|
+
calculOrdreColonnes()
|
|
660
|
+
|
|
661
|
+
// Après reorder, on sauvegarde la sélection dans le nouvel ordre visuel
|
|
662
|
+
if (choixEnCours.value) {
|
|
663
|
+
choixEnCours.value.colonnes = colonnesCliquees.value
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function calculOrdreColonnes() {
|
|
668
|
+
for (let i = 0; i < colonnesEnCours.value.length; i += 1) {
|
|
669
|
+
colonnesEnCours.value[i].ordre = i + 1
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
674
|
+
// axios
|
|
675
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
676
|
+
async function sauvegarder() {
|
|
677
|
+
sauvegardeEnCours.value = true
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
const res: any = await axios
|
|
681
|
+
.getAxios()
|
|
682
|
+
.post(`${props.urlbase}/api/ComposantUI/Colonnes/${props.formulaireId}/Identifiant/${props.identifiant}`, {
|
|
683
|
+
valeur: JSON.stringify(choix.value),
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
const payload = res?.data ?? res
|
|
687
|
+
|
|
688
|
+
// payload peut être un json string ou déjà un tableau
|
|
689
|
+
choix.value = typeof payload === 'string' ? (JSON.parse(payload) as ChoixVue[]) : (payload as ChoixVue[])
|
|
690
|
+
|
|
691
|
+
assurerIds(choix.value)
|
|
692
|
+
|
|
693
|
+
// Notifie le parent
|
|
694
|
+
emit('sauvegarde', choix.value)
|
|
695
|
+
if (choixEnCours.value) emit('selection', choixEnCours.value)
|
|
696
|
+
|
|
697
|
+
fermer()
|
|
698
|
+
} catch (e) {
|
|
699
|
+
erreur.value = String(e)
|
|
700
|
+
} finally {
|
|
701
|
+
sauvegardeEnCours.value = false
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
706
|
+
// Scroll helpers
|
|
707
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
708
|
+
function retourEnHaut(cible: string) {
|
|
709
|
+
goTo(0, { container: cible })
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function defilerChoix(e: Event) {
|
|
713
|
+
const target = e.target as HTMLElement
|
|
714
|
+
choixRetourEnHaut.value = target.scrollTop >= 200
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function ecouteDefilerChoix() {
|
|
718
|
+
const el = document.querySelector('#choixColonnes-choix > .v-data-table__wrapper')
|
|
719
|
+
if (!el) {
|
|
720
|
+
setTimeout(ecouteDefilerChoix, 100)
|
|
721
|
+
return
|
|
722
|
+
}
|
|
723
|
+
el.addEventListener('scroll', defilerChoix)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function defilerColonnes(e: Event) {
|
|
727
|
+
const target = e.target as HTMLElement
|
|
728
|
+
colonnesRetourEnHaut.value = target.scrollTop >= 200
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function ecouteDefilerColonnes() {
|
|
732
|
+
nextTick(() => {
|
|
733
|
+
const el = document.querySelector('#choixColonnes-vue > .v-data-table__wrapper')
|
|
734
|
+
if (!el) {
|
|
735
|
+
setTimeout(ecouteDefilerColonnes, 100)
|
|
736
|
+
return
|
|
737
|
+
}
|
|
738
|
+
el.addEventListener('scroll', defilerColonnes)
|
|
739
|
+
})
|
|
557
740
|
}
|
|
741
|
+
|
|
742
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
743
|
+
// Watchers
|
|
744
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
745
|
+
watch(erreur, () => {
|
|
746
|
+
afficherErreur.value = erreur.value !== ''
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
watch(afficherErreur, v => {
|
|
750
|
+
if (v === false) erreur.value = ''
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
watch(
|
|
754
|
+
expandedChoixIds,
|
|
755
|
+
v => {
|
|
756
|
+
const id = v?.[0]
|
|
757
|
+
|
|
758
|
+
// Fermeture
|
|
759
|
+
if (!id) {
|
|
760
|
+
choixEnCours.value = null
|
|
761
|
+
colonnesEnCours.value = []
|
|
762
|
+
return
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Ouverture / changement
|
|
766
|
+
const ch = choix.value.find(x => x._id === id)
|
|
767
|
+
if (!ch) return
|
|
768
|
+
|
|
769
|
+
ouvrirVue(ch) // IMPORTANT: ouverture "force", pas toggle
|
|
770
|
+
},
|
|
771
|
+
{ flush: 'post' }, // <-- clé du fix
|
|
772
|
+
)
|
|
773
|
+
watch(
|
|
774
|
+
expandedChoixIds,
|
|
775
|
+
v => {
|
|
776
|
+
if (v.length > 1) expandedChoixIds.value = [v[v.length - 1]] // garde le dernier
|
|
777
|
+
},
|
|
778
|
+
{ flush: 'sync' },
|
|
779
|
+
)
|
|
558
780
|
</script>
|
|
781
|
+
|
|
559
782
|
<style scoped>
|
|
783
|
+
/* Layout */
|
|
560
784
|
.relative {
|
|
561
785
|
position: relative;
|
|
562
786
|
}
|
|
787
|
+
|
|
788
|
+
/* Boutons "retour en haut" */
|
|
563
789
|
.retourHautChoix {
|
|
564
790
|
bottom: 4px !important;
|
|
565
791
|
right: 4px;
|
|
@@ -568,19 +794,38 @@
|
|
|
568
794
|
bottom: 8px !important;
|
|
569
795
|
right: 48px;
|
|
570
796
|
}
|
|
571
|
-
|
|
572
|
-
|
|
797
|
+
|
|
798
|
+
/* Scroll vertical des tables */
|
|
573
799
|
.limiteHauteurChoix > .v-data-table__wrapper {
|
|
574
800
|
max-height: min(80vh - 200px, 900px);
|
|
575
801
|
overflow-y: auto;
|
|
576
802
|
}
|
|
577
|
-
#choixColonnes-choix > .v-data-table__wrapper > table > thead > tr > th {
|
|
578
|
-
z-index: 3;
|
|
579
|
-
}
|
|
580
803
|
.limiteHauteurChoix.colonnes > .v-data-table__wrapper {
|
|
581
804
|
max-height: min(80vh - 294px, 600px);
|
|
582
805
|
}
|
|
806
|
+
|
|
807
|
+
/* Z-index du header table 1 */
|
|
808
|
+
#choixColonnes-choix > .v-data-table__wrapper > table > thead > tr > th {
|
|
809
|
+
z-index: 3;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/* Drag cursor */
|
|
583
813
|
.v-data-table.ordonable > .v-data-table__wrapper > table > tbody > tr {
|
|
584
814
|
cursor: move;
|
|
585
815
|
}
|
|
816
|
+
|
|
817
|
+
/* Icons */
|
|
818
|
+
.iconeSupprimer:hover {
|
|
819
|
+
color: red;
|
|
820
|
+
}
|
|
821
|
+
.v-icon.v-icon.v-icon--link.iconeSupprimer:hover {
|
|
822
|
+
color: red !important;
|
|
823
|
+
}
|
|
824
|
+
.iconeEditer:hover {
|
|
825
|
+
color: #095797;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.GouttiereSmall {
|
|
829
|
+
margin-left: 8px;
|
|
830
|
+
}
|
|
586
831
|
</style>
|