Skip to content

Commit 24060fa

Browse files
committed
feat: enhance lifecycle event details display and interaction
- Replaced the dialog for lifecycle event details with a more integrated view using a slide transition. - Introduced a new method to manage the selected lifecycle ID, allowing for better handling of event details. - Improved the layout and user experience by adding loading indicators and error messages for lifecycle details. - Implemented a caching mechanism for JSON data to optimize performance in the editor.
1 parent f72a029 commit 24060fa

File tree

1 file changed

+134
-47
lines changed

1 file changed

+134
-47
lines changed

apps/web/src/pages/identities/table/[_id]/lifecycle.vue

Lines changed: 134 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template lang="pug">
2-
.column.no-wrap.fit(style='height: 100%; overflow: hidden;')
2+
.column.no-wrap.fit(style='height: 100%;')
33
div(style='position: sticky; top: 0; z-index: 1; flex: 0 0 auto;')
44
q-toolbar(:class='[$q.dark.isActive ? "bg-dark" : "bg-white"]')
55
q-toolbar-title Historique des cycles de vie
@@ -53,47 +53,46 @@
5353
@click.stop="openLifecycleDetails(lifecycle)"
5454
)
5555
q-tooltip.text-body2(anchor='top middle' self="bottom middle") Voir le détail de l'événement
56+
q-slide-transition
57+
q-card.q-mt-sm(v-if="selectedLifecycleId === lifecycle?._id" flat bordered)
58+
q-toolbar(bordered dense style="height: 28px; line-height: 28px;")
59+
q-toolbar-title Détails événement cycle de vie
60+
q-space
61+
q-btn(icon="mdi-close" flat round dense @click="closeLifecycleDetails" aria-label="Fermer")
62+
q-bar.bg-transparent(dense style="height: 28px; line-height: 28px;")
63+
q-chip(
64+
v-if="lifecycleDetailsMeta.lifecycleLabel"
65+
dense
66+
size="sm"
67+
:color="lifecycleDetailsMeta.lifecycleColor"
68+
:text-color="lifecycleDetailsMeta.lifecycleTextColor"
69+
:icon="lifecycleDetailsMeta.lifecycleIcon"
70+
)
71+
span {{ lifecycleDetailsMeta.lifecycleLabel }}
72+
q-space
73+
.text-caption.text-grey-7
74+
span(v-if="lifecycleDetailsMeta.actor") Par: {{ lifecycleDetailsMeta.actor }}
75+
span(v-if="lifecycleDetailsMeta.actor && lifecycleDetailsMeta.date") -
76+
span(v-if="lifecycleDetailsMeta.date") Le: {{ lifecycleDetailsMeta.date }}
77+
q-separator
78+
.q-pa-md.row.justify-center(v-if="lifecycleDetailsLoading")
79+
q-spinner-dots(color="primary" size="32px")
80+
q-banner.bg-warning.text-black(v-else-if="!lifecycleDetailsJson")
81+
| Aucun détail exploitable trouvé pour cet événement.
82+
.audit-diff-editor(v-else)
83+
LazyMonacoEditor(
84+
style="height: 45vh; width: 100%"
85+
lang="json"
86+
:model-value="lifecycleDetailsJson"
87+
:options="monacoOptions"
88+
)
5689
q-timeline-entry(
5790
:key="`${group.key}-end-dot`"
5891
color="grey-5"
5992
class="lifecycle-end-entry"
6093
)
6194
q-banner.text-negative.text-center(v-if="empty" dense icon="mdi-flag-off" class="q-my-md")
6295
| Fin de la liste ({{ lifecycles.length }} événement{{ lifecycles.length > 1 ? 's' : '' }}).
63-
64-
q-dialog(v-model="lifecycleDetailsDialogOpen" maximized)
65-
q-card.audit-diff-card
66-
q-toolbar.bg-orange-8.text-white(dense style="height: 28px; line-height: 28px;")
67-
q-toolbar-title Détails événement cycle de vie
68-
q-space
69-
q-btn(icon="mdi-close" flat round dense v-close-popup aria-label="Fermer")
70-
q-bar.bg-transparent(dense style="height: 28px; line-height: 28px;")
71-
q-chip(
72-
v-if="lifecycleDetailsMeta.lifecycleLabel"
73-
dense
74-
size="sm"
75-
:color="lifecycleDetailsMeta.lifecycleColor"
76-
:text-color="lifecycleDetailsMeta.lifecycleTextColor"
77-
:icon="lifecycleDetailsMeta.lifecycleIcon"
78-
)
79-
span {{ lifecycleDetailsMeta.lifecycleLabel }}
80-
q-space
81-
.text-caption.text-grey-7
82-
span(v-if="lifecycleDetailsMeta.actor") Par: {{ lifecycleDetailsMeta.actor }}
83-
span(v-if="lifecycleDetailsMeta.actor && lifecycleDetailsMeta.date") -
84-
span(v-if="lifecycleDetailsMeta.date") Le: {{ lifecycleDetailsMeta.date }}
85-
q-separator
86-
.q-pa-md.row.justify-center(v-if="lifecycleDetailsLoading")
87-
q-spinner-dots(color="primary" size="40px")
88-
q-banner.bg-warning.text-black(v-if="!lifecycleDetailsLoading && !lifecycleDetailsJson")
89-
| Aucun détail exploitable trouvé pour cet événement.
90-
.audit-diff-editor(v-else-if="!lifecycleDetailsLoading")
91-
LazyMonacoEditor(
92-
style="height: 100%; width: 100%"
93-
lang="json"
94-
:model-value="lifecycleDetailsJson"
95-
:options="monacoOptions"
96-
)
9796
</template>
9897

9998
<script lang="ts">
@@ -109,6 +108,8 @@ export default defineNuxtComponent({
109108
{ label: 'Mois', value: 'MM/YYYY' },
110109
{ label: 'Année', value: 'YYYY' },
111110
],
111+
editorJsonCache: new WeakMap<object, string>(),
112+
maxLifecycleEditorChars: 30000,
112113
}
113114
},
114115
async setup() {
@@ -143,16 +144,9 @@ export default defineNuxtComponent({
143144
const lifecycles = ref<any>([])
144145
145146
const lifecycleDetails = ref<any>(null)
146-
const lifecycleDetailsDialogOpen = ref(false)
147+
const selectedLifecycleId = ref<string | null>(null)
147148
const lifecycleDetailsLoading = ref(false)
148-
const lifecycleDetailsJson = computed(() => {
149-
if (!lifecycleDetails.value) return ''
150-
try {
151-
return JSON.stringify(lifecycleDetails.value, null, 2)
152-
} catch {
153-
return ''
154-
}
155-
})
149+
const lifecycleDetailsJson = ref('')
156150
const lifecycleDetailsMeta = reactive({
157151
actor: '',
158152
date: '',
@@ -280,7 +274,7 @@ export default defineNuxtComponent({
280274
getLifecycleTimelineIconStyle,
281275
monacoOptions,
282276
lifecycleDetails,
283-
lifecycleDetailsDialogOpen,
277+
selectedLifecycleId,
284278
lifecycleDetailsLoading,
285279
lifecycleDetailsJson,
286280
lifecycleDetailsMeta,
@@ -317,13 +311,18 @@ export default defineNuxtComponent({
317311
done(true)
318312
}
319313
},
320-
openLifecycleDetails(lifecycle: any) {
314+
async openLifecycleDetails(lifecycle: any) {
315+
if (this.selectedLifecycleId === lifecycle?._id) {
316+
this.closeLifecycleDetails()
317+
return
318+
}
321319
this.lifecycleDetails = lifecycle
322-
this.lifecycleDetailsDialogOpen = true
320+
this.selectedLifecycleId = lifecycle?._id || null
323321
this.lifecycleDetailsLoading = true
324322
325323
try {
326324
const lifecycleInfos = lifecycle?.lifecycle ? this.getLifecycleInfos(lifecycle.lifecycle) : null
325+
this.lifecycleDetailsJson = this.stringifyForEditor(this.buildLifecycleDetailsPayload(lifecycle))
327326
328327
this.lifecycleDetailsMeta.actor = this.getLifecycleActor(lifecycle)
329328
this.lifecycleDetailsMeta.date = this.formatLifecycleDate(lifecycle)
@@ -348,6 +347,94 @@ export default defineNuxtComponent({
348347
this.lifecycleDetailsLoading = false
349348
}
350349
},
350+
closeLifecycleDetails() {
351+
this.selectedLifecycleId = null
352+
this.lifecycleDetails = null
353+
this.lifecycleDetailsJson = ''
354+
},
355+
buildLifecycleDetailsPayload(lifecycle: any) {
356+
return lifecycle
357+
},
358+
stringifyForEditor(payload: unknown): string {
359+
if (payload && typeof payload === 'object') {
360+
const cached = this.editorJsonCache.get(payload as object)
361+
if (cached) {
362+
return cached
363+
}
364+
}
365+
366+
let value = ''
367+
try {
368+
const seen = new WeakSet<object>()
369+
const maxDepth = 4
370+
const maxArrayItems = 50
371+
const maxObjectKeys = 60
372+
const maxStringLength = 1200
373+
374+
const sanitize = (input: unknown, depth: number): unknown => {
375+
if (input === null || input === undefined) return input
376+
377+
const inputType = typeof input
378+
if (inputType === 'string') {
379+
const text = input as string
380+
return text.length > maxStringLength ? `${text.slice(0, maxStringLength)}...[truncated:${text.length - maxStringLength}]` : text
381+
}
382+
if (inputType === 'number' || inputType === 'boolean') return input
383+
if (inputType !== 'object') return String(input)
384+
385+
const obj = input as Record<string, unknown>
386+
if (seen.has(obj)) {
387+
return '[Circular]'
388+
}
389+
if (depth >= maxDepth) {
390+
return '[MaxDepthReached]'
391+
}
392+
seen.add(obj)
393+
394+
if (Array.isArray(obj)) {
395+
const items = obj.slice(0, maxArrayItems).map((item) => sanitize(item, depth + 1))
396+
if (obj.length > maxArrayItems) {
397+
items.push(`[ArrayTruncated:${obj.length - maxArrayItems}]`)
398+
}
399+
return items
400+
}
401+
402+
const entries = Object.entries(obj)
403+
const limitedEntries = entries.slice(0, maxObjectKeys)
404+
const out: Record<string, unknown> = {}
405+
406+
limitedEntries.forEach(([key, val]) => {
407+
out[key] = sanitize(val, depth + 1)
408+
})
409+
410+
if (entries.length > maxObjectKeys) {
411+
out.__truncatedKeys = entries.length - maxObjectKeys
412+
}
413+
414+
return out
415+
}
416+
417+
value = JSON.stringify(sanitize(payload ?? null, 0), null, 2) ?? 'null'
418+
} catch {
419+
value = String(payload)
420+
}
421+
422+
if (value.length > this.maxLifecycleEditorChars) {
423+
this.$q.notify({
424+
message: `Contenu tronque a ${this.maxLifecycleEditorChars} caracteres pour eviter un depassement memoire`,
425+
color: 'warning',
426+
icon: 'mdi-alert',
427+
position: 'top-right',
428+
})
429+
value = `${value.slice(0, this.maxLifecycleEditorChars)}\n\n/* ... contenu tronque ... */`
430+
}
431+
432+
if (payload && typeof payload === 'object') {
433+
this.editorJsonCache.set(payload as object, value)
434+
}
435+
436+
return value
437+
},
351438
},
352439
})
353440
</script>

0 commit comments

Comments
 (0)