Procházet zdrojové kódy

feat: :sparkles: feat (modulo-de-notificação) Foi ajustado o modulo de notificação

Foi criada a página de notificações responsável por listar todas as notificações enviadas tanto para o prestador quanto para o cliente.

fase:dev | origin:escopo
kayo henrique před 3 týdny
rodič
revize
26f5e25537

+ 67 - 4
src/components/dashboard/DashboardHeaderBar.vue

@@ -18,15 +18,71 @@
     </div>
 
     <div class="col row justify-end items-center">
-      <q-btn flat round dense icon="mdi-bell-outline" color="grey-7" size="sm" />
-    </div>
+
+  <q-btn
+    flat
+    round
+    dense
+    color="grey-7"
+    size="sm"
+    @click="goToNotifications"
+  >
+
+    <q-icon
+      name="mdi-bell-outline"
+      size="20px"
+    />
+
+    <!-- BADGE -->
+    <q-badge
+      v-if="unreadNotifications > 0"
+      floating
+      rounded
+      color="pink"
+      class="notification-badge"
+    >
+      {{ unreadNotifications }}
+    </q-badge>
+
+  </q-btn>
+
+</div>
   </div>
 </template>
 
 <script setup>
-import LogoDiariaColorida from 'src/assets/logo_diaria_colorido_sem_texto.svg';
+import LogoDiariaColorida from 'src/assets/logo_diaria_colorido_sem_texto.svg'
+
+import { computed } from 'vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
 
-defineProps({ data: { type: Object, default: () => null } });
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => null
+  },
+
+  notifications: {
+    type: Array,
+    default: () => []
+  }
+})
+
+//vai para dashboard as notificações tem que ser mocada no backend
+const unreadNotifications = computed(() => {
+  return props.notifications.filter((notification) => !notification.read).length
+})
+
+const goToNotifications = () => {
+  router.push({
+  name: 'NotificationsPage',
+  query: {
+    notifications: JSON.stringify(props.notifications)
+  }
+})
+}
 </script>
 
 <style scoped lang="scss">
@@ -53,4 +109,11 @@ defineProps({ data: { type: Object, default: () => null } });
   color: #999;
   line-height: 1;
 }
+
+.notification-badge {
+  min-width: 16px;
+  height: 16px;
+  font-size: 10px;
+  font-weight: 700;
+}
 </style>

+ 11 - 6
src/i18n/locales/pt.json

@@ -444,12 +444,12 @@
       "btn_close": "fechar",
       "btn_help": "Ajuda"
     },
-      "client_proposals": {
-        "candidate": "Candidato",
-        "custom": "sob medida",
-        "age": "({idade} anos)",
-        "distance": "Distância"
-      },
+    "client_proposals": {
+      "candidate": "Candidato",
+      "custom": "sob medida",
+      "age": "({idade} anos)",
+      "distance": "Distância"
+    },
     "last_schedules": {
       "title": "Últimos serviços",
       "reschedule": "reagendar",
@@ -820,5 +820,10 @@
         "btn_keep": "Cancelar o serviço"
       }
     }
+  },
+  "notifications": {
+    "title": "Notificações",
+    "unread": "Não lidas",
+    "mark_all_read": " Marcar todas como lidas"
   }
 }

+ 7 - 1
src/pages/dashboard/DashboardPage.vue

@@ -6,7 +6,10 @@
       </div>
     </template>
     <template v-else>
-      <DashboardHeaderBar :data="headerBar" />
+      <DashboardHeaderBar
+  :data="headerBar"
+  :notifications="notifications"
+/>
       <DashboardRegistrationIncomplete v-if="!registrationComplete" />
       <DashboardSummaryInfos v-else :data="summaryInfos" />
       <DashboardPaymentIncomplete v-if="!hasPaymentMethods" />
@@ -68,11 +71,13 @@ const lastDoneSchedules = ref([]);
 const favoriteProviders = ref([]);
 const providersClose = ref([]);
 const todaySchedules = ref([]);
+const notifications = ref([]);
 const loading = ref(true);
 const showSuccessModal = ref(router.currentRoute.value.fullPath.includes('showSuccessModal') || false);
 
 const registrationComplete = computed(() => store.user?.registration_complete ?? true);
 
+
 const openAcceptedDialog = (schedule) => {
   $q.dialog({
     component: ScheduleAcceptedDialog,
@@ -95,6 +100,7 @@ const reloadDashboard = async () => {
     providersClose.value = response.providersClose ?? [];
     clientProposals.value = response.schedulesProposals ?? [];
     todaySchedules.value = response.todaySchedules ?? [];
+    notifications.value = response.notifications ?? [];
     hasPaymentMethods.value = response.has_payment_methods ?? true;
   }
   if( showSuccessModal.value == true) {

+ 236 - 0
src/pages/notifications/NotificationsPage.vue

@@ -0,0 +1,236 @@
+<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.id)"
+      >
+
+        <div class="row no-wrap items-start">
+
+          <!-- AVATAR -->
+          <q-avatar size="42px" class="q-mr-md">
+            <img :src="item.avatar" />
+          </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 { useRoute, useRouter } from 'vue-router'
+
+const router = useRouter()
+
+
+const route = useRoute()
+
+const notifications = ref([])
+
+onMounted(() => {
+  notifications.value = JSON.parse(route.query.notifications || '[]')
+})
+
+const unreadCount = computed(() => {
+  return notifications.value.filter((n) => !n.read).length
+})
+
+const markAsRead = (id) => {
+  notifications.value = notifications.value.map((notification) => {
+    if (notification.id === id) {
+      return {
+        ...notification,
+        read: true
+      }
+    }
+
+    return notification
+  })
+}
+
+const markAllAsRead = () => {
+  notifications.value = notifications.value.map((notification) => ({
+    ...notification,
+    read: true
+  }))
+}
+</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>

+ 11 - 0
src/router/routes/notifications.route.js

@@ -0,0 +1,11 @@
+export default [
+  {
+    path: '/notifications',
+    name: 'NotificationsPage',
+    component: () => import('src/pages/notifications/NotificationsPage.vue'),
+    meta: {
+      title: 'Notificações',
+      requireAuth: true
+    }
+  }
+]