Add module-aware snackbar notifications
This commit is contained in:
@@ -3,6 +3,7 @@ import { ref, watch, computed, onMounted } from 'vue'
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useUi } from '@/stores/useUi'
|
import { useUi } from '@/stores/useUi'
|
||||||
import { useRealtimeStore } from '@/stores/useRealtime'
|
import { useRealtimeStore } from '@/stores/useRealtime'
|
||||||
|
import { moduleInfo } from '@/constants/moduleInfo'
|
||||||
|
|
||||||
const ui = useUi()
|
const ui = useUi()
|
||||||
const realtime = useRealtimeStore()
|
const realtime = useRealtimeStore()
|
||||||
@@ -10,10 +11,10 @@ const realtime = useRealtimeStore()
|
|||||||
// enlaces de la app
|
// enlaces de la app
|
||||||
const links = [
|
const links = [
|
||||||
{ to: '/', label: 'Chat', icon: '💬' },
|
{ to: '/', label: 'Chat', icon: '💬' },
|
||||||
{ to: '/empleados', label: 'Empleados', icon: '👥' },
|
{ to: '/empleados', label: 'Empleados', icon: moduleInfo.Cliente.icon },
|
||||||
{ to: '/tareas', label: 'Tareas', icon: '📋' },
|
{ to: '/tareas', label: 'Tareas', icon: moduleInfo.TareaRealizada.icon },
|
||||||
{ to: '/planillas', label: 'Planillas', icon: '📂' },
|
{ to: '/planillas', label: 'Planillas', icon: moduleInfo.Planilla.icon },
|
||||||
{ to: '/asistencias', label: 'Asistencias', icon: '⏰' },
|
{ to: '/asistencias', label: 'Asistencias', icon: moduleInfo.Asistencia.icon },
|
||||||
{ to: '/feed', label: 'Feed', icon: '📰' },
|
{ to: '/feed', label: 'Feed', icon: '📰' },
|
||||||
{ to: '/config', label: 'Config', icon: '⚙️' },
|
{ to: '/config', label: 'Config', icon: '⚙️' },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,13 @@
|
|||||||
:key="snack.id"
|
:key="snack.id"
|
||||||
:class="['snackbar-item', typeClass(snack.type)]"
|
:class="['snackbar-item', typeClass(snack.type)]"
|
||||||
>
|
>
|
||||||
<span class="flex-1">{{ snack.text }}</span>
|
<div class="flex-1">
|
||||||
|
<div v-if="snack.icon || snack.header" class="flex items-center gap-1 text-sm opacity-80 mb-1">
|
||||||
|
<span v-if="snack.icon">{{ snack.icon }}</span>
|
||||||
|
<span v-if="snack.header">{{ snack.header }}</span>
|
||||||
|
</div>
|
||||||
|
<span>{{ snack.text }}</span>
|
||||||
|
</div>
|
||||||
<button class="ml-3 text-white/70 hover:text-white" @click="snackbar.remove(snack.id)">×</button>
|
<button class="ml-3 text-white/70 hover:text-white" @click="snackbar.remove(snack.id)">×</button>
|
||||||
</div>
|
</div>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
|
|||||||
6
ui/src/constants/moduleInfo.js
Normal file
6
ui/src/constants/moduleInfo.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const moduleInfo = {
|
||||||
|
Cliente: { label: 'Empleados', icon: '👥' },
|
||||||
|
TareaRealizada: { label: 'Tareas', icon: '📋' },
|
||||||
|
Planilla: { label: 'Planillas', icon: '📂' },
|
||||||
|
Asistencia: { label: 'Asistencias', icon: '⏰' }
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { useEmpleadosStore } from './useEmpleados'
|
|||||||
import { useTareasStore } from './useTareas'
|
import { useTareasStore } from './useTareas'
|
||||||
import { useAsistenciasStore } from './useAsistencias'
|
import { useAsistenciasStore } from './useAsistencias'
|
||||||
import { useSnackbarStore } from './useSnackbar'
|
import { useSnackbarStore } from './useSnackbar'
|
||||||
|
import { moduleInfo } from '@/constants/moduleInfo'
|
||||||
|
|
||||||
export const useRealtimeStore = defineStore('realtime', {
|
export const useRealtimeStore = defineStore('realtime', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -61,7 +62,13 @@ export const useRealtimeStore = defineStore('realtime', {
|
|||||||
const message = `${payload.operation} ${payload.table}${idPart ? ' #' + idPart : ''}`;
|
const message = `${payload.operation} ${payload.table}${idPart ? ' #' + idPart : ''}`;
|
||||||
const type = payload.operation === 'DELETE' ? 'error' :
|
const type = payload.operation === 'DELETE' ? 'error' :
|
||||||
payload.operation === 'INSERT' ? 'success' : 'info';
|
payload.operation === 'INSERT' ? 'success' : 'info';
|
||||||
snackbar.push({ text: message, type });
|
const info = moduleInfo[payload.table] || {};
|
||||||
|
snackbar.push({
|
||||||
|
text: message,
|
||||||
|
type,
|
||||||
|
icon: info.icon,
|
||||||
|
header: info.label
|
||||||
|
});
|
||||||
|
|
||||||
// mark badge for module and operation
|
// mark badge for module and operation
|
||||||
if (this.badges[payload.table]) {
|
if (this.badges[payload.table]) {
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ export const useSnackbarStore = defineStore('snackbar', {
|
|||||||
maxSnacks: 5
|
maxSnacks: 5
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
push({ text, type = 'info', duration = 6000 }) {
|
push({ text, type = 'info', icon = '', header = '', duration = 6000 }) {
|
||||||
const id = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2)
|
const id = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2)
|
||||||
const snack = { id, text, type }
|
const snack = { id, text, type, icon, header }
|
||||||
if (this.snacks.length >= this.maxSnacks) {
|
if (this.snacks.length >= this.maxSnacks) {
|
||||||
this.snacks.shift()
|
this.snacks.shift()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user