|
|
@@ -0,0 +1,363 @@
|
|
|
+<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="handleNotification(item)"
|
|
|
+ >
|
|
|
+
|
|
|
+ <div class="notification-wrapper">
|
|
|
+
|
|
|
+ <!-- AVATAR -->
|
|
|
+ <q-avatar size="44px" class="notification-avatar">
|
|
|
+ <img :src="getNotificationIcon(item.type)" />
|
|
|
+ </q-avatar>
|
|
|
+
|
|
|
+ <!-- CONTENT -->
|
|
|
+ <div class="notification-content">
|
|
|
+
|
|
|
+ <div class="notification-header">
|
|
|
+
|
|
|
+ <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 (id) => {
|
|
|
+ try {
|
|
|
+
|
|
|
+ await api.put(`/notifications/${id}/read`)
|
|
|
+
|
|
|
+ notifications.value = notifications.value.map((notification) => {
|
|
|
+
|
|
|
+ if (notification.id === id) {
|
|
|
+ return {
|
|
|
+ ...notification,
|
|
|
+ read: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return notification
|
|
|
+ })
|
|
|
+
|
|
|
+ } 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)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleNotification = async (notification) => {
|
|
|
+
|
|
|
+ if (!notification.read) {
|
|
|
+ await markAsRead(notification.id)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ notification.origin === 'schedule'
|
|
|
+ && notification.origin_id
|
|
|
+ ) {
|
|
|
+ router.push(`/schedule/${notification.origin_id}`)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getNotificationIcon = (type) => {
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+
|
|
|
+ case 'schedule_client_provider_accepted':
|
|
|
+ return logoDiaria
|
|
|
+
|
|
|
+ case 'schedule_client_provider_refused':
|
|
|
+ return logoDiaria
|
|
|
+
|
|
|
+ case 'schedule_client_provider_cancelled':
|
|
|
+ return logoDiaria
|
|
|
+
|
|
|
+ case 'schedule_client_provider_coming':
|
|
|
+ return logoDiaria
|
|
|
+
|
|
|
+ case 'schedule_client_provider_finished':
|
|
|
+ return logoDiaria
|
|
|
+
|
|
|
+ default:
|
|
|
+ return logoDiaria
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.notifications-page {
|
|
|
+ background: #f4f4f5;
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+/* HEADER */
|
|
|
+.header {
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ padding: 18px 16px 12px;
|
|
|
+
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.back-btn {
|
|
|
+ position: absolute;
|
|
|
+ left: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-title {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ font-size: 17px;
|
|
|
+ font-weight: 700;
|
|
|
+
|
|
|
+ color: #8B5CF6;
|
|
|
+}
|
|
|
+
|
|
|
+/* ACTIONS */
|
|
|
+.actions {
|
|
|
+ padding: 14px 16px 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.unread-text {
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.mark-read-btn {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+
|
|
|
+ color: #ff4fd8;
|
|
|
+}
|
|
|
+
|
|
|
+/* LIST */
|
|
|
+.notifications-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ gap: 12px;
|
|
|
+
|
|
|
+ padding: 0 14px 24px;
|
|
|
+}
|
|
|
+
|
|
|
+/* CARD */
|
|
|
+.notification-card {
|
|
|
+ background: #ffffff;
|
|
|
+
|
|
|
+ border-radius: 24px;
|
|
|
+
|
|
|
+ padding: 16px 16px;
|
|
|
+
|
|
|
+ box-shadow:
|
|
|
+ 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
|
+
|
|
|
+ transition: 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.notification-card.unread {
|
|
|
+ background: #ffffff;
|
|
|
+}
|
|
|
+
|
|
|
+/* WRAPPER */
|
|
|
+.notification-wrapper {
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* AVATAR */
|
|
|
+.notification-avatar {
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ width: 44px !important;
|
|
|
+ height: 44px !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* CONTENT */
|
|
|
+.notification-content {
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* HEADER */
|
|
|
+.notification-header {
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ align-items: flex-start;
|
|
|
+
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+/* TITLE */
|
|
|
+.notification-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 700;
|
|
|
+
|
|
|
+ color: #5b5b5b;
|
|
|
+
|
|
|
+ line-height: 1.1;
|
|
|
+}
|
|
|
+
|
|
|
+/* DESCRIPTION */
|
|
|
+.notification-description {
|
|
|
+ margin-top: 4px;
|
|
|
+
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ line-height: 1.35;
|
|
|
+
|
|
|
+ color: #8f8f8f;
|
|
|
+}
|
|
|
+
|
|
|
+/* TIME */
|
|
|
+.notification-time {
|
|
|
+ margin-top: 8px;
|
|
|
+
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ color: #b5b5b5;
|
|
|
+}
|
|
|
+
|
|
|
+/* STATUS */
|
|
|
+.status-dot {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+
|
|
|
+ border-radius: 999px;
|
|
|
+
|
|
|
+ background: #e6e6e6;
|
|
|
+
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.status-dot.active {
|
|
|
+ background: #d8d8d8;
|
|
|
+}
|
|
|
+</style>
|