codevdesign 1.0.44 → 1.0.45
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/csqcTable/csqcTable.vue +537 -537
- package/package.json +10 -10
|
@@ -1,537 +1,537 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<v-row v-if="barreHautAfficher">
|
|
4
|
-
<slot name="ligne" />
|
|
5
|
-
<!-- Affichage de la boite de recherche-->
|
|
6
|
-
<v-col>
|
|
7
|
-
<slot name="recherche"></slot>
|
|
8
|
-
<Recherche
|
|
9
|
-
v-bind="$attrs"
|
|
10
|
-
:afficher="rechercheAfficher"
|
|
11
|
-
:recherche-texte="rechercheTexte"
|
|
12
|
-
:chargement="chargementListe"
|
|
13
|
-
:recherche="recherche"
|
|
14
|
-
:recherche-avancee="rechercheAvancee"
|
|
15
|
-
:recherche-avancee-texte="rechercheAvanceeTexte"
|
|
16
|
-
:recherche-avancee-largeur="rechercheAvanceeLargeur"
|
|
17
|
-
:recherche-avancee-style="rechercheAvanceeStyle"
|
|
18
|
-
class="flex-grow-1"
|
|
19
|
-
@update:recherche="chargerRecherche"
|
|
20
|
-
@panneau:etat="onPanelChange"
|
|
21
|
-
>
|
|
22
|
-
<template #milieu>
|
|
23
|
-
<slot name="milieu" />
|
|
24
|
-
</template>
|
|
25
|
-
|
|
26
|
-
<template #droite>
|
|
27
|
-
<slot name="droite" />
|
|
28
|
-
<v-btn
|
|
29
|
-
v-if="btnAjouter"
|
|
30
|
-
color="primary"
|
|
31
|
-
class="ml-1 float-right"
|
|
32
|
-
@click.stop="ajouter"
|
|
33
|
-
>
|
|
34
|
-
{{ props.btnAjouterTexte ? props.btnAjouterTexte : $t('csqc.bouton.ajouter') }}
|
|
35
|
-
</v-btn>
|
|
36
|
-
|
|
37
|
-
<exportExcelComponent
|
|
38
|
-
v-if="excel"
|
|
39
|
-
:liste="filteredItems"
|
|
40
|
-
:chargement-liste="chargementListe"
|
|
41
|
-
:nom-fichier="excelNomFichier"
|
|
42
|
-
class="mt-1 ml-1 float-right"
|
|
43
|
-
/>
|
|
44
|
-
<v-icon
|
|
45
|
-
v-if="permettreChoixColonnes"
|
|
46
|
-
color="grisMoyen"
|
|
47
|
-
class="mt-1 ml-1 float-right"
|
|
48
|
-
@click.stop="ouvrirChoixColonnes"
|
|
49
|
-
>
|
|
50
|
-
mdi-table-edit
|
|
51
|
-
</v-icon>
|
|
52
|
-
</template>
|
|
53
|
-
<template #rechercheAvanceeTitre>
|
|
54
|
-
<slot name="rechercheAvanceeTitre" />
|
|
55
|
-
</template>
|
|
56
|
-
<template #rechercheAvanceeApresTitre>
|
|
57
|
-
<slot name="rechercheAvanceeApresTitre" />
|
|
58
|
-
</template>
|
|
59
|
-
<template #rechercheAvancee>
|
|
60
|
-
<slot name="rechercheAvancee" />
|
|
61
|
-
</template>
|
|
62
|
-
</Recherche>
|
|
63
|
-
</v-col>
|
|
64
|
-
</v-row>
|
|
65
|
-
|
|
66
|
-
<!-- datatable-->
|
|
67
|
-
<v-row>
|
|
68
|
-
<v-col
|
|
69
|
-
cols="12"
|
|
70
|
-
class="d-flex ControlesDatatable flex-wrap"
|
|
71
|
-
>
|
|
72
|
-
<v-data-table
|
|
73
|
-
ref="datatable"
|
|
74
|
-
v-bind="$attrs"
|
|
75
|
-
:headers="colonnesAffichees"
|
|
76
|
-
:item-key="itemKey"
|
|
77
|
-
:items="liste"
|
|
78
|
-
:search="recherche"
|
|
79
|
-
:loading="chargementListe"
|
|
80
|
-
:hover="hover"
|
|
81
|
-
:density="densite"
|
|
82
|
-
:items-per-page="itemsParPage"
|
|
83
|
-
:item-per-page-options="itemsParPageOptions"
|
|
84
|
-
:sort-by="triDepart"
|
|
85
|
-
:multi-sort="estMultiTriActif"
|
|
86
|
-
@click:row="cliqueLigne"
|
|
87
|
-
>
|
|
88
|
-
<!-- utilisation des slots via le component parent-->
|
|
89
|
-
<!-- eslint-disable-next-line -->
|
|
90
|
-
<template
|
|
91
|
-
v-for="(_, slot) of $slots"
|
|
92
|
-
:key="slot"
|
|
93
|
-
#[slot]="scope"
|
|
94
|
-
>
|
|
95
|
-
<slot
|
|
96
|
-
:name="slot"
|
|
97
|
-
v-bind="scope"
|
|
98
|
-
/>
|
|
99
|
-
</template>
|
|
100
|
-
|
|
101
|
-
<!-- boutons supprimer et modifier -->
|
|
102
|
-
<!-- eslint-disable-next-line -->
|
|
103
|
-
<template v-slot:item.action="{ item }">
|
|
104
|
-
<slot name="actionsCustom"></slot>
|
|
105
|
-
<v-icon
|
|
106
|
-
v-if="props.btnSupprimer"
|
|
107
|
-
size="large"
|
|
108
|
-
class="iconeSupprimer float-right"
|
|
109
|
-
@click.stop.prevent="ouvrirModaleSupprimer(item)"
|
|
110
|
-
>
|
|
111
|
-
mdi-delete
|
|
112
|
-
</v-icon>
|
|
113
|
-
|
|
114
|
-
<v-icon
|
|
115
|
-
v-if="props.btnModifier"
|
|
116
|
-
size="large"
|
|
117
|
-
class="iconeEditer float-right"
|
|
118
|
-
@click.stop.prevent="modifier(item)"
|
|
119
|
-
>
|
|
120
|
-
mdi-pencil
|
|
121
|
-
</v-icon>
|
|
122
|
-
</template>
|
|
123
|
-
</v-data-table>
|
|
124
|
-
|
|
125
|
-
<!-- Fenêtre de suppression -->
|
|
126
|
-
<confirmation
|
|
127
|
-
v-if="props.btnSupprimer"
|
|
128
|
-
ref="modaleSupprimer"
|
|
129
|
-
:texte="supprimerTexte"
|
|
130
|
-
:titre="supprimerTitreTexte"
|
|
131
|
-
:largeur="modaleSupprimerLargeur"
|
|
132
|
-
@confirmer="supprimer"
|
|
133
|
-
/>
|
|
134
|
-
<modale-choix
|
|
135
|
-
ref="modaleChoix"
|
|
136
|
-
v-if="permettreChoixColonnes"
|
|
137
|
-
:urlbase="urlbase"
|
|
138
|
-
:formulaire-id="formulaireId"
|
|
139
|
-
:identifiant="identifiant"
|
|
140
|
-
:colonnes="colonnesPourChoix"
|
|
141
|
-
:choix-origine="choixOrigineNormalise"
|
|
142
|
-
@selection="selectionChoix"
|
|
143
|
-
@sauvegarde="sauvegardeChoix"
|
|
144
|
-
/>
|
|
145
|
-
</v-col>
|
|
146
|
-
</v-row>
|
|
147
|
-
</div>
|
|
148
|
-
</template>
|
|
149
|
-
<script setup lang="ts">
|
|
150
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
151
|
-
import { computed, onMounted, ref, type PropType, type Slots } from 'vue'
|
|
152
|
-
import { useI18n } from 'vue-i18n'
|
|
153
|
-
import type { SortItem } from 'vuetify/lib/components/VDataTable/composables/sort.mjs'
|
|
154
|
-
|
|
155
|
-
import Recherche from '../csqcRecherche.vue'
|
|
156
|
-
import confirmation from '../csqcConfirmation.vue'
|
|
157
|
-
import
|
|
158
|
-
import
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
type VueColonnes = {
|
|
163
|
-
nomVue: string
|
|
164
|
-
colonnes: string[]
|
|
165
|
-
defaut?: boolean
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const slots = defineSlots<Slots>()
|
|
169
|
-
const emit = defineEmits(['ajouter', 'cliqueLigne', 'supprimer', 'modifier', 'donneesExportees', 'panneau:etat'])
|
|
170
|
-
|
|
171
|
-
// ============================================================================
|
|
172
|
-
// Props
|
|
173
|
-
// ============================================================================
|
|
174
|
-
const props = defineProps({
|
|
175
|
-
barreHautAfficher: { type: Boolean, default: true },
|
|
176
|
-
|
|
177
|
-
btnAjouter: { type: Boolean, default: true },
|
|
178
|
-
btnAjouterTexte: { type: String, default: '' },
|
|
179
|
-
btnModifier: { type: Boolean, default: true },
|
|
180
|
-
btnSupprimer: { type: Boolean, default: true },
|
|
181
|
-
|
|
182
|
-
chargementListe: { type: Boolean, default: false },
|
|
183
|
-
operationEnCours: { type: Boolean, default: false },
|
|
184
|
-
|
|
185
|
-
// Headers Vuetify
|
|
186
|
-
colonnes: {
|
|
187
|
-
type: Array as PropType<Colonne[]>,
|
|
188
|
-
default: () => [],
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
densite: { type: String as PropType<'default' | 'comfortable' | 'compact'>, default: 'default' },
|
|
192
|
-
|
|
193
|
-
excel: { type: Boolean, default: false },
|
|
194
|
-
excelNomFichier: { type: String, default: 'csqc' },
|
|
195
|
-
|
|
196
|
-
// Choix colonnes
|
|
197
|
-
formulaireId: { type: Number, default: -1 },
|
|
198
|
-
identifiant: { type: String, default: '' },
|
|
199
|
-
urlbase: { type: String, default: '' },
|
|
200
|
-
permettreChoixColonnes: { type: Boolean, default: false },
|
|
201
|
-
|
|
202
|
-
// Table
|
|
203
|
-
itemKey: { type: String, default: 'id' },
|
|
204
|
-
itemsParPage: { type: Number, default: 10 },
|
|
205
|
-
itemsParPageOptions: { type: Array, default: () => [10, 25, 30, -1] },
|
|
206
|
-
liste: {
|
|
207
|
-
type: Array as PropType<Record<string, any>[]>,
|
|
208
|
-
default: () => [],
|
|
209
|
-
},
|
|
210
|
-
hover: { type: Boolean, default: false },
|
|
211
|
-
|
|
212
|
-
// Recherche
|
|
213
|
-
rechercheTexte: { type: String, default: '' },
|
|
214
|
-
rechercheAfficher: { type: Boolean, default: true },
|
|
215
|
-
rechercheAvancee: { type: Boolean, default: false },
|
|
216
|
-
rechercheAvanceeTexte: { type: String, default: '' },
|
|
217
|
-
rechercheAvanceeLargeur: { type: Number, default: 12 },
|
|
218
|
-
rechercheAvanceeStyle: { type: String, default: '' },
|
|
219
|
-
|
|
220
|
-
// Modale suppression
|
|
221
|
-
modaleSupprimerChamp: { type: String, default: '' },
|
|
222
|
-
modaleSupprimerTexte: { type: String, default: '' },
|
|
223
|
-
modaleSupprimerTitre: { type: String, default: '' },
|
|
224
|
-
modaleSupprimerLargeur: { type: String, default: '525px' },
|
|
225
|
-
|
|
226
|
-
// Tri
|
|
227
|
-
triDescDepart: { type: Boolean, default: false },
|
|
228
|
-
triParDepart: {
|
|
229
|
-
type: [Array, String] as PropType<string | string[] | SortItem[] | undefined>,
|
|
230
|
-
default: undefined,
|
|
231
|
-
},
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
const { t } = useI18n({ useScope: 'global' })
|
|
235
|
-
const recherche = ref<string>('')
|
|
236
|
-
|
|
237
|
-
// Modale suppression (on garde l’item sélectionné)
|
|
238
|
-
const itemSelectionne = ref<any>(null)
|
|
239
|
-
|
|
240
|
-
// ============================================================================
|
|
241
|
-
// State - Choix de colonnes (vues sauvegardées) + sélection courante
|
|
242
|
-
// ============================================================================
|
|
243
|
-
const modaleChoix = ref<InstanceType<typeof ModaleChoix> | null>(null)
|
|
244
|
-
const openChoixColonnes = ref(false)
|
|
245
|
-
const choix = ref<VueColonnes[]>([]) // la liste des vues disponibles
|
|
246
|
-
const vueActive = ref<VueColonnes | null>(null) // optionnel: la vue "appliquée"
|
|
247
|
-
const valeurSelectionChoixColonnes = ref<string | null>(null) // nomVue sélectionné dans la modale / UI
|
|
248
|
-
|
|
249
|
-
// ============================================================================
|
|
250
|
-
// Charger le choix de colonnes sauvegardé s'il y a lieu
|
|
251
|
-
// ============================================================================
|
|
252
|
-
onMounted(async () => {
|
|
253
|
-
if (!props.permettreChoixColonnes) return
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const res: any = await axios
|
|
257
|
-
.getAxios()
|
|
258
|
-
.get(`${props.urlbase}/api/ComposantUI/Colonnes/${props.formulaireId}/Identifiant/${props.identifiant}`)
|
|
259
|
-
|
|
260
|
-
const payload = res?.data ?? res
|
|
261
|
-
|
|
262
|
-
if (!payload) {
|
|
263
|
-
choix.value = []
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
choix.value = typeof payload === 'string' ? JSON.parse(payload) : payload
|
|
268
|
-
} catch (e) {
|
|
269
|
-
choix.value = []
|
|
270
|
-
// console.debug(e)
|
|
271
|
-
}
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
// ============================================================================
|
|
275
|
-
// Computed - Normaliser pour la modale Choix colonne
|
|
276
|
-
// ============================================================================
|
|
277
|
-
const choixOrigineNormalise = computed(() =>
|
|
278
|
-
(choix.value ?? []).map(c => ({
|
|
279
|
-
_id: '',
|
|
280
|
-
nomVue: c.nomVue,
|
|
281
|
-
colonnes: c.colonnes ?? [],
|
|
282
|
-
defaut: c.defaut ?? false,
|
|
283
|
-
})),
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
// ============================================================================
|
|
287
|
-
// Actions - Modale choix colonnes
|
|
288
|
-
// ============================================================================
|
|
289
|
-
function ouvrirChoixColonnes() {
|
|
290
|
-
modaleChoix.value?.ouvrir()
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function appliquerVue(vue: VueColonnes) {
|
|
294
|
-
vueActive.value = vue
|
|
295
|
-
openChoixColonnes.value = false
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function selectionChoix(vue: VueColonnes) {
|
|
299
|
-
// on garde une trace du nom sélectionné (pour retrouver la vue plus tard)
|
|
300
|
-
valeurSelectionChoixColonnes.value = vue.nomVue
|
|
301
|
-
appliquerVue(vue)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function sauvegardeChoix(vues: VueColonnes[]) {
|
|
305
|
-
if (!props.permettreChoixColonnes) return
|
|
306
|
-
|
|
307
|
-
// si la sélection courante n’existe plus, on reset
|
|
308
|
-
if (
|
|
309
|
-
valeurSelectionChoixColonnes.value != null &&
|
|
310
|
-
vues.find(x => x.nomVue === valeurSelectionChoixColonnes.value) == null
|
|
311
|
-
) {
|
|
312
|
-
valeurSelectionChoixColonnes.value = null
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
choix.value = vues
|
|
316
|
-
|
|
317
|
-
// optionnel: appliquer automatiquement la vue par défaut
|
|
318
|
-
const defaut = vues.find(v => v.defaut)
|
|
319
|
-
if (defaut) appliquerVue(defaut)
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// ============================================================================
|
|
323
|
-
// Computed - Colonnes à envoyer à la modale (liste ordonnée + libellé)
|
|
324
|
-
// ============================================================================
|
|
325
|
-
const colonnesPourChoix = computed(() =>
|
|
326
|
-
props.colonnes
|
|
327
|
-
// on exclut l'action (souvent toujours visible) + colonnes "cachées"
|
|
328
|
-
.filter(c => c.key && c.key !== 'action' && (c as any).align !== 'd-none')
|
|
329
|
-
.map((c, idx) => ({
|
|
330
|
-
title: (c as any).title ?? (c as any).text ?? String((c as any).key ?? (c as any).value),
|
|
331
|
-
value: String((c as any).key ?? (c as any).value), // ← on force string
|
|
332
|
-
ordre: idx + 1,
|
|
333
|
-
})),
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
// ============================================================================
|
|
337
|
-
// Computed - Headers réellement affichés par la table (selon le choix actif)
|
|
338
|
-
// ============================================================================
|
|
339
|
-
const colonnesAffichees = computed(() => {
|
|
340
|
-
// 1) colonnes "disponibles" (on enlève celles explicitement cachées)
|
|
341
|
-
const colonnesFiltre = props.colonnes.filter(c => (c as any).align !== 'd-none')
|
|
342
|
-
|
|
343
|
-
// 2) si feature OFF ou pas de vues sauvegardées => on retourne tout
|
|
344
|
-
if (!props.permettreChoixColonnes) return colonnesFiltre
|
|
345
|
-
if (!choix.value?.length) return colonnesFiltre
|
|
346
|
-
|
|
347
|
-
// 3) retrouver la vue sélectionnée (sinon la défaut)
|
|
348
|
-
const choixSelection = choix.value.find(
|
|
349
|
-
x => valeurSelectionChoixColonnes.value === x.nomVue || (!valeurSelectionChoixColonnes.value && x.defaut),
|
|
350
|
-
)
|
|
351
|
-
if (!choixSelection) return colonnesFiltre
|
|
352
|
-
|
|
353
|
-
// 4) reconstruire la liste de headers dans l’ordre sauvegardé
|
|
354
|
-
return choixSelection.colonnes.map(k => colonnesFiltre.find(c => String(c.key) === k)).filter(Boolean) as Colonne[]
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
// ============================================================================
|
|
358
|
-
// Computed - Tri initial (Vuetify sort-by)
|
|
359
|
-
// ============================================================================
|
|
360
|
-
const triDepart = computed<SortItem[] | undefined>(() => {
|
|
361
|
-
if (props.triParDepart == null) return undefined
|
|
362
|
-
|
|
363
|
-
const ordre = props.triDescDepart ? 'desc' : 'asc'
|
|
364
|
-
if (typeof props.triParDepart === 'string') return [{ key: props.triParDepart, order: ordre }]
|
|
365
|
-
|
|
366
|
-
const retour: SortItem[] = []
|
|
367
|
-
for (let i = 0; i < props.triParDepart.length; i += 1) {
|
|
368
|
-
const tri = props.triParDepart[i]!
|
|
369
|
-
if (typeof tri === 'string') retour.push({ key: tri, order: ordre })
|
|
370
|
-
else retour.push(tri)
|
|
371
|
-
}
|
|
372
|
-
return retour
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
const estMultiTriActif = computed(() => {
|
|
376
|
-
if (props.triParDepart == null) return false
|
|
377
|
-
if (typeof props.triParDepart === 'string') return false
|
|
378
|
-
return true
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
// ============================================================================
|
|
382
|
-
// Computed - Export (filtrage local basé sur recherche)
|
|
383
|
-
// ============================================================================
|
|
384
|
-
const filteredItems = computed(() => {
|
|
385
|
-
if (!recherche.value) return props.liste
|
|
386
|
-
const q = recherche.value.toLowerCase()
|
|
387
|
-
return props.liste.filter(item => Object.values(item).some(val => String(val).toLowerCase().includes(q)))
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
// ============================================================================
|
|
391
|
-
// UI Actions - Recherche / Table events
|
|
392
|
-
// ============================================================================
|
|
393
|
-
function chargerRecherche(val: string) {
|
|
394
|
-
recherche.value = val
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function ajouter() {
|
|
398
|
-
emit('ajouter')
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function cliqueLigne(_e: Event, { item }: { item: any }) {
|
|
402
|
-
emit('cliqueLigne', item)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function modifier(item: any) {
|
|
406
|
-
emit('modifier', item)
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ============================================================================
|
|
410
|
-
// Suppression - Texte + actions
|
|
411
|
-
// ============================================================================
|
|
412
|
-
const supprimerTexte = computed(() => {
|
|
413
|
-
if (itemSelectionne.value == null) return ''
|
|
414
|
-
|
|
415
|
-
if (props.modaleSupprimerTexte) return props.modaleSupprimerTexte
|
|
416
|
-
|
|
417
|
-
return t('csqc.message.supprimerMessage', {
|
|
418
|
-
nom: itemSelectionne?.value?.[props.modaleSupprimerChamp] ?? '',
|
|
419
|
-
})
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
const supprimerTitreTexte = computed(() => props.modaleSupprimerTitre || t('csqc.message.supprimerTitre'))
|
|
423
|
-
|
|
424
|
-
function ouvrirModaleSupprimer(item: any) {
|
|
425
|
-
itemSelectionne.value = item
|
|
426
|
-
emit('supprimer', itemSelectionne.value) // tu sembles gérer la modale ailleurs
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function supprimer() {
|
|
430
|
-
emit('supprimer', itemSelectionne.value)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// ============================================================================
|
|
434
|
-
// UI - Panneau recherche avancée
|
|
435
|
-
// ============================================================================
|
|
436
|
-
function onPanelChange(val: boolean) {
|
|
437
|
-
emit('panneau:etat', val)
|
|
438
|
-
}
|
|
439
|
-
</script>
|
|
440
|
-
|
|
441
|
-
<style scoped lang="css">
|
|
442
|
-
.ControlesDatatable {
|
|
443
|
-
gap: 4px;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/* barre de recherche design QC*/
|
|
447
|
-
.BarreRecherche {
|
|
448
|
-
border-radius: 4px 0 0 4px !important;
|
|
449
|
-
min-height: 40px;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
.BarreRechercheBackIcone {
|
|
453
|
-
border-right: 1px solid #808a9d;
|
|
454
|
-
border-top: 1px solid #808a9d;
|
|
455
|
-
border-bottom: 1px solid #808a9d;
|
|
456
|
-
background-color: #095797 !important;
|
|
457
|
-
height: 40px !important;
|
|
458
|
-
width: 40px !important;
|
|
459
|
-
max-width: 40px !important;
|
|
460
|
-
min-width: 40px !important;
|
|
461
|
-
padding-right: 0 !important;
|
|
462
|
-
padding-left: 0 !important;
|
|
463
|
-
border-radius: 0 4px 4px 0 !important;
|
|
464
|
-
margin-left: -16px;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/* icone loupe */
|
|
468
|
-
.BarreRechercheIcone {
|
|
469
|
-
font-size: 34px !important;
|
|
470
|
-
margin-left: 1px !important;
|
|
471
|
-
margin-top: 2px !important;
|
|
472
|
-
color: white !important;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/* datatable contour */
|
|
476
|
-
.v-data-table {
|
|
477
|
-
border: 1px solid #d3d3d3;
|
|
478
|
-
border-radius: 5px;
|
|
479
|
-
overflow: hidden;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/* hover */
|
|
483
|
-
.v-data-table:hover {
|
|
484
|
-
cursor: pointer !important;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/* datatable row */
|
|
488
|
-
.v-data-table .v-table__wrapper > table > thead > tr > td,
|
|
489
|
-
.v-data-table .v-table__wrapper > table > thead > tr th,
|
|
490
|
-
.v-data-table .v-table__wrapper > table tbody > tr > td,
|
|
491
|
-
.v-data-table .v-table__wrapper > table tbody > tr th {
|
|
492
|
-
background-color: #d3d3d375 !important;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/* datatable header contour */
|
|
496
|
-
.v-data-table .v-table__wrapper > table > thead > tr > th,
|
|
497
|
-
.v-data-table .v-table__wrapper > table tbody > tr > th {
|
|
498
|
-
background-color: white !important;
|
|
499
|
-
border-bottom: 4px #223654 solid !important;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/* datatable header intérieur*/
|
|
503
|
-
.v-data-table-header__content {
|
|
504
|
-
background-color: white !important;
|
|
505
|
-
color: #223654 !important;
|
|
506
|
-
font-weight: 600;
|
|
507
|
-
font-size: 15px;
|
|
508
|
-
height: 46px;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/* datatable footer */
|
|
512
|
-
.v-data-table-footer {
|
|
513
|
-
background-color: white !important;
|
|
514
|
-
border-top: 4px #223654 solid !important;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/* datatable row hover */
|
|
518
|
-
.v-data-table .v-table__wrapper > table tbody > tr:hover > td,
|
|
519
|
-
.v-data-table .v-table__wrapper > table tbody > tr:hover {
|
|
520
|
-
background-color: #e0e0e0 !important;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
.iconeSupprimer:hover {
|
|
524
|
-
color: red;
|
|
525
|
-
}
|
|
526
|
-
.v-icon.v-icon.v-icon--link.iconeSupprimer:hover {
|
|
527
|
-
color: red !important;
|
|
528
|
-
}
|
|
529
|
-
.iconeEditer:hover {
|
|
530
|
-
color: #095797;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/* hover icone à droite du ajouter*/
|
|
534
|
-
.text-grisMoyen:hover {
|
|
535
|
-
color: #095797 !important;
|
|
536
|
-
}
|
|
537
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-row v-if="barreHautAfficher">
|
|
4
|
+
<slot name="ligne" />
|
|
5
|
+
<!-- Affichage de la boite de recherche-->
|
|
6
|
+
<v-col>
|
|
7
|
+
<slot name="recherche"></slot>
|
|
8
|
+
<Recherche
|
|
9
|
+
v-bind="$attrs"
|
|
10
|
+
:afficher="rechercheAfficher"
|
|
11
|
+
:recherche-texte="rechercheTexte"
|
|
12
|
+
:chargement="chargementListe"
|
|
13
|
+
:recherche="recherche"
|
|
14
|
+
:recherche-avancee="rechercheAvancee"
|
|
15
|
+
:recherche-avancee-texte="rechercheAvanceeTexte"
|
|
16
|
+
:recherche-avancee-largeur="rechercheAvanceeLargeur"
|
|
17
|
+
:recherche-avancee-style="rechercheAvanceeStyle"
|
|
18
|
+
class="flex-grow-1"
|
|
19
|
+
@update:recherche="chargerRecherche"
|
|
20
|
+
@panneau:etat="onPanelChange"
|
|
21
|
+
>
|
|
22
|
+
<template #milieu>
|
|
23
|
+
<slot name="milieu" />
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<template #droite>
|
|
27
|
+
<slot name="droite" />
|
|
28
|
+
<v-btn
|
|
29
|
+
v-if="btnAjouter"
|
|
30
|
+
color="primary"
|
|
31
|
+
class="ml-1 float-right"
|
|
32
|
+
@click.stop="ajouter"
|
|
33
|
+
>
|
|
34
|
+
{{ props.btnAjouterTexte ? props.btnAjouterTexte : $t('csqc.bouton.ajouter') }}
|
|
35
|
+
</v-btn>
|
|
36
|
+
|
|
37
|
+
<exportExcelComponent
|
|
38
|
+
v-if="excel"
|
|
39
|
+
:liste="filteredItems"
|
|
40
|
+
:chargement-liste="chargementListe"
|
|
41
|
+
:nom-fichier="excelNomFichier"
|
|
42
|
+
class="mt-1 ml-1 float-right"
|
|
43
|
+
/>
|
|
44
|
+
<v-icon
|
|
45
|
+
v-if="permettreChoixColonnes"
|
|
46
|
+
color="grisMoyen"
|
|
47
|
+
class="mt-1 ml-1 float-right"
|
|
48
|
+
@click.stop="ouvrirChoixColonnes"
|
|
49
|
+
>
|
|
50
|
+
mdi-table-edit
|
|
51
|
+
</v-icon>
|
|
52
|
+
</template>
|
|
53
|
+
<template #rechercheAvanceeTitre>
|
|
54
|
+
<slot name="rechercheAvanceeTitre" />
|
|
55
|
+
</template>
|
|
56
|
+
<template #rechercheAvanceeApresTitre>
|
|
57
|
+
<slot name="rechercheAvanceeApresTitre" />
|
|
58
|
+
</template>
|
|
59
|
+
<template #rechercheAvancee>
|
|
60
|
+
<slot name="rechercheAvancee" />
|
|
61
|
+
</template>
|
|
62
|
+
</Recherche>
|
|
63
|
+
</v-col>
|
|
64
|
+
</v-row>
|
|
65
|
+
|
|
66
|
+
<!-- datatable-->
|
|
67
|
+
<v-row>
|
|
68
|
+
<v-col
|
|
69
|
+
cols="12"
|
|
70
|
+
class="d-flex ControlesDatatable flex-wrap"
|
|
71
|
+
>
|
|
72
|
+
<v-data-table
|
|
73
|
+
ref="datatable"
|
|
74
|
+
v-bind="$attrs"
|
|
75
|
+
:headers="colonnesAffichees"
|
|
76
|
+
:item-key="itemKey"
|
|
77
|
+
:items="liste"
|
|
78
|
+
:search="recherche"
|
|
79
|
+
:loading="chargementListe"
|
|
80
|
+
:hover="hover"
|
|
81
|
+
:density="densite"
|
|
82
|
+
:items-per-page="itemsParPage"
|
|
83
|
+
:item-per-page-options="itemsParPageOptions"
|
|
84
|
+
:sort-by="triDepart"
|
|
85
|
+
:multi-sort="estMultiTriActif"
|
|
86
|
+
@click:row="cliqueLigne"
|
|
87
|
+
>
|
|
88
|
+
<!-- utilisation des slots via le component parent-->
|
|
89
|
+
<!-- eslint-disable-next-line -->
|
|
90
|
+
<template
|
|
91
|
+
v-for="(_, slot) of $slots"
|
|
92
|
+
:key="slot"
|
|
93
|
+
#[slot]="scope"
|
|
94
|
+
>
|
|
95
|
+
<slot
|
|
96
|
+
:name="slot"
|
|
97
|
+
v-bind="scope"
|
|
98
|
+
/>
|
|
99
|
+
</template>
|
|
100
|
+
|
|
101
|
+
<!-- boutons supprimer et modifier -->
|
|
102
|
+
<!-- eslint-disable-next-line -->
|
|
103
|
+
<template v-slot:item.action="{ item }">
|
|
104
|
+
<slot name="actionsCustom"></slot>
|
|
105
|
+
<v-icon
|
|
106
|
+
v-if="props.btnSupprimer"
|
|
107
|
+
size="large"
|
|
108
|
+
class="iconeSupprimer float-right"
|
|
109
|
+
@click.stop.prevent="ouvrirModaleSupprimer(item)"
|
|
110
|
+
>
|
|
111
|
+
mdi-delete
|
|
112
|
+
</v-icon>
|
|
113
|
+
|
|
114
|
+
<v-icon
|
|
115
|
+
v-if="props.btnModifier"
|
|
116
|
+
size="large"
|
|
117
|
+
class="iconeEditer float-right"
|
|
118
|
+
@click.stop.prevent="modifier(item)"
|
|
119
|
+
>
|
|
120
|
+
mdi-pencil
|
|
121
|
+
</v-icon>
|
|
122
|
+
</template>
|
|
123
|
+
</v-data-table>
|
|
124
|
+
|
|
125
|
+
<!-- Fenêtre de suppression -->
|
|
126
|
+
<confirmation
|
|
127
|
+
v-if="props.btnSupprimer"
|
|
128
|
+
ref="modaleSupprimer"
|
|
129
|
+
:texte="supprimerTexte"
|
|
130
|
+
:titre="supprimerTitreTexte"
|
|
131
|
+
:largeur="modaleSupprimerLargeur"
|
|
132
|
+
@confirmer="supprimer"
|
|
133
|
+
/>
|
|
134
|
+
<modale-choix
|
|
135
|
+
ref="modaleChoix"
|
|
136
|
+
v-if="permettreChoixColonnes"
|
|
137
|
+
:urlbase="urlbase"
|
|
138
|
+
:formulaire-id="formulaireId"
|
|
139
|
+
:identifiant="identifiant"
|
|
140
|
+
:colonnes="colonnesPourChoix"
|
|
141
|
+
:choix-origine="choixOrigineNormalise"
|
|
142
|
+
@selection="selectionChoix"
|
|
143
|
+
@sauvegarde="sauvegardeChoix"
|
|
144
|
+
/>
|
|
145
|
+
</v-col>
|
|
146
|
+
</v-row>
|
|
147
|
+
</div>
|
|
148
|
+
</template>
|
|
149
|
+
<script setup lang="ts">
|
|
150
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
151
|
+
import { computed, onMounted, ref, type PropType, type Slots, defineAsyncComponent } from 'vue'
|
|
152
|
+
import { useI18n } from 'vue-i18n'
|
|
153
|
+
import type { SortItem } from 'vuetify/lib/components/VDataTable/composables/sort.mjs'
|
|
154
|
+
|
|
155
|
+
import Recherche from '../csqcRecherche.vue'
|
|
156
|
+
import confirmation from '../csqcConfirmation.vue'
|
|
157
|
+
import axios from '../../outils/appAxios'
|
|
158
|
+
import Colonne from '../../modeles/composants/datatableColonne'
|
|
159
|
+
const ModaleChoix = defineAsyncComponent(() => import('./csqcTableModaleChoixColonnes.vue'))
|
|
160
|
+
const exportExcelComponent = defineAsyncComponent(() => import('./csqcTableExportExcel.vue'))
|
|
161
|
+
|
|
162
|
+
type VueColonnes = {
|
|
163
|
+
nomVue: string
|
|
164
|
+
colonnes: string[]
|
|
165
|
+
defaut?: boolean
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const slots = defineSlots<Slots>()
|
|
169
|
+
const emit = defineEmits(['ajouter', 'cliqueLigne', 'supprimer', 'modifier', 'donneesExportees', 'panneau:etat'])
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Props
|
|
173
|
+
// ============================================================================
|
|
174
|
+
const props = defineProps({
|
|
175
|
+
barreHautAfficher: { type: Boolean, default: true },
|
|
176
|
+
|
|
177
|
+
btnAjouter: { type: Boolean, default: true },
|
|
178
|
+
btnAjouterTexte: { type: String, default: '' },
|
|
179
|
+
btnModifier: { type: Boolean, default: true },
|
|
180
|
+
btnSupprimer: { type: Boolean, default: true },
|
|
181
|
+
|
|
182
|
+
chargementListe: { type: Boolean, default: false },
|
|
183
|
+
operationEnCours: { type: Boolean, default: false },
|
|
184
|
+
|
|
185
|
+
// Headers Vuetify
|
|
186
|
+
colonnes: {
|
|
187
|
+
type: Array as PropType<Colonne[]>,
|
|
188
|
+
default: () => [],
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
densite: { type: String as PropType<'default' | 'comfortable' | 'compact'>, default: 'default' },
|
|
192
|
+
|
|
193
|
+
excel: { type: Boolean, default: false },
|
|
194
|
+
excelNomFichier: { type: String, default: 'csqc' },
|
|
195
|
+
|
|
196
|
+
// Choix colonnes
|
|
197
|
+
formulaireId: { type: Number, default: -1 },
|
|
198
|
+
identifiant: { type: String, default: '' },
|
|
199
|
+
urlbase: { type: String, default: '' },
|
|
200
|
+
permettreChoixColonnes: { type: Boolean, default: false },
|
|
201
|
+
|
|
202
|
+
// Table
|
|
203
|
+
itemKey: { type: String, default: 'id' },
|
|
204
|
+
itemsParPage: { type: Number, default: 10 },
|
|
205
|
+
itemsParPageOptions: { type: Array, default: () => [10, 25, 30, -1] },
|
|
206
|
+
liste: {
|
|
207
|
+
type: Array as PropType<Record<string, any>[]>,
|
|
208
|
+
default: () => [],
|
|
209
|
+
},
|
|
210
|
+
hover: { type: Boolean, default: false },
|
|
211
|
+
|
|
212
|
+
// Recherche
|
|
213
|
+
rechercheTexte: { type: String, default: '' },
|
|
214
|
+
rechercheAfficher: { type: Boolean, default: true },
|
|
215
|
+
rechercheAvancee: { type: Boolean, default: false },
|
|
216
|
+
rechercheAvanceeTexte: { type: String, default: '' },
|
|
217
|
+
rechercheAvanceeLargeur: { type: Number, default: 12 },
|
|
218
|
+
rechercheAvanceeStyle: { type: String, default: '' },
|
|
219
|
+
|
|
220
|
+
// Modale suppression
|
|
221
|
+
modaleSupprimerChamp: { type: String, default: '' },
|
|
222
|
+
modaleSupprimerTexte: { type: String, default: '' },
|
|
223
|
+
modaleSupprimerTitre: { type: String, default: '' },
|
|
224
|
+
modaleSupprimerLargeur: { type: String, default: '525px' },
|
|
225
|
+
|
|
226
|
+
// Tri
|
|
227
|
+
triDescDepart: { type: Boolean, default: false },
|
|
228
|
+
triParDepart: {
|
|
229
|
+
type: [Array, String] as PropType<string | string[] | SortItem[] | undefined>,
|
|
230
|
+
default: undefined,
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const { t } = useI18n({ useScope: 'global' })
|
|
235
|
+
const recherche = ref<string>('')
|
|
236
|
+
|
|
237
|
+
// Modale suppression (on garde l’item sélectionné)
|
|
238
|
+
const itemSelectionne = ref<any>(null)
|
|
239
|
+
|
|
240
|
+
// ============================================================================
|
|
241
|
+
// State - Choix de colonnes (vues sauvegardées) + sélection courante
|
|
242
|
+
// ============================================================================
|
|
243
|
+
const modaleChoix = ref<InstanceType<typeof ModaleChoix> | null>(null)
|
|
244
|
+
const openChoixColonnes = ref(false)
|
|
245
|
+
const choix = ref<VueColonnes[]>([]) // la liste des vues disponibles
|
|
246
|
+
const vueActive = ref<VueColonnes | null>(null) // optionnel: la vue "appliquée"
|
|
247
|
+
const valeurSelectionChoixColonnes = ref<string | null>(null) // nomVue sélectionné dans la modale / UI
|
|
248
|
+
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Charger le choix de colonnes sauvegardé s'il y a lieu
|
|
251
|
+
// ============================================================================
|
|
252
|
+
onMounted(async () => {
|
|
253
|
+
if (!props.permettreChoixColonnes) return
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const res: any = await axios
|
|
257
|
+
.getAxios()
|
|
258
|
+
.get(`${props.urlbase}/api/ComposantUI/Colonnes/${props.formulaireId}/Identifiant/${props.identifiant}`)
|
|
259
|
+
|
|
260
|
+
const payload = res?.data ?? res
|
|
261
|
+
|
|
262
|
+
if (!payload) {
|
|
263
|
+
choix.value = []
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
choix.value = typeof payload === 'string' ? JSON.parse(payload) : payload
|
|
268
|
+
} catch (e) {
|
|
269
|
+
choix.value = []
|
|
270
|
+
// console.debug(e)
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// Computed - Normaliser pour la modale Choix colonne
|
|
276
|
+
// ============================================================================
|
|
277
|
+
const choixOrigineNormalise = computed(() =>
|
|
278
|
+
(choix.value ?? []).map(c => ({
|
|
279
|
+
_id: '',
|
|
280
|
+
nomVue: c.nomVue,
|
|
281
|
+
colonnes: c.colonnes ?? [],
|
|
282
|
+
defaut: c.defaut ?? false,
|
|
283
|
+
})),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// Actions - Modale choix colonnes
|
|
288
|
+
// ============================================================================
|
|
289
|
+
function ouvrirChoixColonnes() {
|
|
290
|
+
modaleChoix.value?.ouvrir()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function appliquerVue(vue: VueColonnes) {
|
|
294
|
+
vueActive.value = vue
|
|
295
|
+
openChoixColonnes.value = false
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function selectionChoix(vue: VueColonnes) {
|
|
299
|
+
// on garde une trace du nom sélectionné (pour retrouver la vue plus tard)
|
|
300
|
+
valeurSelectionChoixColonnes.value = vue.nomVue
|
|
301
|
+
appliquerVue(vue)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function sauvegardeChoix(vues: VueColonnes[]) {
|
|
305
|
+
if (!props.permettreChoixColonnes) return
|
|
306
|
+
|
|
307
|
+
// si la sélection courante n’existe plus, on reset
|
|
308
|
+
if (
|
|
309
|
+
valeurSelectionChoixColonnes.value != null &&
|
|
310
|
+
vues.find(x => x.nomVue === valeurSelectionChoixColonnes.value) == null
|
|
311
|
+
) {
|
|
312
|
+
valeurSelectionChoixColonnes.value = null
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
choix.value = vues
|
|
316
|
+
|
|
317
|
+
// optionnel: appliquer automatiquement la vue par défaut
|
|
318
|
+
const defaut = vues.find(v => v.defaut)
|
|
319
|
+
if (defaut) appliquerVue(defaut)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ============================================================================
|
|
323
|
+
// Computed - Colonnes à envoyer à la modale (liste ordonnée + libellé)
|
|
324
|
+
// ============================================================================
|
|
325
|
+
const colonnesPourChoix = computed(() =>
|
|
326
|
+
props.colonnes
|
|
327
|
+
// on exclut l'action (souvent toujours visible) + colonnes "cachées"
|
|
328
|
+
.filter(c => c.key && c.key !== 'action' && (c as any).align !== 'd-none')
|
|
329
|
+
.map((c, idx) => ({
|
|
330
|
+
title: (c as any).title ?? (c as any).text ?? String((c as any).key ?? (c as any).value),
|
|
331
|
+
value: String((c as any).key ?? (c as any).value), // ← on force string
|
|
332
|
+
ordre: idx + 1,
|
|
333
|
+
})),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
// ============================================================================
|
|
337
|
+
// Computed - Headers réellement affichés par la table (selon le choix actif)
|
|
338
|
+
// ============================================================================
|
|
339
|
+
const colonnesAffichees = computed(() => {
|
|
340
|
+
// 1) colonnes "disponibles" (on enlève celles explicitement cachées)
|
|
341
|
+
const colonnesFiltre = props.colonnes.filter(c => (c as any).align !== 'd-none')
|
|
342
|
+
|
|
343
|
+
// 2) si feature OFF ou pas de vues sauvegardées => on retourne tout
|
|
344
|
+
if (!props.permettreChoixColonnes) return colonnesFiltre
|
|
345
|
+
if (!choix.value?.length) return colonnesFiltre
|
|
346
|
+
|
|
347
|
+
// 3) retrouver la vue sélectionnée (sinon la défaut)
|
|
348
|
+
const choixSelection = choix.value.find(
|
|
349
|
+
x => valeurSelectionChoixColonnes.value === x.nomVue || (!valeurSelectionChoixColonnes.value && x.defaut),
|
|
350
|
+
)
|
|
351
|
+
if (!choixSelection) return colonnesFiltre
|
|
352
|
+
|
|
353
|
+
// 4) reconstruire la liste de headers dans l’ordre sauvegardé
|
|
354
|
+
return choixSelection.colonnes.map(k => colonnesFiltre.find(c => String(c.key) === k)).filter(Boolean) as Colonne[]
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
// ============================================================================
|
|
358
|
+
// Computed - Tri initial (Vuetify sort-by)
|
|
359
|
+
// ============================================================================
|
|
360
|
+
const triDepart = computed<SortItem[] | undefined>(() => {
|
|
361
|
+
if (props.triParDepart == null) return undefined
|
|
362
|
+
|
|
363
|
+
const ordre = props.triDescDepart ? 'desc' : 'asc'
|
|
364
|
+
if (typeof props.triParDepart === 'string') return [{ key: props.triParDepart, order: ordre }]
|
|
365
|
+
|
|
366
|
+
const retour: SortItem[] = []
|
|
367
|
+
for (let i = 0; i < props.triParDepart.length; i += 1) {
|
|
368
|
+
const tri = props.triParDepart[i]!
|
|
369
|
+
if (typeof tri === 'string') retour.push({ key: tri, order: ordre })
|
|
370
|
+
else retour.push(tri)
|
|
371
|
+
}
|
|
372
|
+
return retour
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const estMultiTriActif = computed(() => {
|
|
376
|
+
if (props.triParDepart == null) return false
|
|
377
|
+
if (typeof props.triParDepart === 'string') return false
|
|
378
|
+
return true
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Computed - Export (filtrage local basé sur recherche)
|
|
383
|
+
// ============================================================================
|
|
384
|
+
const filteredItems = computed(() => {
|
|
385
|
+
if (!recherche.value) return props.liste
|
|
386
|
+
const q = recherche.value.toLowerCase()
|
|
387
|
+
return props.liste.filter(item => Object.values(item).some(val => String(val).toLowerCase().includes(q)))
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
// ============================================================================
|
|
391
|
+
// UI Actions - Recherche / Table events
|
|
392
|
+
// ============================================================================
|
|
393
|
+
function chargerRecherche(val: string) {
|
|
394
|
+
recherche.value = val
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function ajouter() {
|
|
398
|
+
emit('ajouter')
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function cliqueLigne(_e: Event, { item }: { item: any }) {
|
|
402
|
+
emit('cliqueLigne', item)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function modifier(item: any) {
|
|
406
|
+
emit('modifier', item)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ============================================================================
|
|
410
|
+
// Suppression - Texte + actions
|
|
411
|
+
// ============================================================================
|
|
412
|
+
const supprimerTexte = computed(() => {
|
|
413
|
+
if (itemSelectionne.value == null) return ''
|
|
414
|
+
|
|
415
|
+
if (props.modaleSupprimerTexte) return props.modaleSupprimerTexte
|
|
416
|
+
|
|
417
|
+
return t('csqc.message.supprimerMessage', {
|
|
418
|
+
nom: itemSelectionne?.value?.[props.modaleSupprimerChamp] ?? '',
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
const supprimerTitreTexte = computed(() => props.modaleSupprimerTitre || t('csqc.message.supprimerTitre'))
|
|
423
|
+
|
|
424
|
+
function ouvrirModaleSupprimer(item: any) {
|
|
425
|
+
itemSelectionne.value = item
|
|
426
|
+
emit('supprimer', itemSelectionne.value) // tu sembles gérer la modale ailleurs
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function supprimer() {
|
|
430
|
+
emit('supprimer', itemSelectionne.value)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ============================================================================
|
|
434
|
+
// UI - Panneau recherche avancée
|
|
435
|
+
// ============================================================================
|
|
436
|
+
function onPanelChange(val: boolean) {
|
|
437
|
+
emit('panneau:etat', val)
|
|
438
|
+
}
|
|
439
|
+
</script>
|
|
440
|
+
|
|
441
|
+
<style scoped lang="css">
|
|
442
|
+
.ControlesDatatable {
|
|
443
|
+
gap: 4px;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/* barre de recherche design QC*/
|
|
447
|
+
.BarreRecherche {
|
|
448
|
+
border-radius: 4px 0 0 4px !important;
|
|
449
|
+
min-height: 40px;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.BarreRechercheBackIcone {
|
|
453
|
+
border-right: 1px solid #808a9d;
|
|
454
|
+
border-top: 1px solid #808a9d;
|
|
455
|
+
border-bottom: 1px solid #808a9d;
|
|
456
|
+
background-color: #095797 !important;
|
|
457
|
+
height: 40px !important;
|
|
458
|
+
width: 40px !important;
|
|
459
|
+
max-width: 40px !important;
|
|
460
|
+
min-width: 40px !important;
|
|
461
|
+
padding-right: 0 !important;
|
|
462
|
+
padding-left: 0 !important;
|
|
463
|
+
border-radius: 0 4px 4px 0 !important;
|
|
464
|
+
margin-left: -16px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/* icone loupe */
|
|
468
|
+
.BarreRechercheIcone {
|
|
469
|
+
font-size: 34px !important;
|
|
470
|
+
margin-left: 1px !important;
|
|
471
|
+
margin-top: 2px !important;
|
|
472
|
+
color: white !important;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* datatable contour */
|
|
476
|
+
.v-data-table {
|
|
477
|
+
border: 1px solid #d3d3d3;
|
|
478
|
+
border-radius: 5px;
|
|
479
|
+
overflow: hidden;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* hover */
|
|
483
|
+
.v-data-table:hover {
|
|
484
|
+
cursor: pointer !important;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* datatable row */
|
|
488
|
+
.v-data-table .v-table__wrapper > table > thead > tr > td,
|
|
489
|
+
.v-data-table .v-table__wrapper > table > thead > tr th,
|
|
490
|
+
.v-data-table .v-table__wrapper > table tbody > tr > td,
|
|
491
|
+
.v-data-table .v-table__wrapper > table tbody > tr th {
|
|
492
|
+
background-color: #d3d3d375 !important;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* datatable header contour */
|
|
496
|
+
.v-data-table .v-table__wrapper > table > thead > tr > th,
|
|
497
|
+
.v-data-table .v-table__wrapper > table tbody > tr > th {
|
|
498
|
+
background-color: white !important;
|
|
499
|
+
border-bottom: 4px #223654 solid !important;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* datatable header intérieur*/
|
|
503
|
+
.v-data-table-header__content {
|
|
504
|
+
background-color: white !important;
|
|
505
|
+
color: #223654 !important;
|
|
506
|
+
font-weight: 600;
|
|
507
|
+
font-size: 15px;
|
|
508
|
+
height: 46px;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* datatable footer */
|
|
512
|
+
.v-data-table-footer {
|
|
513
|
+
background-color: white !important;
|
|
514
|
+
border-top: 4px #223654 solid !important;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* datatable row hover */
|
|
518
|
+
.v-data-table .v-table__wrapper > table tbody > tr:hover > td,
|
|
519
|
+
.v-data-table .v-table__wrapper > table tbody > tr:hover {
|
|
520
|
+
background-color: #e0e0e0 !important;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.iconeSupprimer:hover {
|
|
524
|
+
color: red;
|
|
525
|
+
}
|
|
526
|
+
.v-icon.v-icon.v-icon--link.iconeSupprimer:hover {
|
|
527
|
+
color: red !important;
|
|
528
|
+
}
|
|
529
|
+
.iconeEditer:hover {
|
|
530
|
+
color: #095797;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/* hover icone à droite du ajouter*/
|
|
534
|
+
.text-grisMoyen:hover {
|
|
535
|
+
color: #095797 !important;
|
|
536
|
+
}
|
|
537
|
+
</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codevdesign",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.45",
|
|
4
4
|
"description": "Composants Vuetify 3 pour les projets Codev",
|
|
5
5
|
"files": [
|
|
6
6
|
"./**/*.vue",
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
"build": "vite build"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"vuetify": "^3.
|
|
17
|
-
"vue-i18n": "^11.
|
|
18
|
-
"
|
|
19
|
-
"tinymce": "^
|
|
20
|
-
"tinymce-
|
|
21
|
-
"
|
|
16
|
+
"vuetify": "^3.11.2",
|
|
17
|
+
"vue-i18n": "^11.2.1",
|
|
18
|
+
"tinymce": "^8.3.2",
|
|
19
|
+
"tinymce-i18n": "^26.1.12",
|
|
20
|
+
"@tinymce/tinymce-vue": "^6.3.0",
|
|
21
|
+
"vue": "^3.5.18",
|
|
22
|
+
"sortablejs": "^1.15.6",
|
|
23
|
+
"@e965/xlsx": "^0.20.3"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
26
|
"@types/node": "^22.13.5",
|
|
@@ -26,7 +28,5 @@
|
|
|
26
28
|
"typescript": "^5.8.3",
|
|
27
29
|
"vite": "^7.0.0"
|
|
28
30
|
},
|
|
29
|
-
"peerDependencies": {
|
|
30
|
-
"vue": "^3.5.0"
|
|
31
|
-
}
|
|
31
|
+
"peerDependencies": {}
|
|
32
32
|
}
|