Improve realtime feed

This commit is contained in:
josedario87
2025-06-09 19:49:24 -06:00
parent 5dae4a20d3
commit 9030469fe7
4 changed files with 57 additions and 12 deletions

View File

@@ -105,5 +105,5 @@ The specific prop name matches the module (e.g., `asistencia` for `cardAsistenci
## Realtime Feed
The UI provides a *Feed* view that consumes the realtime events generated by the backend. You can access it via the sidebar link "Feed". Each incoming event is rendered using these standardized cards so you can monitor inserts, updates and deletes as they happen.
The UI provides a *Feed* view that consumes the realtime events generated by the backend. You can access it via the sidebar link "Feed". Each incoming event is rendered using these standardized cards so you can monitor inserts, updates and deletes as they happen. Update events show the old card followed by the new one with an arrow in between.

View File

@@ -3,7 +3,28 @@
<p class="text-xs text-gray-500 mb-1">
{{ event.operation }} · {{ event.table }} {{ formatTimestamp(event.receivedAt) }}
</p>
<component v-if="component" :is="component" v-bind="componentProps" />
<template v-if="event.operation === 'UPDATE'">
<div class="flex items-center space-x-2">
<component
v-if="component"
:is="component"
v-bind="componentPropsOld"
class="old-card opacity-70"
/>
<span class="text-2xl"></span>
<component
v-if="component"
:is="component"
v-bind="componentPropsNew"
class="new-card"
/>
</div>
</template>
<component
v-else-if="component"
:is="component"
v-bind="componentProps"
/>
</div>
</template>
@@ -31,22 +52,28 @@ const itemData = computed(() =>
props.event.operation === 'DELETE' ? props.event.old : props.event.new
)
const componentProps = computed(() => {
const data = itemData.value || {}
const buildProps = (data) => {
switch (props.event.table) {
case 'Planilla':
return { planilla: data }
return { planilla: data || {} }
case 'Cliente':
return { employee: data }
return { employee: data || {} }
case 'TareaRealizada':
return { tarea: data }
return { tarea: data || {} }
case 'Asistencia':
return { asistencia: data }
return { asistencia: data || {} }
default:
return {}
}
}
const componentProps = computed(() => {
return buildProps(itemData.value)
})
const componentPropsOld = computed(() => buildProps(props.event.old))
const componentPropsNew = computed(() => buildProps(props.event.new))
const formatTimestamp = (ts) => {
if (!ts) return ''
return new Date(ts).toLocaleString('es-HN', { hour12: true })
@@ -57,4 +84,9 @@ const formatTimestamp = (ts) => {
.realtime-event {
margin-bottom: 1rem;
}
.old-card,
.new-card {
flex: 1 1 0%;
}
</style>

View File

@@ -16,16 +16,16 @@ onMounted(() => {
<h1>Feed en Tiempo Real</h1>
</header>
<div class="feed-list overflow-auto pr-2" style="max-height: calc(100vh - 160px);">
<transition-group name="feed" tag="div" class="feed-list overflow-auto pr-2" style="max-height: calc(100vh - 160px);">
<RealtimeEventCard
v-for="(ev, idx) in realtime.events"
:key="idx"
:key="ev.receivedAt + idx"
:event="ev"
/>
<div v-if="realtime.events.length === 0" class="text-center text-gray-500 mt-4">
No hay eventos aún.
</div>
</div>
</transition-group>
</div>
</template>
@@ -50,4 +50,17 @@ onMounted(() => {
font-size: 2.2em;
font-weight: 600;
}
.feed-enter-active,
.feed-leave-active {
transition: all 0.3s ease-out;
}
.feed-enter-from {
opacity: 0;
transform: translateY(-10px);
}
.feed-enter-to {
opacity: 1;
transform: translateY(0);
}
</style>