22q-dialog(
33 ref ="dialogRef" ,
44 @hide ="onDialogHide" ,
5+ transition-show ="scale" ,
6+ transition-hide ="scale" ,
57)
6- q-card.q-dialog-plugin
7- q-card-section
8- .text-h6 Suppression en masse des identités
9- q-card-section {{ mainText }}
10- q-card-actions
11- q-space
12- q-btn(
13- color ="positive" ,
14- label ="Valider" ,
15- @click ="syncIdentities"
8+ q-card.identity-modal-card ( flat bordered )
9+ q-card-section.identity-modal-header.row.items-center.no-wrap.bg-negative.text-white
10+ q-avatar( round color ="white" text-color ="negative" icon ="mdi-delete-alert" size ="32px" )
11+ .column.q-ml-md.col
12+ .text-h6.text-weight-medium Suppression en masse
13+ .text-caption ( style ="opacity: 0.92" ) Les identités listées seront mises dans la corbeille
14+ q-separator
15+ q-card-section.identity-modal-body
16+ .identity-modal-body-top
17+ p.text-body2.identity-modal-lede {{ mainText }}
18+ .row.items-center.no-wrap.q-mb-sm
19+ q-icon( color ="negative" name ="mdi-account-multiple-outline" size ="22px" )
20+ .text-subtitle1.q-ml-sm.text-weight-medium Identités concernées
21+ q-space
22+ q-badge( color ="negative" text-color ="white" rounded ) {{ selectedRows.length }}
23+ q-banner(
24+ v-if ="selectedRows.length === 0"
25+ dense
26+ rounded
27+ class ="bg-amber-2 text-dark"
28+ )
29+ span Aucune identité dans la sélection.
30+ .identity-modal-list-wrap ( v-if ="selectedRows.length > 0" : class = "listWrapClass" )
31+ q-list( dense separator padding )
32+ q-item.identity-modal-list-item (
33+ v-for ="item in identityListItems"
34+ :key ="item.key"
35+ )
36+ q-item-section( side )
37+ q-avatar( :color ="item.avatarColor" text-color ="white" size ="36px" ) {{ item.initials }}
38+ q-item-section
39+ q-item-label.text-weight-medium ( lines ="2" ) {{ item.label }}
40+ q-item-label.text-caption.text-grey-6 ( lines ="1" style ="font-family: ui-monospace, monospace" ) {{ item.idShort }}
41+ q-card-actions.identity-modal-actions ( align ="right" )
42+ q-btn.identity-modal-btn-cancel (
43+ outline
44+ color ="grey-8"
45+ no-caps
46+ padding ="sm lg"
47+ label ="Annuler"
48+ @click ="cancelSync"
1649 )
1750 q-btn(
18- color ="negative" ,
19- label ="Annuler" ,
20- @click ="cancelSync"
51+ unelevated
52+ no-caps
53+ padding ="sm lg"
54+ color ="negative"
55+ icon-right ="mdi-check"
56+ label ="Supprimer"
57+ :disable ="selectedRows.length === 0"
58+ @click ="syncIdentities"
2159 )
2260</template >
2361
2462<script lang="ts" setup>
25- import { ref , computed } from ' vue'
26- import { useDialogPluginComponent } from ' quasar'
63+ import { computed } from ' vue'
64+ import { useDialogPluginComponent , useQuasar } from ' quasar'
2765
2866const props = defineProps ({
2967 selectedIdentities: {
@@ -34,17 +72,190 @@ const props = defineProps({
3472
3573defineEmits ([... useDialogPluginComponent .emits ])
3674
37- const mainText = computed (() => ` Vous êtes sur le point de supprimer ${props .selectedIdentities .length } identités. Voulez-vous continuer ? ` )
75+ const AVATAR_COLORS = [' orange-8' , ' teal' , ' indigo' , ' deep-orange' , ' purple' , ' cyan' , ' brown' ]
76+
77+ function idToString(id : unknown ): string {
78+ if (id == null ) return ' '
79+ if (typeof id === ' string' ) return id
80+ if (typeof id === ' object' && id !== null && ' $oid' in (id as Record <string , unknown >)) {
81+ return String ((id as Record <string , unknown >).$oid )
82+ }
83+ return String (id )
84+ }
85+
86+ function strOrJoin(v : unknown ): string {
87+ if (v == null ) return ' '
88+ if (typeof v === ' string' ) return v .trim ()
89+ if (Array .isArray (v )) {
90+ return v
91+ .map ((x ) => (typeof x === ' string' ? x .trim () : x != null ? String (x ).trim () : ' ' ))
92+ .filter (Boolean )
93+ .join (' ' )
94+ }
95+ return String (v ).trim ()
96+ }
97+
98+ function buildIdentityLabel(row : Record <string , unknown >): string {
99+ const p = row ?.inetOrgPerson as Record <string , unknown > | undefined
100+ if (p && typeof p === ' object' ) {
101+ const fromCn = strOrJoin (p .cn )
102+ if (fromCn ) return fromCn
103+ const dn = strOrJoin (p .displayName )
104+ if (dn ) return dn
105+ const gn = strOrJoin (p .givenName )
106+ const sn = strOrJoin (p .sn )
107+ if (gn || sn ) return [gn , sn ].filter (Boolean ).join (' ' )
108+ const mail = strOrJoin (p .mail )
109+ if (mail ) return mail
110+ const uid = strOrJoin (p .uid )
111+ if (uid ) return uid
112+ }
113+ const id = idToString (row ?._id )
114+ if (id ) return ` Identité ${id .length > 14 ? ` ${id .slice (0 , 14 )}… ` : id } `
115+ return ' Identité (sans identifiant)'
116+ }
117+
118+ function initialsFromLabel(label : string ): string {
119+ const t = label .trim ()
120+ if (! t ) return ' ?'
121+ const parts = t .split (/ \s + / ).filter (Boolean )
122+ if (parts .length >= 2 ) {
123+ const a = parts [0 ][0 ] || ' '
124+ const b = parts [1 ][0 ] || ' '
125+ return ` ${a }${b } ` .toUpperCase ()
126+ }
127+ if (t .length >= 2 ) return t .slice (0 , 2 ).toUpperCase ()
128+ return t .charAt (0 ).toUpperCase ()
129+ }
130+
131+ function shortId(id : string ): string {
132+ if (! id ) return ' —'
133+ if (id .length <= 22 ) return id
134+ return ` ${id .slice (0 , 10 )}…${id .slice (- 8 )} `
135+ }
136+
137+ const selectedRows = computed (() => {
138+ const raw = props .selectedIdentities
139+ if (! Array .isArray (raw )) return [] as Record <string , unknown >[]
140+ return raw as Record <string , unknown >[]
141+ })
142+
143+ /* eslint-disable @typescript-eslint/no-unused-vars -- template Pug */
144+ const $q = useQuasar ()
145+
146+ const listWrapClass = computed (() => ($q .dark .isActive ? ' identity-modal-list-wrap--dark' : ' identity-modal-list-wrap--light' ))
147+
148+ const identityListItems = computed (() => {
149+ return selectedRows .value .map ((row , idx ) => {
150+ const label = buildIdentityLabel (row )
151+ const id = idToString (row ?._id )
152+ return {
153+ key: ` ${id || ' noid' }_${idx } ` ,
154+ label ,
155+ idShort: shortId (id ),
156+ initials: initialsFromLabel (label ),
157+ avatarColor: AVATAR_COLORS [idx % AVATAR_COLORS .length ],
158+ }
159+ })
160+ })
161+
162+ const mainText = computed (
163+ () =>
164+ ` Vous allez supprimer ${selectedRows .value .length } identité${selectedRows .value .length > 1 ? ' s' : ' ' }. Cette action est irréversible. Vérifiez la liste puis confirmez. ` ,
165+ )
166+
167+ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent ()
38168
39169const syncIdentities = () => {
40- console .log (' syncIdentities' )
41170 onDialogOK ({ success: true })
42171}
43172
44173const cancelSync = () => {
45- console .log (' cancelSync' )
46174 onDialogCancel ()
47175}
48-
49- const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent ()
176+ /* eslint-enable @typescript-eslint/no-unused-vars */
50177 </script >
178+
179+ <style scoped>
180+ .identity-modal-card {
181+ min-width : 340px ;
182+ max-width : min (520px , 96vw );
183+ display : flex ;
184+ flex-direction : column ;
185+ max-height : min (580px , 88vh );
186+ border-radius : 12px ;
187+ overflow : hidden ;
188+ box-shadow : 0 12px 40px rgba (0 , 0 , 0 , 0.14 );
189+ }
190+
191+ .body--dark .identity-modal-card {
192+ box-shadow : 0 12px 40px rgba (0 , 0 , 0 , 0.45 );
193+ }
194+
195+ .identity-modal-header {
196+ padding : 1rem 1.25rem ;
197+ }
198+
199+ .identity-modal-body {
200+ flex : 1 1 auto ;
201+ min-height : 0 ;
202+ display : flex ;
203+ flex-direction : column ;
204+ overflow : hidden ;
205+ padding-top : 1rem ;
206+ padding-bottom : 0.5rem ;
207+ }
208+
209+ .identity-modal-body-top {
210+ flex-shrink : 0 ;
211+ }
212+
213+ .identity-modal-lede {
214+ line-height : 1.55 ;
215+ margin-bottom : 1rem ;
216+ opacity : 0.92 ;
217+ }
218+
219+ .identity-modal-list-wrap--light {
220+ background : rgba (0 , 0 , 0 , 0.03 );
221+ border : 1px solid rgba (0 , 0 , 0 , 0.08 );
222+ }
223+
224+ .identity-modal-list-wrap--dark {
225+ background : rgba (255 , 255 , 255 , 0.06 );
226+ border : 1px solid rgba (255 , 255 , 255 , 0.12 );
227+ }
228+
229+ .identity-modal-list-wrap--light ,
230+ .identity-modal-list-wrap--dark {
231+ flex : 1 1 auto ;
232+ min-height : 0 ;
233+ border-radius : 10px ;
234+ overflow-x : hidden ;
235+ overflow-y : auto ;
236+ }
237+
238+ .identity-modal-list-item {
239+ border-radius : 8px ;
240+ margin-bottom : 2px ;
241+ transition : background-color 0.15s ease ;
242+ }
243+
244+ .identity-modal-list-wrap--light .identity-modal-list-item :hover {
245+ background-color : rgba (0 , 0 , 0 , 0.04 );
246+ }
247+
248+ .identity-modal-list-wrap--dark .identity-modal-list-item :hover {
249+ background-color : rgba (255 , 255 , 255 , 0.06 );
250+ }
251+
252+ .identity-modal-actions {
253+ padding : 0.5rem 1rem 1rem ;
254+ background : rgba (0 , 0 , 0 , 0.02 );
255+ }
256+
257+ .body--dark .identity-modal-actions {
258+ background : rgba (255 , 255 , 255 , 0.04 );
259+ }
260+
261+ </style >
0 commit comments