|
|
@@ -0,0 +1,274 @@
|
|
|
+<template>
|
|
|
+ <q-page class="notifications-page">
|
|
|
+
|
|
|
+ <!-- HEADER -->
|
|
|
+ <div class="header row items-center">
|
|
|
+
|
|
|
+ <q-btn
|
|
|
+ flat
|
|
|
+ round
|
|
|
+ dense
|
|
|
+ icon="chevron_left"
|
|
|
+ class="back-btn"
|
|
|
+ @click="router.back()"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div class="header-title">
|
|
|
+ {{ $t('notifications.title') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- ACTIONS -->
|
|
|
+ <div class="actions row justify-between items-center">
|
|
|
+
|
|
|
+ <div class="unread-text">
|
|
|
+ {{ $t('notifications.unread') }} {{ unreadCount }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-btn
|
|
|
+ flat
|
|
|
+ no-caps
|
|
|
+ class="mark-read-btn"
|
|
|
+ label="✓ Marcar todas como lidas"
|
|
|
+ @click="markAllAsRead"
|
|
|
+ />
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- LIST -->
|
|
|
+ <div class="notifications-list">
|
|
|
+
|
|
|
+ <q-card
|
|
|
+ v-for="item in notifications"
|
|
|
+ :key="item.id"
|
|
|
+ flat
|
|
|
+ clickable
|
|
|
+ class="notification-card"
|
|
|
+ :class="{ unread: !item.read }"
|
|
|
+ @click="markAsRead(item)"
|
|
|
+ >
|
|
|
+
|
|
|
+ <div class="row no-wrap items-start">
|
|
|
+
|
|
|
+ <!-- AVATAR -->
|
|
|
+ <q-avatar size="42px" class="q-mr-md">
|
|
|
+ <img :src="logoDiaria" />
|
|
|
+ </q-avatar>
|
|
|
+
|
|
|
+ <!-- CONTENT -->
|
|
|
+ <div class="col">
|
|
|
+
|
|
|
+ <div class="row justify-between items-start">
|
|
|
+
|
|
|
+ <div class="notification-title">
|
|
|
+ {{ item.title }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- STATUS -->
|
|
|
+ <div
|
|
|
+ class="status-dot"
|
|
|
+ :class="{ active: !item.read }"
|
|
|
+ />
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="notification-description">
|
|
|
+ {{ item.description }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="notification-time">
|
|
|
+ {{ item.time }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </q-card>
|
|
|
+
|
|
|
+</div>
|
|
|
+
|
|
|
+ </q-page>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+
|
|
|
+import { computed, ref, onMounted } from 'vue'
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
+
|
|
|
+import { api } from 'boot/axios'
|
|
|
+
|
|
|
+import logoDiaria from 'src/assets/logo_diaria_colorido_sem_texto.svg'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+
|
|
|
+const notifications = ref([])
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadNotifications()
|
|
|
+})
|
|
|
+
|
|
|
+const unreadCount = computed(() => {
|
|
|
+ return notifications.value.filter((n) => !n.read).length
|
|
|
+})
|
|
|
+
|
|
|
+const loadNotifications = async () => {
|
|
|
+ try {
|
|
|
+
|
|
|
+ const response = await api.get('/notifications')
|
|
|
+
|
|
|
+ notifications.value = response.data.payload || []
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const markAsRead = async (notification) => {
|
|
|
+ try {
|
|
|
+
|
|
|
+ await api.put(`/notifications/${notification.id}/read`)
|
|
|
+
|
|
|
+ notifications.value = notifications.value.map((item) => {
|
|
|
+
|
|
|
+ if (item.id === notification.id) {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ read: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return item
|
|
|
+ })
|
|
|
+
|
|
|
+ if (
|
|
|
+ notification.origin === 'schedule'
|
|
|
+ && notification.origin_id
|
|
|
+ ) {
|
|
|
+ router.push(`/schedule/${notification.origin_id}`)
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const markAllAsRead = async () => {
|
|
|
+ try {
|
|
|
+
|
|
|
+ await api.put('/notifications/read-all')
|
|
|
+
|
|
|
+ notifications.value = notifications.value.map((notification) => ({
|
|
|
+ ...notification,
|
|
|
+ read: true
|
|
|
+ }))
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.notifications-page {
|
|
|
+ background: #f5f5f7;
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+/* HEADER */
|
|
|
+.header {
|
|
|
+ position: relative;
|
|
|
+ padding: 18px 16px 12px;
|
|
|
+ background: white;
|
|
|
+}
|
|
|
+
|
|
|
+.back-btn {
|
|
|
+ position: absolute;
|
|
|
+ left: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-title {
|
|
|
+ width: 100%;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+
|
|
|
+ color: #8B5CF6;
|
|
|
+}
|
|
|
+
|
|
|
+/* ACTIONS */
|
|
|
+.actions {
|
|
|
+ padding: 14px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.unread-text {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.mark-read-btn {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #ff4fd8;
|
|
|
+}
|
|
|
+
|
|
|
+/* LIST */
|
|
|
+.notifications-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+/* CARD */
|
|
|
+.notification-card {
|
|
|
+ border-radius: 0;
|
|
|
+ padding: 16px;
|
|
|
+ background: white;
|
|
|
+
|
|
|
+ border-bottom: 1px solid #ececec;
|
|
|
+
|
|
|
+ transition: 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.notification-card.unread {
|
|
|
+ background: #f8eff7;
|
|
|
+}
|
|
|
+
|
|
|
+/* TITLE */
|
|
|
+.notification-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #555;
|
|
|
+}
|
|
|
+
|
|
|
+/* DESCRIPTION */
|
|
|
+.notification-description {
|
|
|
+ margin-top: 4px;
|
|
|
+
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.4;
|
|
|
+
|
|
|
+ color: #777;
|
|
|
+}
|
|
|
+
|
|
|
+/* TIME */
|
|
|
+.notification-time {
|
|
|
+ margin-top: 10px;
|
|
|
+
|
|
|
+ font-size: 11px;
|
|
|
+ color: #aaa;
|
|
|
+}
|
|
|
+
|
|
|
+/* STATUS */
|
|
|
+.status-dot {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #ddd;
|
|
|
+}
|
|
|
+
|
|
|
+.status-dot.active {
|
|
|
+ background: #ff5be1;
|
|
|
+}
|
|
|
+</style>
|