feat(ui): add snackbar for realtime notifications
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { watchEffect, computed } from 'vue' // Added computed
|
||||
import TopBar from '@/components/ui/TopBar.vue'
|
||||
import NavBar from '@/components/ui/NavBar.vue'
|
||||
import SnackbarContainer from '@/components/ui/SnackbarContainer.vue'
|
||||
import { useUi } from '@/stores/useUi'
|
||||
|
||||
const ui = useUi()
|
||||
@@ -80,6 +81,7 @@ const transitionDurationStyle = computed(() => {
|
||||
</transition>
|
||||
</router-view>
|
||||
</main>
|
||||
<SnackbarContainer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
67
ui/src/components/ui/SnackbarContainer.vue
Normal file
67
ui/src/components/ui/SnackbarContainer.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="snackbar-wrapper pointer-events-none">
|
||||
<transition-group name="snackbar" tag="div" class="snackbar-container">
|
||||
<div
|
||||
v-for="snack in snackbar.snacks"
|
||||
:key="snack.id"
|
||||
:class="['snackbar', snack.type]"
|
||||
>
|
||||
{{ snack.text }}
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useSnackbarStore } from '@/stores/useSnackbar'
|
||||
|
||||
const snackbar = useSnackbarStore()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.snackbar-wrapper {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.snackbar-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.snackbar {
|
||||
pointer-events: auto;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.snackbar.info {
|
||||
background-color: #323232;
|
||||
}
|
||||
|
||||
.snackbar.success {
|
||||
background-color: #22c55e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.snackbar.error {
|
||||
background-color: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.snackbar-enter-active,
|
||||
.snackbar-leave-active {
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
}
|
||||
.snackbar-enter-from,
|
||||
.snackbar-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import { usePlanillasStore } from './usePlanillas'
|
||||
import { useEmpleadosStore } from './useEmpleados'
|
||||
import { useTareasStore } from './useTareas'
|
||||
import { useAsistenciasStore } from './useAsistencias'
|
||||
import { useSnackbarStore } from './useSnackbar'
|
||||
|
||||
export const useRealtimeStore = defineStore('realtime', {
|
||||
state: () => ({
|
||||
@@ -55,6 +56,11 @@ export const useRealtimeStore = defineStore('realtime', {
|
||||
addEvent();
|
||||
}
|
||||
|
||||
const snackbar = useSnackbarStore();
|
||||
const idPart = payload.new?.id || payload.old?.id;
|
||||
const message = `${payload.operation} ${payload.table}${idPart ? ' #' + idPart : ''}`;
|
||||
snackbar.push({ text: message });
|
||||
|
||||
// mark badge for module and operation
|
||||
if (this.badges[payload.table]) {
|
||||
this.badges[payload.table][payload.operation] = true;
|
||||
|
||||
22
ui/src/stores/useSnackbar.js
Normal file
22
ui/src/stores/useSnackbar.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSnackbarStore = defineStore('snackbar', {
|
||||
state: () => ({
|
||||
snacks: []
|
||||
}),
|
||||
actions: {
|
||||
push({ text, type = 'info', duration = 4000 }) {
|
||||
const id = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2)
|
||||
const snack = { id, text, type }
|
||||
this.snacks.push(snack)
|
||||
if (duration > 0) {
|
||||
setTimeout(() => this.remove(id), duration)
|
||||
}
|
||||
return id
|
||||
},
|
||||
remove(id) {
|
||||
const index = this.snacks.findIndex(s => s.id === id)
|
||||
if (index !== -1) this.snacks.splice(index, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user