Skip to content

Commit 07d3c68

Browse files
committed
feat: enhance mail template modal with improved identity display and selection features
- Updated the mail template modal to include a more informative display of selected identities. - Introduced a new layout for displaying recipient information, including a badge for the count of selected identities. - Added computed properties for managing identity labels and selection states, improving user experience. - Refactored checkbox handling for sending emails to selected identities, ensuring clarity in the UI.
1 parent 1ca9644 commit 07d3c68

2 files changed

Lines changed: 210 additions & 34 deletions

File tree

apps/web/src/components/pages/identities/modals/mail-template.vue

Lines changed: 204 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,7 @@ q-dialog(
2323
)
2424
template(#before)
2525
.q-pa-md.overflow-auto(style="height: 100%;")
26-
p.text-body2(v-if="showSendAll") {{ introText }}
27-
q-checkbox(
28-
v-if="showSendAll"
29-
v-model="initAllIdentities"
30-
:label="checkboxLabel"
31-
color="teal-7"
32-
dense
33-
)
34-
q-separator.q-my-md(v-if="showSendAll")
35-
26+
p.text-body2.identity-modal-lede.q-mb-md {{ mainText }}
3627
q-select(
3728
v-model="templateName"
3829
:options="templates"
@@ -125,6 +116,42 @@ q-dialog(
125116
@click="addVar"
126117
)
127118

119+
q-separator.q-my-md
120+
.row.items-center.no-wrap.q-mb-sm
121+
q-icon(color="teal-7" name="mdi-account-multiple-outline" size="22px")
122+
.text-subtitle1.q-ml-sm.text-weight-medium Destinataires
123+
q-space
124+
q-badge(color="teal-7" text-color="white" rounded) {{ selectedRows.length }}
125+
q-banner(
126+
v-if="selectedRows.length === 0"
127+
dense
128+
rounded
129+
class="bg-amber-2 text-dark"
130+
)
131+
span Aucune identité dans la sélection.
132+
.identity-modal-list-wrap.mail-template-dest-in-form(
133+
v-if="selectedRows.length > 0"
134+
:class="listWrapClass"
135+
)
136+
q-list(dense separator padding)
137+
q-item.identity-modal-list-item(
138+
v-for="item in identityListItems"
139+
:key="item.key"
140+
)
141+
q-item-section(side)
142+
q-avatar(:color="item.avatarColor" text-color="white" size="36px") {{ item.initials }}
143+
q-item-section
144+
q-item-label.text-weight-medium(lines="2") {{ item.label }}
145+
q-item-label.text-caption.text-grey-6(lines="1" style="font-family: ui-monospace, monospace") {{ item.idShort }}
146+
template(v-if="showSendAll")
147+
q-separator.q-my-md
148+
q-checkbox(
149+
v-model="initAllIdentities"
150+
:label="checkboxLabel"
151+
color="teal-7"
152+
dense
153+
)
154+
128155
template(#after)
129156
.bg-grey-1.column(style="min-height: 0; height: 100%; overflow: hidden;")
130157
q-bar(flat)
@@ -154,7 +181,8 @@ q-dialog(
154181

155182
.column(v-else style="min-height: 0; width: 100%;")
156183
.col.q-pa-md.overflow-auto(style="min-height: 0;")
157-
p.text-body2.text-weight-medium.q-mb-sm {{ mainText }}
184+
p.text-body2.identity-modal-lede.q-mb-md {{ mainText }}
185+
q-separator.q-my-md
158186
q-select(
159187
v-model="templateName"
160188
:options="templates"
@@ -248,13 +276,40 @@ q-dialog(
248276
)
249277

250278
q-separator.q-my-md
251-
q-checkbox(
252-
v-if="showSendAll"
253-
v-model="initAllIdentities"
254-
:label="checkboxLabel"
255-
color="teal-7"
279+
.row.items-center.no-wrap.q-mb-sm
280+
q-icon(color="teal-7" name="mdi-account-multiple-outline" size="22px")
281+
.text-subtitle1.q-ml-sm.text-weight-medium Destinataires
282+
q-space
283+
q-badge(color="teal-7" text-color="white" rounded) {{ selectedRows.length }}
284+
q-banner(
285+
v-if="selectedRows.length === 0"
256286
dense
287+
rounded
288+
class="bg-amber-2 text-dark"
257289
)
290+
span Aucune identité dans la sélection.
291+
.identity-modal-list-wrap.mail-template-dest-in-form(
292+
v-if="selectedRows.length > 0"
293+
:class="listWrapClass"
294+
)
295+
q-list(dense separator padding)
296+
q-item.identity-modal-list-item(
297+
v-for="item in identityListItems"
298+
:key="item.key"
299+
)
300+
q-item-section(side)
301+
q-avatar(:color="item.avatarColor" text-color="white" size="36px") {{ item.initials }}
302+
q-item-section
303+
q-item-label.text-weight-medium(lines="2") {{ item.label }}
304+
q-item-label.text-caption.text-grey-6(lines="1" style="font-family: ui-monospace, monospace") {{ item.idShort }}
305+
template(v-if="showSendAll")
306+
q-separator.q-my-md
307+
q-checkbox(
308+
v-model="initAllIdentities"
309+
:label="checkboxLabel"
310+
color="teal-7"
311+
dense
312+
)
258313

259314
q-separator
260315
q-card-actions.identity-modal-actions(align="right")
@@ -301,21 +356,99 @@ const props = defineProps({
301356
302357
defineEmits([...useDialogPluginComponent.emits])
303358
359+
const AVATAR_COLORS = ['orange-8', 'teal', 'indigo', 'deep-orange', 'purple', 'cyan', 'brown']
360+
361+
function idToString(id: unknown): string {
362+
if (id == null) return ''
363+
if (typeof id === 'string') return id
364+
if (typeof id === 'object' && id !== null && '$oid' in (id as Record<string, unknown>)) {
365+
return String((id as Record<string, unknown>).$oid)
366+
}
367+
return String(id)
368+
}
369+
370+
function strOrJoin(v: unknown): string {
371+
if (v == null) return ''
372+
if (typeof v === 'string') return v.trim()
373+
if (Array.isArray(v)) {
374+
return v
375+
.map((x) => (typeof x === 'string' ? x.trim() : x != null ? String(x).trim() : ''))
376+
.filter(Boolean)
377+
.join(' ')
378+
}
379+
return String(v).trim()
380+
}
381+
382+
function buildIdentityLabel(row: Record<string, unknown>): string {
383+
const p = row?.inetOrgPerson as Record<string, unknown> | undefined
384+
if (p && typeof p === 'object') {
385+
const fromCn = strOrJoin(p.cn)
386+
if (fromCn) return fromCn
387+
const dn = strOrJoin(p.displayName)
388+
if (dn) return dn
389+
const gn = strOrJoin(p.givenName)
390+
const sn = strOrJoin(p.sn)
391+
if (gn || sn) return [gn, sn].filter(Boolean).join(' ')
392+
const mail = strOrJoin(p.mail)
393+
if (mail) return mail
394+
const uid = strOrJoin(p.uid)
395+
if (uid) return uid
396+
}
397+
const id = idToString(row?._id)
398+
if (id) return `Identité ${id.length > 14 ? `${id.slice(0, 14)}…` : id}`
399+
return 'Identité (sans identifiant)'
400+
}
401+
402+
function initialsFromLabel(label: string): string {
403+
const t = label.trim()
404+
if (!t) return '?'
405+
const parts = t.split(/\s+/).filter(Boolean)
406+
if (parts.length >= 2) {
407+
const a = parts[0][0] || ''
408+
const b = parts[1][0] || ''
409+
return `${a}${b}`.toUpperCase()
410+
}
411+
if (t.length >= 2) return t.slice(0, 2).toUpperCase()
412+
return t.charAt(0).toUpperCase()
413+
}
414+
415+
function shortId(id: string): string {
416+
if (!id) return ''
417+
if (id.length <= 22) return id
418+
return `${id.slice(0, 10)}…${id.slice(-8)}`
419+
}
420+
304421
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any -- template Pug */
305422
const q = useQuasar()
306423
const splitterModel = ref(42)
307424
308-
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
425+
const selectedRows = computed(() => {
426+
const raw = props.selectedIdentities
427+
if (!Array.isArray(raw)) return [] as Record<string, unknown>[]
428+
return raw as Record<string, unknown>[]
429+
})
309430
310-
const mainText = computed(
311-
() => `Vous êtes sur le point d'envoyer un mail à ${props.selectedIdentities.length} identités "${props.identityTypesName}". Voulez-vous continuer ?`,
312-
)
431+
const listWrapClass = computed(() => (q.dark.isActive ? 'identity-modal-list-wrap--dark' : 'identity-modal-list-wrap--light'))
432+
433+
const identityListItems = computed(() => {
434+
return selectedRows.value.map((row, idx) => {
435+
const label = buildIdentityLabel(row)
436+
const id = idToString(row?._id)
437+
return {
438+
key: `${id || 'noid'}_${idx}`,
439+
label,
440+
idShort: shortId(id),
441+
initials: initialsFromLabel(label),
442+
avatarColor: AVATAR_COLORS[idx % AVATAR_COLORS.length],
443+
}
444+
})
445+
})
313446
314-
const introText = computed(() => {
315-
if (showSendAll.value) {
316-
return `Vous êtes sur le point d'envoyer un mail à ${props.selectedIdentities.length} identités "${props.identityTypesName}". Vous pouvez aussi choisir de l'envoyer à toutes les identités synchronisées (${props.allIdentitiesCount}).`
317-
}
318-
return `Vous êtes sur le point d'envoyer un mail à ${props.selectedIdentities.length} identités "${props.identityTypesName}".`
447+
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
448+
449+
const mainText = computed(() => {
450+
const n = selectedRows.value.length
451+
return `Vous êtes sur le point d'envoyer un mail à ${n} identité${n > 1 ? 's' : ''} « ${props.identityTypesName} ». Voulez-vous continuer ?`
319452
})
320453
321454
const checkboxLabel = computed(() => {
@@ -324,7 +457,7 @@ const checkboxLabel = computed(() => {
324457
325458
const showSendAll = computed(() => {
326459
const total = Number(props.allIdentitiesCount || 0)
327-
const selected = Array.isArray(props.selectedIdentities) ? props.selectedIdentities.length : 0
460+
const selected = selectedRows.value.length
328461
return total > Math.max(selected, 1)
329462
})
330463
@@ -542,7 +675,6 @@ const cancelSync = () => {
542675
}
543676
544677
.mail-template-card > :deep(.q-card__actions) {
545-
margin-top: auto;
546678
flex: 0 0 auto;
547679
width: 100%;
548680
max-width: 100%;
@@ -555,14 +687,58 @@ const cancelSync = () => {
555687
min-width: 0;
556688
width: 100%;
557689
max-width: 100%;
558-
height: 84%;
690+
height: 100%;
691+
}
692+
693+
.mail-template-dest-in-form.identity-modal-list-wrap--light,
694+
.mail-template-dest-in-form.identity-modal-list-wrap--dark {
695+
max-height: min(260px, 40vh);
559696
}
560697
561698
.identity-modal-header {
562699
padding: 1rem 1.25rem;
563700
flex-shrink: 0;
564701
}
565702
703+
.identity-modal-lede {
704+
line-height: 1.55;
705+
margin-bottom: 1rem;
706+
opacity: 0.92;
707+
}
708+
709+
.identity-modal-list-wrap--light {
710+
background: rgba(0, 0, 0, 0.03);
711+
border: 1px solid rgba(0, 0, 0, 0.08);
712+
}
713+
714+
.identity-modal-list-wrap--dark {
715+
background: rgba(255, 255, 255, 0.06);
716+
border: 1px solid rgba(255, 255, 255, 0.12);
717+
}
718+
719+
.identity-modal-list-wrap--light,
720+
.identity-modal-list-wrap--dark {
721+
flex: 1 1 auto;
722+
min-height: 0;
723+
border-radius: 10px;
724+
overflow-x: hidden;
725+
overflow-y: auto;
726+
}
727+
728+
.identity-modal-list-item {
729+
border-radius: 8px;
730+
margin-bottom: 2px;
731+
transition: background-color 0.15s ease;
732+
}
733+
734+
.identity-modal-list-wrap--light .identity-modal-list-item:hover {
735+
background-color: rgba(0, 0, 0, 0.04);
736+
}
737+
738+
.identity-modal-list-wrap--dark .identity-modal-list-item:hover {
739+
background-color: rgba(255, 255, 255, 0.06);
740+
}
741+
566742
.identity-modal-actions {
567743
padding: 0.5rem 1rem 1rem;
568744
background: rgba(0, 0, 0, 0.02);

apps/web/src/pages/identities/table.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -553,19 +553,19 @@ export default defineNuxtComponent({
553553
async sendTemplateMailToIdentities(identities, data: { template?: string; variables?: Record<string, string> }) {
554554
const ids = this.bulkIdsFromIdentities(identities)
555555
try {
556-
const result = (await this.$http.post('/management/mail/sendmany', {
556+
const result = await this.$http.post('/management/mail/sendmany', {
557557
body: {
558558
ids,
559559
template: data?.template,
560560
variables: data?.variables,
561561
...(data?.recipientAddressSource ? { recipientAddressSource: data.recipientAddressSource } : {}),
562562
},
563-
})) as { data?: { sent?: number; skipped?: number } }
564-
565-
const sent = result?.data?.sent ?? 0
566-
const skipped = result?.data?.skipped ?? 0
563+
})
564+
const payload = (result as { _data?: { data?: { sent?: number; skipped?: number } } })._data?.data
565+
const sent = Number(payload?.sent ?? 0)
566+
const skipped = Number(payload?.skipped ?? 0)
567567
this.$q.notify({
568-
message: `Mail envoyé (${sent} envoyé${sent > 1 ? 's' : ''}, ${skipped} ignoré${skipped > 1 ? 's' : ''})`,
568+
message: `Mail(s) envoyé(s)`,
569569
color: skipped > 0 ? 'warning' : 'positive',
570570
})
571571
} catch (error: unknown) {

0 commit comments

Comments
 (0)