Ver Fonte

refatoracao rotas: padronizacao de rotas e permissoes por tipo de usuario

Gustavo Zanatta há 3 semanas atrás
pai
commit
7e81bd4b5a

+ 13 - 4
src/api/appointment.js

@@ -1,22 +1,31 @@
 import api from "src/api";
 
+// ─── Rotas do Associado ───────────────────────────────────────────────────────
+
 export const getMyAppointments = async () => {
-  const { data } = await api.get("/appointment/my");
+  const { data } = await api.get("/associado/appointment/my");
   return data.payload;
 };
 
 export const createAppointment = async (appointment) => {
-  const { data } = await api.post("/appointment", appointment);
+  const { data } = await api.post("/associado/appointment", appointment);
   return data.payload;
 };
 
 export const cancelAppointment = async (id) => {
-  const { data } = await api.put(`/appointment/${id}`, { status: "cancelado" });
+  const { data } = await api.put(`/associado/appointment/${id}`, { status: "cancelado" });
   return data.payload;
 };
 
 export const updateAppointment = async (id, payload) => {
-  const { data } = await api.put(`/appointment/${id}`, payload);
+  const { data } = await api.put(`/associado/appointment/${id}`, payload);
+  return data.payload;
+};
+
+// ─── Rotas do Parceiro ────────────────────────────────────────────────────────
+
+export const getPartnerAppointments = async () => {
+  const { data } = await api.get("/parceiro/appointment");
   return data.payload;
 };
 

+ 36 - 0
src/api/notification.js

@@ -1,5 +1,41 @@
 import api from "src/api";
 
+// ─── Rotas do Associado ───────────────────────────────────────────────────────
+
+export const getMyNotificationsAssociado = async () => {
+  const { data } = await api.get("/associado/notification/my");
+  return data.payload;
+};
+
+export const getMyUnreadNotificationsAssociado = async () => {
+  const { data } = await api.get("/associado/notification/my/unread");
+  return data.payload;
+};
+
+export const markNotificationAsReadAssociado = async (sendId) => {
+  const { data } = await api.patch(`/associado/notification/${sendId}/read`);
+  return data.payload;
+};
+
+// ─── Rotas do Parceiro ────────────────────────────────────────────────────────
+
+export const getMyNotificationsParceiro = async () => {
+  const { data } = await api.get("/parceiro/notification/my");
+  return data.payload;
+};
+
+export const getMyUnreadNotificationsParceiro = async () => {
+  const { data } = await api.get("/parceiro/notification/my/unread");
+  return data.payload;
+};
+
+export const markNotificationAsReadParceiro = async (sendId) => {
+  const { data } = await api.patch(`/parceiro/notification/${sendId}/read`);
+  return data.payload;
+};
+
+// ─── Rotas Admin ──────────────────────────────────────────────────────────────
+
 export const getMyUnreadNotifications = async () => {
   const { data } = await api.get("/notification/my/unread");
   return data.payload;

+ 19 - 0
src/api/partnerAgreement.js

@@ -75,6 +75,25 @@ export const deletePartnerMedia = async (id, mediaId) => {
   await api.delete(`/partner-agreement/${id}/media/${mediaId}`);
 };
 
+// ─── Rotas do Associado ───────────────────────────────────────────────────────
+
+export const getConvenios = async () => {
+  const { data } = await api.get("/associado/partner-agreement");
+  return data.payload;
+};
+
+export const getConvenio = async (id) => {
+  const { data } = await api.get(`/associado/partner-agreement/${id}`);
+  return data.payload;
+};
+
+export const getConvenioDados = async (id) => {
+  const { data } = await api.get(`/associado/partner-agreement/${id}/dados`);
+  return data.payload;
+};
+
+// ─── Rotas do Parceiro (my) ───────────────────────────────────────────────────
+
 export const getMyPartnerAgreement = async () => {
   const { data } = await api.get("/partner-agreement/my");
   return data.payload;

+ 10 - 0
src/api/partnerAgreementService.js

@@ -1,5 +1,15 @@
 import api from "src/api";
 
+export const getServicesByConvenio = async (partnerAgreementId) => {
+  const { data } = await api.get(`/associado/partner-agreement-service/partner/${partnerAgreementId}`);
+  return data.payload;
+};
+
+export const getConvenioService = async (id) => {
+  const { data } = await api.get(`/associado/partner-agreement-service/${id}`);
+  return data.payload;
+};
+
 export const getServicesByPartner = async (partnerAgreementId) => {
   const { data } = await api.get(`/partner-agreement-service/partner/${partnerAgreementId}`);
   return data.payload;

+ 24 - 0
src/api/storeItem.js

@@ -1,5 +1,29 @@
 import api from "src/api";
 
+// ─── Rotas do Associado ───────────────────────────────────────────────────────
+
+export const getStoreItemsAssociado = async () => {
+  const { data } = await api.get("/associado/store-item");
+  return data.payload;
+};
+
+export const getStoreItemAssociado = async (id) => {
+  const { data } = await api.get(`/associado/store-item/${id}`);
+  return data.payload;
+};
+
+export const getMyInterestsAssociado = async () => {
+  const { data } = await api.get("/associado/store-item/my/interests");
+  return data.payload;
+};
+
+export const toggleInterestAssociado = async (id) => {
+  const { data } = await api.post(`/associado/store-item/${id}/interest`);
+  return data.payload;
+};
+
+// ─── Rotas Admin ──────────────────────────────────────────────────────────────
+
 export const getStoreItems = async () => {
   const { data } = await api.get("/store-item");
   return data.payload;

+ 7 - 3
src/components/selects/PartnerAgreementSelect.vue

@@ -24,12 +24,12 @@
 
 <script setup>
 import { ref, onMounted } from "vue";
-import { getPartnerAgreements } from "src/api/partnerAgreement";
+import { getPartnerAgreements, getConvenios } from "src/api/partnerAgreement";
 import { normalizeString } from "src/helpers/utils";
 import { useI18n } from "vue-i18n";
 import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 
-const { label, placeholder } = defineProps({
+const { label, placeholder, forAssociado } = defineProps({
   label: {
     type: String,
     default: () => useI18n().t("ui.navigation.convenios"),
@@ -38,6 +38,10 @@ const { label, placeholder } = defineProps({
     type: String,
     default: () => useI18n().t("common.actions.search"),
   },
+  forAssociado: {
+    type: Boolean,
+    default: false,
+  },
 });
 
 const selectedPartner = defineModel({ type: Object });
@@ -56,7 +60,7 @@ const filterFn = (val, update) => {
 
 onMounted(async () => {
   try {
-    const partners = await getPartnerAgreements();
+    const partners = await (forAssociado ? getConvenios() : getPartnerAgreements());
     baseOptions.value = partners.map((p) => ({
       label: p.trade_name || p.company_name,
       value: p.id,

+ 7 - 3
src/components/selects/PartnerAgreementServiceSelect.vue

@@ -20,11 +20,11 @@
 
 <script setup>
 import { ref, watch } from "vue";
-import { getServicesByPartner } from "src/api/partnerAgreementService";
+import { getServicesByPartner, getServicesByConvenio } from "src/api/partnerAgreementService";
 import { useI18n } from "vue-i18n";
 import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 
-const { label, placeholder, partnerAgreementId } = defineProps({
+const { label, placeholder, partnerAgreementId, forAssociado } = defineProps({
   label: {
     type: String,
     default: () => useI18n().t("associado.service"),
@@ -37,6 +37,10 @@ const { label, placeholder, partnerAgreementId } = defineProps({
     type: Number,
     default: null,
   },
+  forAssociado: {
+    type: Boolean,
+    default: false,
+  },
 });
 
 const selectedService = defineModel({ type: Object });
@@ -53,7 +57,7 @@ watch(
     if (!id) return;
     loading.value = true;
     try {
-      const services = await getServicesByPartner(id);
+      const services = await (forAssociado ? getServicesByConvenio(id) : getServicesByPartner(id));
       serviceOptions.value = services.map((s) => ({
         label: s.name,
         value: s.id,

+ 2 - 0
src/pages/associado/agendamentos/AgendamentosPage.vue

@@ -26,6 +26,7 @@
                   :label="$t('ui.navigation.convenios')"
                   :rules="[inputRules.required]"
                   class="col-12 input-violet"
+                  for-associado
                 />
                 <PartnerAgreementServiceSelect
                   v-model="selectedService"
@@ -33,6 +34,7 @@
                   :label="$t('associado.service')"
                   :rules="[inputRules.required]"
                   class="col-12 input-violet"
+                  for-associado
                 />
                 <DefaultInputDatePicker
                   v-model:untreated-date="appointmentForm.date"

+ 2 - 2
src/pages/associado/convenios/ConveniosPage.vue

@@ -145,7 +145,7 @@
 
 <script setup>
 import { ref, computed, onMounted } from "vue";
-import { getPartnerAgreements } from "src/api/partnerAgreement";
+import { getConvenios } from "src/api/partnerAgreement";
 import { getCategories } from "src/api/category";
 import { normalizeString } from "src/helpers/utils";
 import { date } from "quasar";
@@ -185,7 +185,7 @@ const openPartnerDetail = (partner) => {
 onMounted(async () => {
   try {
     const [partnerList, categoryList] = await Promise.all([
-      getPartnerAgreements(),
+      getConvenios(),
       getCategories("partner"),
     ]);
     partners.value = partnerList;

+ 2 - 2
src/pages/associado/interesses/InteressesPage.vue

@@ -53,7 +53,7 @@
 import { ref, onMounted } from "vue";
 import { useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
-import { getMyInterests, toggleInterest as apiToggleInterest } from "src/api/storeItem";
+import { getMyInterestsAssociado, toggleInterestAssociado as apiToggleInterest } from "src/api/storeItem";
 
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 
@@ -84,7 +84,7 @@ const removeInterest = async (item) => {
 
 onMounted(async () => {
   try {
-    items.value = await getMyInterests();
+    items.value = await getMyInterestsAssociado();
   } catch (e) {
     console.error(e);
   } finally {

+ 2 - 2
src/pages/associado/loja/AssociadoLojaPage.vue

@@ -178,7 +178,7 @@ import { ref, computed, onMounted, watch } from "vue";
 import { useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
 import { normalizeString } from "src/helpers/utils";
-import { getStoreItems, toggleInterest as apiToggleInterest } from "src/api/storeItem";
+import { getStoreItemsAssociado, toggleInterestAssociado as apiToggleInterest } from "src/api/storeItem";
 import { getCategories } from "src/api/category";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 
@@ -267,7 +267,7 @@ const toggleInterest = async (item) => {
 onMounted(async () => {
   try {
     const [itemsData, catsData] = await Promise.all([
-      getStoreItems(),
+      getStoreItemsAssociado(),
       getCategories("store"),
     ]);
     items.value = itemsData;

+ 3 - 3
src/pages/associado/notificacoes/NotificacoesAssociadoPage.vue

@@ -92,7 +92,7 @@
 <script setup>
 import { ref, computed, onMounted } from "vue";
 import { useI18n } from "vue-i18n";
-import { getMyNotifications, markNotificationAsRead } from "src/api/notification";
+import { getMyNotificationsAssociado, markNotificationAsReadAssociado } from "src/api/notification";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 
 const { t } = useI18n();
@@ -132,7 +132,7 @@ const formatDate = (dateStr) => {
 const onRead = async (item) => {
   if (item.read) return;
   try {
-    await markNotificationAsRead(item.id);
+    await markNotificationAsReadAssociado(item.id);
     item.read = true;
   } catch (e) {
     console.error(e);
@@ -141,7 +141,7 @@ const onRead = async (item) => {
 
 onMounted(async () => {
   try {
-    notifications.value = await getMyNotifications();
+    notifications.value = await getMyNotificationsAssociado();
   } catch (e) {
     console.error(e);
   } finally {

+ 187 - 0
src/pages/parceiros-convenios/AgendamentosParceiroPage.vue

@@ -0,0 +1,187 @@
+<template>
+  <div>
+    <DefaultHeaderPage />
+
+    <div class="q-pa-md">
+      <div v-if="loading" class="flex flex-center q-py-xl">
+        <q-spinner color="violet-normal" size="48px" />
+      </div>
+
+      <div v-else-if="appointments.length === 0" class="text-center text-grey q-py-xl">
+        {{ $t("http.errors.no_records_found") }}
+      </div>
+
+      <q-table
+        v-else
+        class="softpar-table q-pa-sm"
+        :rows="pagedAppointments"
+        :columns="columns"
+        row-key="id"
+        hide-pagination
+        :rows-per-page-options="[0]"
+      >
+        <template #body-cell-status="props">
+          <q-td :props="props">
+            <q-chip
+              outline
+              :color="statusColor(props.row.status)"
+              :label="$t(`agendamento.status.${props.row.status}`)"
+              size="sm"
+            />
+          </q-td>
+        </template>
+
+        <template #body-cell-acoes="props">
+          <q-td :props="props">
+            <q-btn
+              dense
+              round
+              flat
+              icon="mdi-check"
+              color="positive"
+              size="sm"
+              :disable="props.row.status !== 'pendente'"
+              :loading="actionId === props.row.id && actionType === 'approve'"
+              @click="onApprove(props.row)"
+            />
+            <q-btn
+              dense
+              round
+              flat
+              icon="mdi-close"
+              color="negative"
+              size="sm"
+              :disable="props.row.status !== 'pendente'"
+              :loading="actionId === props.row.id && actionType === 'reject'"
+              @click="onReject(props.row)"
+            />
+          </q-td>
+        </template>
+      </q-table>
+
+      <div v-if="totalPages > 1" class="flex flex-center q-mt-lg">
+        <q-pagination
+          v-model="currentPage"
+          :max="totalPages"
+          boundary-numbers
+          color="violet-normal"
+          active-color="violet-normal"
+          direction-links
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import { useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { getPartnerAppointments, approveAppointment, rejectAppointment } from "src/api/appointment";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+
+const $q = useQuasar();
+const { t } = useI18n();
+
+const loading = ref(true);
+const appointments = ref([]);
+const currentPage = ref(1);
+const PER_PAGE = 15;
+const actionId = ref(null);
+const actionType = ref(null);
+
+const columns = computed(() => [
+  { name: "pedido", label: t("agendamento.col.pedido"), field: "order_number", align: "left" },
+  { name: "associado", label: t("agendamento.associado"), field: (row) => row.user?.name || "—", align: "left" },
+  { name: "servico", label: t("agendamento.col.servico"), field: (row) => row.partner_agreement_service?.name || "—", align: "left" },
+  { name: "solicitacao", label: t("agendamento.col.solicitacao"), field: (row) => formatDate(row.created_at), align: "left" },
+  { name: "horario", label: t("common.terms.hour2"), field: (row) => formatDateTime(row.date, row.time), align: "left" },
+  { name: "acoes", label: t("common.terms.actions"), field: "id", align: "center" },
+  { name: "status", label: t("common.terms.status"), field: "status", align: "center" },
+]);
+
+const totalPages = computed(() => Math.max(1, Math.ceil(appointments.value.length / PER_PAGE)));
+
+const pagedAppointments = computed(() => {
+  const start = (currentPage.value - 1) * PER_PAGE;
+  return appointments.value.slice(start, start + PER_PAGE);
+});
+
+const statusColor = (status) => {
+  const map = { pendente: "warning", confirmado: "positive", cancelado: "negative", recusado: "negative", concluido: "grey" };
+  return map[status] ?? "grey";
+};
+
+const formatDate = (isoStr) => {
+  if (!isoStr) return "—";
+  const d = new Date(isoStr);
+  if (isNaN(d)) return "—";
+  return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
+};
+
+const formatDateTime = (date, time) => {
+  if (!date) return "—";
+  const d = new Date(date + "T00:00:00");
+  if (isNaN(d)) return "—";
+  const dateStr = d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
+  return time ? `${dateStr} ${time}` : dateStr;
+};
+
+const onApprove = (row) => {
+  $q.dialog({
+    title: t("common.ui.messages.confirm_action"),
+    message: t("agendamento.confirm_approve"),
+    cancel: true,
+    persistent: true,
+  }).onOk(async () => {
+    actionId.value = row.id;
+    actionType.value = "approve";
+    try {
+      await approveAppointment(row.id);
+      row.status = "confirmado";
+      $q.notify({ type: "positive", message: t("http.success") });
+    } catch {
+      $q.notify({ type: "negative", message: t("http.errors.failed") });
+    } finally {
+      actionId.value = null;
+      actionType.value = null;
+    }
+  });
+};
+
+const onReject = (row) => {
+  $q.dialog({
+    title: t("common.ui.messages.confirm_action"),
+    message: t("agendamento.confirm_reject"),
+    cancel: true,
+    persistent: true,
+  }).onOk(async () => {
+    actionId.value = row.id;
+    actionType.value = "reject";
+    try {
+      await rejectAppointment(row.id);
+      row.status = "recusado";
+      $q.notify({ type: "positive", message: t("http.success") });
+    } catch {
+      $q.notify({ type: "negative", message: t("http.errors.failed") });
+    } finally {
+      actionId.value = null;
+      actionType.value = null;
+    }
+  });
+};
+
+onMounted(async () => {
+  try {
+    appointments.value = await getPartnerAppointments();
+  } catch (e) {
+    console.error(e);
+  } finally {
+    loading.value = false;
+  }
+});
+</script>
+
+<style lang="scss">
+@import "src/css/table.scss";
+</style>

+ 202 - 0
src/pages/parceiros-convenios/NotificacoesParceiroPage.vue

@@ -0,0 +1,202 @@
+<template>
+  <div>
+    <DefaultHeaderPage />
+
+    <div class="q-pt-sm">
+
+      <div v-if="loading" class="flex flex-center q-pa-xl">
+        <q-spinner color="violet-normal" size="50px" />
+      </div>
+
+      <div v-else-if="pagedItems.length === 0" class="flex flex-center q-pa-xl text-grey-6">
+        {{ $t('notification.empty') }}
+      </div>
+
+      <div v-else>
+        <div class="row q-px-md q-col-gutter-md">
+          <div
+            v-for="item in pagedItems"
+            :key="item.id"
+            class="col-xl-4 col-lg-4 col-md-4 col-sm-6 col-12"
+          >
+            <q-card
+              flat
+              bordered
+              class="notif-card"
+              :class="{ 'notif-card--unread': !item.read }"
+              @click="onRead(item)"
+            >
+              <div class="notif-card__image">
+                <img
+                  v-if="imageUrl(item)"
+                  :src="imageUrl(item)"
+                  alt=""
+                  class="notif-card__img"
+                />
+                <div v-else class="notif-card__placeholder flex flex-center">
+                  <q-icon
+                    name="mdi-bell-outline"
+                    size="40px"
+                    :color="item.read ? 'grey-4' : 'violet-normal'"
+                  />
+                </div>
+              </div>
+
+              <q-card-section class="q-pt-sm q-pb-xs">
+                <div class="row items-start justify-between no-wrap q-mb-xs">
+                  <div
+                    class="notif-card__title text-weight-bold ellipsis"
+                    :class="item.read ? 'text-grey-7' : 'text-violet-normal'"
+                  >
+                    {{ item.notification?.title }}
+                  </div>
+                  <q-badge
+                    v-if="!item.read"
+                    color="violet-normal"
+                    rounded
+                    class="q-ml-xs"
+                    style="flex-shrink: 0"
+                  />
+                </div>
+                <div class="notif-card__message text-caption text-grey-7">
+                  {{ item.notification?.message }}
+                </div>
+              </q-card-section>
+
+              <q-card-actions class="q-pt-xs q-pb-sm q-px-md">
+                <div class="text-caption text-grey-6">
+                  {{ formatDate(item.created_at) }}
+                </div>
+              </q-card-actions>
+            </q-card>
+          </div>
+        </div>
+
+        <div v-if="totalPages > 1" class="flex flex-center q-mt-lg">
+          <q-pagination
+            v-model="currentPage"
+            :max="totalPages"
+            :max-pages="6"
+            boundary-numbers
+            color="violet-normal"
+            active-color="violet-normal"
+            direction-links
+          />
+        </div>
+      </div>
+
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import { useI18n } from "vue-i18n";
+import { getMyNotificationsParceiro, markNotificationAsReadParceiro } from "src/api/notification";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+
+const { t } = useI18n();
+
+const loading = ref(true);
+const notifications = ref([]);
+const currentPage = ref(1);
+const PER_PAGE = 12;
+
+const totalPages = computed(() => Math.max(1, Math.ceil(notifications.value.length / PER_PAGE)));
+
+const pagedItems = computed(() => {
+  const start = (currentPage.value - 1) * PER_PAGE;
+  return notifications.value.slice(start, start + PER_PAGE);
+});
+
+const imageUrl = (item) => {
+  const media = item.notification?.media?.[0]?.url;
+  if (media) return media;
+  const direct = 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 date = new Date(dateStr);
+  const today = new Date();
+  const isToday =
+    date.getDate() === today.getDate() &&
+    date.getMonth() === today.getMonth() &&
+    date.getFullYear() === today.getFullYear();
+  if (isToday) return t("common.terms.today");
+  return date.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
+};
+
+const onRead = async (item) => {
+  if (item.read) return;
+  try {
+    await markNotificationAsReadParceiro(item.id);
+    item.read = true;
+  } catch (e) {
+    console.error(e);
+  }
+};
+
+onMounted(async () => {
+  try {
+    notifications.value = await getMyNotificationsParceiro();
+  } catch (e) {
+    console.error(e);
+  } finally {
+    loading.value = false;
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.notif-card {
+  border-radius: 8px;
+  overflow: hidden;
+  cursor: pointer;
+  transition: box-shadow 0.2s, transform 0.15s;
+
+  &:hover {
+    box-shadow: 0 4px 16px rgba(102, 29, 117, 0.15);
+    transform: translateY(-2px);
+  }
+
+  &--unread {
+    border-top: 3px solid #7b2d97;
+  }
+
+  &__image {
+    width: 100%;
+    height: 120px;
+    overflow: hidden;
+  }
+
+  &__img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    display: block;
+  }
+
+  &__placeholder {
+    width: 100%;
+    height: 100%;
+    background: #f0e8f1;
+  }
+
+  &__title {
+    font-size: 14px;
+    line-height: 1.3;
+    min-width: 0;
+  }
+
+  &__message {
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    line-height: 1.4;
+  }
+}
+</style>

+ 26 - 0
src/router/routes/parceiro.route.js

@@ -1,4 +1,30 @@
 export default [
+  {
+    path: "/parceiro/agendamentos",
+    name: "AgendamentosPage",
+    component: () => import("pages/parceiros-convenios/AgendamentosParceiroPage.vue"),
+    meta: {
+      title: { value: "ui.navigation.appointments", translate: true },
+      requireAuth: true,
+      requiredPermission: "parceiro.agendamento",
+      breadcrumbs: [
+        { name: "AgendamentosPage", title: "ui.navigation.appointments", translate: true },
+      ],
+    },
+  },
+  {
+    path: "/parceiro/notificacoes",
+    name: "NotificacoesPage",
+    component: () => import("pages/parceiros-convenios/NotificacoesParceiroPage.vue"),
+    meta: {
+      title: { value: "ui.navigation.notifications", translate: true },
+      requireAuth: true,
+      requiredPermission: "parceiro.notificacao",
+      breadcrumbs: [
+        { name: "NotificacoesPage", title: "ui.navigation.notifications", translate: true },
+      ],
+    },
+  },
   {
     path: "/parceiro/meus-dados",
     name: "MeusDadosPage",

+ 1 - 1
src/stores/navigation.js

@@ -142,7 +142,7 @@ export const navigationStore = defineStore("navigation", () => {
       name: "NotificacoesAssociadoPage",
       icon: "mdi-bell-outline",
       permission: false,
-      permissionScope: "notificacao",
+      permissionScope: "associado.notificacao",
       allowedTypes: ["associado"],
     },