Browse Source

funcao de afastamento + usuario afastado n consegue criar agendamento + usuario ativo cria agendamento aprovado automaticamente + associado afastado nao retorna na validacao de carteirinha

Gustavo Zanatta 1 month ago
parent
commit
772d934d22

+ 2 - 2
quasar.config.js

@@ -57,8 +57,8 @@ export default defineConfig((ctx) => {
       // analyze: true,
       env: {
         APP_NAME: "serprati-associado",
-        // API_URL: ctx.dev ? "http://localhost:3000" : process.env.API_URL,
-        API_URL: ctx.dev ? "http://10.0.2.2:3000" : "https://api.serprati.softpar.inf.br",
+        API_URL: ctx.dev ? "http://localhost:3000" : process.env.API_URL,
+        // API_URL: ctx.dev ? "http://10.0.2.2:3000" : "https://api.serprati.softpar.inf.br",
         PASSWORD: ctx.dev ? (process.env.DEV_PASSWORD ?? "") : "",
         WEBSOCKET_API: ctx.dev
           ? "http://localhost:4321/"

+ 4 - 4
src/api/appointment.js

@@ -29,8 +29,8 @@ export const getPartnerAppointments = async () => {
   return data.payload;
 };
 
-export const approveAppointmentParceiro = async (id) => {
-  const { data } = await api.put(`/parceiro/appointment/${id}/approve`);
+export const approveAppointmentParceiro = async (id, payload) => {
+  const { data } = await api.put(`/parceiro/appointment/${id}/approve`, payload);
   return data.payload;
 };
 
@@ -52,8 +52,8 @@ export const getAdminAppointmentsPaginated = async ({ page = 1, perPage = 10, fi
   return { data: { result: data.payload } };
 };
 
-export const approveAppointment = async (id) => {
-  const { data } = await api.put(`/appointment/${id}/approve`);
+export const approveAppointment = async (id, payload) => {
+  const { data } = await api.put(`/appointment/${id}/approve`, payload);
   return data.payload;
 };
 

+ 130 - 0
src/components/NotificationDetailDialog.vue

@@ -0,0 +1,130 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="notif-detail-card">
+
+      <q-card-section class="row items-center q-pb-none">
+        <div class="text-h6 text-violet-normal ellipsis" style="max-width: calc(100% - 48px)">
+          {{ item.notification?.title }}
+        </div>
+        <q-space />
+        <q-btn icon="mdi-close" flat round dense @click="handleClose" />
+      </q-card-section>
+
+      <q-separator class="q-mt-sm" />
+
+      <q-card-section class="q-pt-md notif-detail-card__scroll">
+        <div v-if="imageUrl" class="notif-detail-card__image q-mb-md">
+          <img :src="imageUrl" alt="" class="notif-detail-card__img" />
+        </div>
+
+        <div class="text-body2 text-grey-8 notif-detail-card__message">
+          {{ item.notification?.message }}
+        </div>
+
+        <div class="text-caption text-grey-5 q-mt-md">
+          {{ formatDate(item.created_at) }}
+        </div>
+      </q-card-section>
+
+      <q-separator />
+
+      <q-card-actions align="right" class="q-pa-sm">
+        <q-btn
+          flat
+          color="violet-normal"
+          :label="$t('common.actions.close')"
+          @click="handleClose"
+        />
+      </q-card-actions>
+
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { useDialogPluginComponent } from "quasar";
+import { markNotificationAsReadAssociado } from "src/api/notification";
+
+const props = defineProps({
+  item: { type: Object, required: true },
+});
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
+const { t } = useI18n();
+
+const markedReadId = ref(null);
+
+const imageUrl = computed(() => {
+  const media = props.item.notification?.media?.[0]?.url;
+  if (media) return media;
+  const direct = props.item.notification?.image_url;
+  if (!direct) return null;
+  return direct.startsWith("http") ? direct : (process.env.API_URL ?? "") + direct;
+});
+
+const formatDate = (dateStr) => {
+  if (!dateStr) return "";
+  const d = new Date(dateStr);
+  const today = new Date();
+  const isToday =
+    d.getDate() === today.getDate() &&
+    d.getMonth() === today.getMonth() &&
+    d.getFullYear() === today.getFullYear();
+  if (isToday) return t("common.terms.today");
+  return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
+};
+
+const handleClose = () => {
+  onDialogOK(markedReadId.value);
+};
+
+onMounted(async () => {
+  if (props.item.read) return;
+  try {
+    await markNotificationAsReadAssociado(props.item.id);
+    markedReadId.value = props.item.id;
+  } catch (e) {
+    console.error(e);
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.notif-detail-card {
+  width: 90vw;
+  max-width: 520px;
+  min-width: 300px;
+
+  &__scroll {
+    max-height: 65vh;
+    overflow-y: auto;
+  }
+
+  &__image {
+    width: 100%;
+    border-radius: 8px;
+    overflow: hidden;
+    background: #f0e8f1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  &__img {
+    width: 100%;
+    max-height: 300px;
+    object-fit: contain;
+    display: block;
+  }
+
+  &__message {
+    white-space: pre-wrap;
+    word-break: break-word;
+    line-height: 1.6;
+  }
+}
+</style>

+ 211 - 0
src/components/UnreadNotificationsDialog.vue

@@ -0,0 +1,211 @@
+<template>
+  <q-dialog ref="dialogRef" persistent @hide="onDialogHide">
+    <q-card class="unread-dialog-card">
+
+      <q-card-section class="q-pb-sm">
+        <div class="text-h6 text-violet-normal">{{ $t('notification.pending_read_title') }}</div>
+        <div class="text-caption text-grey-6 q-mt-xs">{{ $t('notification.pending_read_subtitle') }}</div>
+      </q-card-section>
+
+      <q-separator />
+
+      <q-card-section class="unread-dialog-card__scroll q-pa-sm">
+        <div v-if="loading" class="flex flex-center q-pa-xl">
+          <q-spinner color="violet-normal" size="48px" />
+        </div>
+
+        <div v-else class="notif-list">
+          <div
+            v-for="item in notifications"
+            :key="item.id"
+            class="notif-item"
+            :class="{ 'notif-item--unread': !item.read }"
+            @click="openDetail(item)"
+          >
+            <div class="notif-item__icon">
+              <q-icon
+                name="mdi-bell-outline"
+                size="24px"
+                :color="item.read ? 'grey-4' : 'violet-normal'"
+              />
+            </div>
+
+            <div class="notif-item__content">
+              <div class="notif-item__title" :class="{ 'notif-item__title--read': item.read }">
+                {{ item.notification?.title }}
+              </div>
+              <div class="notif-item__message">{{ item.notification?.message }}</div>
+              <div class="notif-item__date">{{ formatDate(item.created_at) }}</div>
+            </div>
+
+            <q-icon
+              v-if="item.read"
+              name="mdi-check-circle"
+              color="positive"
+              size="20px"
+              class="q-ml-xs"
+              style="flex-shrink: 0"
+            />
+            <q-badge
+              v-else
+              color="violet-normal"
+              rounded
+              class="q-ml-xs"
+              style="flex-shrink: 0; width: 8px; height: 8px; min-height: unset; padding: 0"
+            />
+          </div>
+        </div>
+      </q-card-section>
+
+      <q-separator />
+
+      <q-card-actions align="right" class="q-pa-md">
+        <div class="text-caption text-grey-6 q-mr-auto">
+          {{ localUnreadCount > 0 ? $t('notification.pending_read_hint') : '' }}
+        </div>
+        <q-btn
+          v-if="localUnreadCount === 0"
+          unelevated
+          color="violet-normal"
+          :label="$t('notification.pending_read_close')"
+          @click="onDialogOK()"
+        />
+      </q-card-actions>
+
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+import { useDialogPluginComponent, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { getMyUnreadNotificationsAssociado } from "src/api/notification";
+import NotificationDetailDialog from "src/components/NotificationDetailDialog.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
+const { t } = useI18n();
+const $q = useQuasar();
+
+const loading = ref(false);
+const notifications = ref([]);
+
+const localUnreadCount = computed(() => notifications.value.filter((n) => !n.read).length);
+
+const formatDate = (dateStr) => {
+  if (!dateStr) return "";
+  const d = new Date(dateStr);
+  const today = new Date();
+  const isToday =
+    d.getDate() === today.getDate() &&
+    d.getMonth() === today.getMonth() &&
+    d.getFullYear() === today.getFullYear();
+  if (isToday) return t("common.terms.today");
+  return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
+};
+
+const openDetail = (item) => {
+  $q.dialog({ component: NotificationDetailDialog, componentProps: { item } }).onOk((markedId) => {
+    if (markedId) {
+      const found = notifications.value.find((n) => n.id === markedId);
+      if (found) found.read = true;
+    }
+  });
+};
+
+const fetchUnread = async () => {
+  loading.value = true;
+  try {
+    notifications.value = await getMyUnreadNotificationsAssociado();
+  } catch (e) {
+    console.error(e);
+  } finally {
+    loading.value = false;
+  }
+};
+
+fetchUnread();
+</script>
+
+<style scoped lang="scss">
+@use "src/css/quasar.variables.scss" as vars;
+
+.unread-dialog-card {
+  width: 90vw;
+  max-width: 480px;
+  min-width: 300px;
+
+  &__scroll {
+    max-height: 55vh;
+    overflow-y: auto;
+  }
+}
+
+.notif-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.notif-item {
+  background: vars.$violet-light;
+  border-radius: 10px;
+  padding: 12px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 10px;
+  cursor: pointer;
+  transition: opacity 0.15s;
+
+  &:active {
+    opacity: 0.75;
+  }
+
+  &--unread {
+    border-left: 3px solid vars.$violet-normal;
+  }
+
+  &__icon {
+    flex-shrink: 0;
+  }
+
+  &__content {
+    flex: 1;
+    min-width: 0;
+  }
+
+  &__title {
+    font-size: 14px;
+    font-weight: 600;
+    color: vars.$violet-normal;
+    margin-bottom: 3px;
+    line-height: 1.3;
+
+    &--read {
+      color: vars.$color-text-2;
+      font-weight: 400;
+    }
+  }
+
+  &__message {
+    font-size: 12px;
+    color: vars.$color-text-2;
+    line-height: 1.4;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+  }
+
+  &__date {
+    font-size: 11px;
+    color: vars.$violet-normal;
+    margin-top: 4px;
+    opacity: 0.7;
+  }
+}
+</style>

+ 8 - 1
src/i18n/locales/en.json

@@ -77,7 +77,8 @@
       "download_certificate": "Download certificate",
       "download_boleto": "Download Boleto",
       "copy_paste_code": "Copy and paste the code below to make the payment",
-      "go_home": "Go Home"
+      "go_home": "Go Home",
+      "close": "Close"
     },
     "terms": {
       "actions": "Actions",
@@ -494,6 +495,8 @@
     },
     "no_notifications": "No notifications at the moment",
     "mark_as_read": "Mark as read",
+    "on_leave_appointment": "You are on leave. Please contact the secretariat for more information.",
+    "on_leave_carteirinha": "Member on leave. If you have questions, please contact the secretariat.",
     "validity": "Validity",
     "language_options": {
       "pt": "Português",
@@ -642,6 +645,10 @@
     }
   },
   "notification": {
+    "pending_read_title":    "Pending Notifications",
+    "pending_read_subtitle": "You have unread notifications. Read all of them to continue.",
+    "pending_read_hint":     "Click on each notification to mark it as read.",
+    "pending_read_close":    "Close",
     "new": "New notification",
     "history": "History",
     "title_label": "Title",

+ 8 - 1
src/i18n/locales/es.json

@@ -77,7 +77,8 @@
       "download_certificate": "Descargar certificado",
       "download_boleto": "Descargar Boleto",
       "copy_paste_code": "Copie y pegue el código a continuación para realizar el pago",
-      "go_home": "Ir al Inicio"
+      "go_home": "Ir al Inicio",
+      "close": "Cerrar"
     },
     "terms": {
       "actions": "Acciones",
@@ -494,6 +495,8 @@
     },
     "no_notifications": "Sin notificaciones en este momento",
     "mark_as_read": "Marcar como leída",
+    "on_leave_appointment": "Está de baja. Comuníquese con la secretaría para más información.",
+    "on_leave_carteirinha": "Asociado de baja. Si tiene dudas, comuníquese con la secretaría.",
     "validity": "Validez",
     "language_options": {
       "pt": "Português",
@@ -642,6 +645,10 @@
     }
   },
   "notification": {
+    "pending_read_title":    "Notificaciones Pendientes",
+    "pending_read_subtitle": "Tiene notificaciones no leídas. Léalas todas para continuar.",
+    "pending_read_hint":     "Haga clic en cada notificación para marcarla como leída.",
+    "pending_read_close":    "Cerrar",
     "new": "Nueva notificación",
     "history": "Historial",
     "title_label": "Título",

+ 8 - 3
src/i18n/locales/pt.json

@@ -77,7 +77,8 @@
       "download_certificate": "Baixar certificado",
       "download_boleto": "Baixar Boleto",
       "copy_paste_code": "Copie e cole o código abaixo para efetuar o pagamento",
-      "go_home": "Ir para o Início"
+      "go_home": "Ir para o Início",
+      "close": "Fechar"
     },
     "terms": {
       "actions": "Ações",
@@ -494,6 +495,8 @@
     },
     "no_notifications": "Nenhuma notificação no momento",
     "mark_as_read": "Marcar como lida",
+    "on_leave_appointment": "Você está afastado. Entre em contato com a secretaria para mais informações.",
+    "on_leave_carteirinha": "Associado afastado. Em caso de dúvidas, entre em contato com a secretaria.",
     "validity": "Validade",
     "language_options": {
       "pt": "Português",
@@ -641,6 +644,10 @@
     }
   },
   "notification": {
+    "pending_read_title":    "Notificações Pendentes",
+    "pending_read_subtitle": "Você possui notificações não lidas. Leia todas para continuar.",
+    "pending_read_hint":     "Clique em cada notificação para marcá-la como lida.",
+    "pending_read_close":    "Fechar",
     "new": "Nova notificação",
     "history": "Histórico",
     "title_label": "Título",
@@ -677,8 +684,6 @@
     "aprovados_automaticamente": "Aprovados Automaticamente",
     "associado": "Associado",
     "solicitar": "Solicitar",
-    "confirm_approve": "Tem certeza que deseja aprovar este agendamento?",
-    "confirm_reject": "Tem certeza que deseja recusar este agendamento?",
     "status": {
       "pendente": "Aguardando",
       "confirmado": "Aprovado",

+ 14 - 1
src/layouts/MainLayout.vue

@@ -48,12 +48,16 @@
 <script setup>
 import { ref, computed, onMounted, useTemplateRef, watch } from "vue";
 import { useRoute } from "vue-router";
+import { useQuasar } from "quasar";
 import { userStore } from "src/stores/user";
+import { getMyUnreadNotificationsAssociado } from "src/api/notification";
+import UnreadNotificationsDialog from "src/components/UnreadNotificationsDialog.vue";
 
 import LeftMenuLayout from "src/components/layout/LeftMenuLayout.vue";
 import LeftMenuLayoutMobile from "src/components/layout/LeftMenuLayoutMobile.vue";
 
 const route = useRoute();
+const $q = useQuasar();
 const store = userStore();
 
 const leftDrawerOpen = ref(false);
@@ -70,7 +74,16 @@ const toggleLeftDrawer = () => {
   leftDrawerOpen.value = !leftDrawerOpen.value;
 };
 
-onMounted(() => {});
+onMounted(async () => {
+  try {
+    const unread = await getMyUnreadNotificationsAssociado();
+    if (unread && unread.length > 0) {
+      $q.dialog({ component: UnreadNotificationsDialog });
+    }
+  } catch {
+    // silent — não bloqueia o app se a checagem falhar
+  }
+});
 
 watch(route, (value) => {
   if (oldValue.path != value.path) {

+ 37 - 32
src/pages/associado/agendamentos/AgendamentosPage.vue

@@ -17,7 +17,12 @@
 
       <!-- Tab: Nova Solicitação -->
       <div v-if="activeTab === 'novo'">
-        <q-card flat class="form-card">
+        <div v-if="isOnLeave" class="on-leave-notice">
+          <q-icon name="mdi-account-off-outline" size="32px" color="warning" />
+          <div class="on-leave-notice__text">{{ $t('associado.on_leave_appointment') }}</div>
+        </div>
+
+        <q-card v-else flat class="form-card">
           <q-card-section>
             <q-form ref="appointmentFormRef" class="form-column" @submit="submitAppointment">
               <PartnerAgreementSelect
@@ -35,22 +40,6 @@
                 class="input-violet"
                 for-associado
               />
-              <DefaultInputDatePicker
-                v-model:untreated-date="appointmentForm.date"
-                :label="$t('common.terms.date')"
-                :rules="[inputRules.required]"
-                class="input-violet"
-                placeholder="dd/mm/aaaa"
-                lazy-rules
-              />
-              <DefaultInput
-                v-model="appointmentForm.time"
-                :label="$t('common.terms.hour')"
-                :rules="[inputRules.required]"
-                mask="##:##"
-                placeholder="HH:MM"
-                class="input-violet"
-              />
               <DefaultInput
                 v-model="appointmentForm.observations"
                 :label="$t('associado.notes')"
@@ -159,7 +148,6 @@ import { createAppointment, getMyAppointments, updateAppointment } from "src/api
 import { useInputRules } from "src/composables/useInputRules";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 import DefaultInput from "src/components/defaults/DefaultInput.vue";
-import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
 import PartnerAgreementSelect from "src/components/selects/PartnerAgreementSelect.vue";
 import PartnerAgreementServiceSelect from "src/components/selects/PartnerAgreementServiceSelect.vue";
 import { userStore } from "src/stores/user";
@@ -170,6 +158,11 @@ const { inputRules } = useInputRules();
 const appointmentFormRef = useTemplateRef("appointmentFormRef");
 const user = userStore();
 
+const isOnLeave = computed(() => {
+  const s = user.user?.status;
+  return s === "on_leave" || s?.value === "on_leave";
+});
+
 const activeTab = ref("novo");
 const PER_PAGE = 12;
 const currentPage = ref(1);
@@ -181,7 +174,7 @@ const tabs = computed(() => [
 
 const selectedPartner  = ref(null);
 const selectedService  = ref(null);
-const appointmentForm  = reactive({ date: "", time: "", observations: "" });
+const appointmentForm  = reactive({ observations: "" });
 const submitting       = ref(false);
 const editingId        = ref(null);
 const appointments     = ref([]);
@@ -231,12 +224,10 @@ watch(activeTab, (val) => {
 });
 
 const resetForm = () => {
-  selectedPartner.value   = null;
-  selectedService.value   = null;
-  appointmentForm.date    = "";
-  appointmentForm.time    = "";
+  selectedPartner.value        = null;
+  selectedService.value        = null;
   appointmentForm.observations = "";
-  editingId.value         = null;
+  editingId.value              = null;
 };
 
 const submitAppointment = async () => {
@@ -246,8 +237,6 @@ const submitAppointment = async () => {
   const payload = {
     partner_agreement_id:         selectedPartner.value.value,
     partner_agreement_service_id: selectedService.value.value,
-    date:         appointmentForm.date,
-    time:         appointmentForm.time,
     observations: appointmentForm.observations,
   };
   try {
@@ -268,13 +257,11 @@ const submitAppointment = async () => {
 };
 
 const onEditAppointment = (apt) => {
-  editingId.value        = apt.id;
-  selectedPartner.value  = { value: apt.partner_agreement_id, label: apt.partner_agreement?.trade_name || apt.partner_agreement?.company_name || "" };
-  selectedService.value  = { value: apt.partner_agreement_service_id, label: apt.partner_agreement_service?.name || "" };
-  appointmentForm.date   = apt.date ?? "";
-  appointmentForm.time   = apt.time ?? "";
+  editingId.value              = apt.id;
+  selectedPartner.value        = { value: apt.partner_agreement_id, label: apt.partner_agreement?.trade_name || apt.partner_agreement?.company_name || "" };
+  selectedService.value        = { value: apt.partner_agreement_service_id, label: apt.partner_agreement_service?.name || "" };
   appointmentForm.observations = apt.observations ?? "";
-  activeTab.value        = "novo";
+  activeTab.value              = "novo";
 };
 </script>
 
@@ -312,6 +299,24 @@ const onEditAppointment = (apt) => {
   &--selected { background: #4d1658; color: #fff; }
 }
 
+.on-leave-notice {
+  background: white;
+  border-radius: 12px;
+  padding: 32px 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 12px;
+  text-align: center;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
+
+  &__text {
+    font-size: 14px;
+    color: vars.$color-text-2;
+    line-height: 1.5;
+  }
+}
+
 .form-card {
   background: white !important;
   border-radius: 12px;

+ 24 - 1
src/pages/associado/carteirinha/CarteirinhaPage.vue

@@ -12,6 +12,11 @@
           <img :src="LogoSerPratiParceiro" class="card-logo" alt="SerPrati" />
         </div>
 
+        <div v-if="isOnLeave" class="on-leave-banner">
+          <q-icon name="mdi-account-off-outline" size="16px" class="q-mr-xs" />
+          {{ $t('associado.on_leave_carteirinha') }}
+        </div>
+
         <div class="card-body" :class="isVertical ? 'card-body--vertical' : 'card-body--horizontal'">
           <q-avatar
             class="card-avatar"
@@ -85,7 +90,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted, useTemplateRef, watch, nextTick } from "vue";
+import { ref, computed, onMounted, useTemplateRef, watch, nextTick } from "vue";
 import QRCode from "qrcode";
 import { userStore } from "src/stores/user";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
@@ -93,6 +98,11 @@ import LogoSerPratiParceiro from "src/assets/logo_serprati_parceiro.svg";
 
 const store = userStore();
 const user = ref(null);
+
+const isOnLeave = computed(() => {
+  const s = user.value?.status;
+  return s === "on_leave" || s?.value === "on_leave";
+});
 const isVertical = ref(false);
 const qrCanvas = useTemplateRef("qrCanvas");
 const qrReady = ref(false);
@@ -139,6 +149,19 @@ onMounted(async () => {
     }
   }
 
+  .on-leave-banner {
+    background: rgba(255, 152, 0, 0.85);
+    color: #fff;
+    font-size: 11px;
+    font-weight: 600;
+    text-align: center;
+    padding: 6px 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 4px;
+  }
+
   .card-body {
     padding: 8px 16px 16px;
     display: flex;

+ 11 - 9
src/pages/associado/notificacoes/NotificacoesAssociadoPage.vue

@@ -72,10 +72,13 @@
 
 <script setup>
 import { ref, computed, onMounted } from "vue";
+import { useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
-import { getMyNotificationsAssociado, markNotificationAsReadAssociado } from "src/api/notification";
+import { getMyNotificationsAssociado } from "src/api/notification";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+import NotificationDetailDialog from "src/components/NotificationDetailDialog.vue";
 
+const $q = useQuasar();
 const { t } = useI18n();
 
 const loading = ref(true);
@@ -110,14 +113,13 @@ const formatDate = (dateStr) => {
   return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
 };
 
-const onRead = async (item) => {
-  if (item.read) return;
-  try {
-    await markNotificationAsReadAssociado(item.id);
-    item.read = true;
-  } catch (e) {
-    console.error(e);
-  }
+const onRead = (item) => {
+  $q.dialog({ component: NotificationDetailDialog, componentProps: { item } }).onOk((markedId) => {
+    if (markedId) {
+      const found = notifications.value.find((n) => n.id === markedId);
+      if (found) found.read = true;
+    }
+  });
 };
 
 onMounted(async () => {