Преглед изворни кода

feat: :sparkles: feat (notificacoes adm) criada pagina de notificacoes adm

foi criada a pagina de notificacoes no layout adm, incluindo a tab de cadastrar nova notificacao definidio destinatarios, e a tela de visualizar todas as notificacoes

fase:dev | origin:escopo
Gustavo Zanatta пре 3 недеља
родитељ
комит
7b0889b46a

+ 29 - 0
src/api/notification.js

@@ -9,3 +9,32 @@ export const markNotificationAsRead = async (sendId) => {
   const { data } = await api.patch(`/notification/${sendId}/read`);
   return data.payload;
 };
+
+export const getNotificationsPaginated = async ({ page = 1, perPage = 12 } = {}) => {
+  const { data } = await api.get("/notification/paginated", {
+    params: { page, per_page: perPage },
+  });
+  return data.payload;
+};
+
+export const sendNotification = async (payload) => {
+  const form = new FormData();
+  form.append("title", payload.title);
+  form.append("message", payload.message);
+  form.append("recipient", payload.recipient);
+
+  if (payload.recipient_position_id != null) {
+    form.append("recipient_position_id", payload.recipient_position_id);
+  }
+  if (payload.recipient_sector_id != null) {
+    form.append("recipient_sector_id", payload.recipient_sector_id);
+  }
+  if (payload.image instanceof File) {
+    form.append("image", payload.image);
+  }
+
+  const { data } = await api.post("/notification", form, {
+    headers: { "Content-Type": "multipart/form-data" },
+  });
+  return data.payload;
+};

+ 104 - 67
src/i18n/locales/en.json

@@ -4,13 +4,27 @@
       "description": "Manage the products available in the store"
     },
     "associado": {
-      "profile": { "description": "View and edit your personal data and dependents" },
-      "carteirinha": { "description": "Your digital membership card with QR Code" },
-      "convenios": { "description": "Browse available partners and agreements" },
-      "loja": { "description": "Browse available store items" },
-      "interesses": { "description": "Items you marked as interest" },
-      "agendamentos": { "description": "Request and track your appointments" },
-      "notificacoes": { "description": "Your notifications and alerts" }
+      "profile": {
+        "description": "View and edit your personal data and dependents"
+      },
+      "carteirinha": {
+        "description": "Your digital membership card with QR Code"
+      },
+      "convenios": {
+        "description": "Browse available partners and agreements"
+      },
+      "loja": {
+        "description": "Browse available store items"
+      },
+      "interesses": {
+        "description": "Items you marked as interest"
+      },
+      "agendamentos": {
+        "description": "Request and track your appointments"
+      },
+      "notificacoes": {
+        "description": "Your notifications and alerts"
+      }
     },
     "gestao_associados": {
       "description": "View, edit, and add members to the system"
@@ -35,6 +49,9 @@
     },
     "parceiros_convenios": {
       "description": "View, edit and add partners and agreements to the system"
+    },
+    "notificacoes": {
+      "description": "History"
     }
   },
   "common": {
@@ -114,7 +131,8 @@
       "all": "All",
       "certificate": "Certificate",
       "version": "Version",
-      "whatsapp": "WhatsApp"
+      "whatsapp": "WhatsApp",
+      "today": "Today"
     },
     "months": {
       "january": "January",
@@ -466,8 +484,7 @@
       "pt": "Português",
       "en": "English",
       "es": "Español"
-    },
-    "search_placeholder": "Search Members"
+    }
   },
   "charts": {
     "nps": {
@@ -531,66 +548,86 @@
     }
   },
   "loja": {
-    "product":                    "Product",
-    "products":                   "Products",
-    "supplier_price":             "Supplier Price",
-    "supplier":                   "Supplier",
-    "stock":                       "Stock",
-    "interests":                  "Interested",
-    "interested_btn":             "INTERESTED",
-    "interested_in":              "Interested in",
-    "media":                      "Media",
-    "add_media":                  "Add Media",
-    "uploading_media":            "Uploading media...",
-    "activate":                   "Activate",
-    "deactivate":                 "Deactivate",
-    "variation":                  "Variation",
-    "variation_tamanho":          "Size",
-    "variation_cor":              "Color",
-    "variation_modelo":           "Model",
-    "variation_label":            "Name",
-    "variation_value":            "Value (R$)",
-    "variation_stock":            "Stock",
-    "variation_cor_placeholder":  "E.g.: Purple, White...",
+    "product": "Product",
+    "products": "Products",
+    "supplier_price": "Supplier Price",
+    "supplier": "Supplier",
+    "stock": "Stock",
+    "interests": "Interested",
+    "interested_btn": "INTERESTED",
+    "interested_in": "Interested in",
+    "media": "Media",
+    "add_media": "Add Media",
+    "uploading_media": "Uploading media...",
+    "activate": "Activate",
+    "deactivate": "Deactivate",
+    "variation": "Variation",
+    "variation_tamanho": "Size",
+    "variation_cor": "Color",
+    "variation_modelo": "Model",
+    "variation_label": "Name",
+    "variation_value": "Value (R$)",
+    "variation_stock": "Stock",
+    "variation_cor_placeholder": "E.g.: Purple, White...",
     "variation_modelo_placeholder": "E.g.: Associate, Premium...",
-    "variation_empty":            "No variation added",
-    "add_variation":              "Add",
-    "filter_all":                 "All",
-    "filter_recent":              "Recent",
-    "filter_others":              "Others"
+    "variation_empty": "No variation added",
+    "add_variation": "Add",
+    "filter_all": "All",
+    "filter_recent": "Recent",
+    "filter_others": "Others"
   },
   "parceiro": {
-    "category":                "Category",
-    "company_name":            "Company Name",
-    "responsible":             "Responsible",
-    "discount_percentage":     "Discount (%)",
-    "logo":                    "Logo",
-    "website":                 "Website",
-    "zip_code":                "ZIP Code",
-    "address":                 "Address",
-    "neighborhood":            "Neighborhood",
-    "working_hours":           "Working Hours",
+    "category": "Category",
+    "company_name": "Company Name",
+    "responsible": "Responsible",
+    "discount_percentage": "Discount (%)",
+    "logo": "Logo",
+    "website": "Website",
+    "zip_code": "ZIP Code",
+    "address": "Address",
+    "neighborhood": "Neighborhood",
+    "working_hours": "Working Hours",
     "working_hours_placeholder": "e.g. Mon-Fri 8am-6pm, Sat 8am-12pm",
-    "contract_start":          "Contract Start",
-    "contract_end":            "Contract End",
-    "contract_files":          "Contract Files",
-    "add_file":                "Add File",
-    "services":                "Services",
-    "service":                 "Service",
-    "service_category":        "Service Category",
-    "service_number":          "Service Number",
-    "price":                   "Price",
-    "associate_price":         "Associate Price",
-    "supplier_price":          "Supplier Price",
-    "requires_scheduling":     "Requires Scheduling",
-    "search_placeholder":      "Search for service",
-    "cadastro_title":          "Partner Registration",
-    "cadastro_parceiro":       "Partner Registration",
-    "dados_parceiro":          "Partner Data",
-    "tab_dados":               "Company Data",
-    "tab_contato":             "Contact",
-    "tab_endereco":            "Address",
-    "tab_horario":             "Schedule & Contract",
-    "tab_servicos":            "My Services"
+    "contract_start": "Contract Start",
+    "contract_end": "Contract End",
+    "contract_files": "Contract Files",
+    "add_file": "Add File",
+    "services": "Services",
+    "service": "Service",
+    "service_category": "Service Category",
+    "service_number": "Service Number",
+    "price": "Price",
+    "associate_price": "Associate Price",
+    "supplier_price": "Supplier Price",
+    "requires_scheduling": "Requires Scheduling",
+    "search_placeholder": "Search for service",
+    "cadastro_title": "Partner Registration",
+    "cadastro_parceiro": "Partner Registration",
+    "dados_parceiro": "Partner Data",
+    "tab_dados": "Company Data",
+    "tab_contato": "Contact",
+    "tab_endereco": "Address",
+    "tab_horario": "Schedule & Contract",
+    "tab_servicos": "My Services"
+  },
+  "notification": {
+    "new": "New notification",
+    "history": "History",
+    "title_label": "Title",
+    "title_placeholder": "Notification title",
+    "message_label": "Message",
+    "message_placeholder": "Write the message",
+    "recipients": "Recipients",
+    "all": "All",
+    "by_position": "Recipients By Position",
+    "by_sector": "Recipients By Sector",
+    "media": "Media",
+    "send": "Send Notification",
+    "sent_to": "Sent to {count} contacts",
+    "seen_by": "Seen by {count} contacts",
+    "details": "Details",
+    "empty": "No notifications found",
+    "recipient_associado": "Associate",
+    "recipient_parceiro": "Partner/Agreements"
   }
 }

+ 104 - 67
src/i18n/locales/es.json

@@ -4,13 +4,27 @@
       "description": "Gestione los productos disponibles en la tienda"
     },
     "associado": {
-      "profile": { "description": "Visualice y edite sus datos personales y dependientes" },
-      "carteirinha": { "description": "Su tarjeta digital con código QR de identificación" },
-      "convenios": { "description": "Consulte los socios y convenios disponibles" },
-      "loja": { "description": "Consulte los artículos disponibles en la tienda" },
-      "interesses": { "description": "Artículos que marcó como interés" },
-      "agendamentos": { "description": "Solicite y realice seguimiento de sus citas" },
-      "notificacoes": { "description": "Sus notificaciones y avisos" }
+      "profile": {
+        "description": "Visualice y edite sus datos personales y dependientes"
+      },
+      "carteirinha": {
+        "description": "Su tarjeta digital con código QR de identificación"
+      },
+      "convenios": {
+        "description": "Consulte los socios y convenios disponibles"
+      },
+      "loja": {
+        "description": "Consulte los artículos disponibles en la tienda"
+      },
+      "interesses": {
+        "description": "Artículos que marcó como interés"
+      },
+      "agendamentos": {
+        "description": "Solicite y realice seguimiento de sus citas"
+      },
+      "notificacoes": {
+        "description": "Sus notificaciones y avisos"
+      }
     },
     "gestao_associados": {
       "description": "Consulte, edite y agregue los asociados del sistema"
@@ -35,6 +49,9 @@
     },
     "parceiros_convenios": {
       "description": "Consulte, edite y agregue los socios y convenios del sistema"
+    },
+    "notificacoes": {
+      "description": "Historial"
     }
   },
   "common": {
@@ -114,7 +131,8 @@
       "all": "Todos",
       "certificate": "Certificado",
       "version": "Versión",
-      "whatsapp": "WhatsApp"
+      "whatsapp": "WhatsApp",
+      "today": "Hoy"
     },
     "months": {
       "january": "Enero",
@@ -415,7 +433,7 @@
     "dependent": "Dependiente",
     "dependents": "Dependientes",
     "import_afastamentos": "Ausencias",
-    "search_placeholder": "Buscar Asociados",
+    "search_placeholder": "Buscar por asociados",
     "no_dependents": "Ningún dependiente registrado",
     "kinship": "Parentesco",
     "dependent_statuses": {
@@ -466,8 +484,7 @@
       "pt": "Português",
       "en": "English",
       "es": "Español"
-    },
-    "search_placeholder": "Buscar por asociados"
+    }
   },
   "charts": {
     "nps": {
@@ -531,65 +548,85 @@
     }
   },
   "loja": {
-    "product":                    "Producto",
-    "products":                   "Productos",
-    "supplier_price":             "Precio Proveedor",
-    "supplier":                   "Proveedor",
-    "interests":                  "Interesados",
-    "interested_btn":             "INTERESADOS",
-    "interested_in":              "Interesados en",
-    "media":                      "Medios",
-    "add_media":                  "Agregar Medio",
-    "uploading_media":            "Subiendo medio...",
-    "activate":                   "Activar",
-    "deactivate":                 "Desactivar",
-    "variation":                  "Variación",
-    "variation_tamanho":          "Talla",
-    "variation_cor":              "Color",
-    "variation_modelo":           "Modelo",
-    "variation_label":            "Nombre",
-    "variation_value":            "Valor (R$)",
-    "variation_stock":            "Stock",
-    "variation_cor_placeholder":  "Ej: Morado, Blanco...",
+    "product": "Producto",
+    "products": "Productos",
+    "supplier_price": "Precio Proveedor",
+    "supplier": "Proveedor",
+    "interests": "Interesados",
+    "interested_btn": "INTERESADOS",
+    "interested_in": "Interesados en",
+    "media": "Medios",
+    "add_media": "Agregar Medio",
+    "uploading_media": "Subiendo medio...",
+    "activate": "Activar",
+    "deactivate": "Desactivar",
+    "variation": "Variación",
+    "variation_tamanho": "Talla",
+    "variation_cor": "Color",
+    "variation_modelo": "Modelo",
+    "variation_label": "Nombre",
+    "variation_value": "Valor (R$)",
+    "variation_stock": "Stock",
+    "variation_cor_placeholder": "Ej: Morado, Blanco...",
     "variation_modelo_placeholder": "Ej: Asociado, Premium...",
-    "variation_empty":            "Sin variaciones agregadas",
-    "add_variation":              "Agregar",
-    "filter_all":                 "Todos",
-    "filter_recent":              "Recientes",
-    "filter_others":              "Otros"
+    "variation_empty": "Sin variaciones agregadas",
+    "add_variation": "Agregar",
+    "filter_all": "Todos",
+    "filter_recent": "Recientes",
+    "filter_others": "Otros"
   },
   "parceiro": {
-    "category":                "Categoría",
-    "company_name":            "Nombre de la Empresa",
-    "responsible":             "Responsable",
-    "discount_percentage":     "Descuento (%)",
-    "logo":                    "Logo",
-    "website":                 "Sitio Web",
-    "zip_code":                "Código Postal",
-    "address":                 "Dirección",
-    "neighborhood":            "Barrio",
-    "working_hours":           "Horario de Atención",
+    "category": "Categoría",
+    "company_name": "Nombre de la Empresa",
+    "responsible": "Responsable",
+    "discount_percentage": "Descuento (%)",
+    "logo": "Logo",
+    "website": "Sitio Web",
+    "zip_code": "Código Postal",
+    "address": "Dirección",
+    "neighborhood": "Barrio",
+    "working_hours": "Horario de Atención",
     "working_hours_placeholder": "Ej: Lun-Vie 8h-18h, Sáb 8h-12h",
-    "contract_start":          "Inicio del Contrato",
-    "contract_end":            "Fin del Contrato",
-    "contract_files":          "Archivos del Contrato",
-    "add_file":                "Agregar Archivo",
-    "services":                "Servicios",
-    "service":                 "Servicio",
-    "service_category":        "Categoría del Servicio",
-    "service_number":          "Número del Servicio",
-    "price":                   "Precio",
-    "associate_price":         "Precio Asociado",
-    "supplier_price":          "Precio Proveedor",
-    "requires_scheduling":     "Requiere Programación",
-    "search_placeholder":      "Buscar por servicio",
-    "cadastro_title":          "Registro de Socio",
-    "cadastro_parceiro":       "Registro de Socio",
-    "dados_parceiro":          "Datos del Socio",
-    "tab_dados":               "Datos de la Empresa",
-    "tab_contato":             "Contacto",
-    "tab_endereco":            "Dirección",
-    "tab_horario":             "Horario y Contrato",
-    "tab_servicos":            "Mis Servicios"
+    "contract_start": "Inicio del Contrato",
+    "contract_end": "Fin del Contrato",
+    "contract_files": "Archivos del Contrato",
+    "add_file": "Agregar Archivo",
+    "services": "Servicios",
+    "service": "Servicio",
+    "service_category": "Categoría del Servicio",
+    "service_number": "Número del Servicio",
+    "price": "Precio",
+    "associate_price": "Precio Asociado",
+    "supplier_price": "Precio Proveedor",
+    "requires_scheduling": "Requiere Programación",
+    "search_placeholder": "Buscar por servicio",
+    "cadastro_title": "Registro de Socio",
+    "cadastro_parceiro": "Registro de Socio",
+    "dados_parceiro": "Datos del Socio",
+    "tab_dados": "Datos de la Empresa",
+    "tab_contato": "Contacto",
+    "tab_endereco": "Dirección",
+    "tab_horario": "Horario y Contrato",
+    "tab_servicos": "Mis Servicios"
+  },
+  "notification": {
+    "new": "Nueva notificación",
+    "history": "Historial",
+    "title_label": "Título",
+    "title_placeholder": "Título de la notificación",
+    "message_label": "Mensaje",
+    "message_placeholder": "Escribe el mensaje",
+    "recipients": "Destinatarios",
+    "all": "Todos",
+    "by_position": "Destinatarios Por Cargo",
+    "by_sector": "Destinatarios Por Sector",
+    "media": "Medios",
+    "send": "Enviar Notificación",
+    "sent_to": "Enviado a {count} contactos",
+    "seen_by": "Visto por {count} contactos",
+    "details": "Detalles",
+    "empty": "No se encontraron notificaciones",
+    "recipient_associado": "Asociado",
+    "recipient_parceiro": "Socio/Convenios"
   }
 }

+ 105 - 68
src/i18n/locales/pt.json

@@ -4,13 +4,27 @@
       "description": "Gerencie os produtos disponíveis na loja"
     },
     "associado": {
-      "profile": { "description": "Visualize e edite seus dados pessoais e dependentes" },
-      "carteirinha": { "description": "Sua carteira digital com QR Code de identificação" },
-      "convenios": { "description": "Confira os parceiros e convênios disponíveis para você" },
-      "loja": { "description": "Confira os itens disponíveis na loja" },
-      "interesses": { "description": "Itens que você marcou como interesse" },
-      "agendamentos": { "description": "Solicite e acompanhe seus agendamentos" },
-      "notificacoes": { "description": "Suas notificações e avisos" }
+      "profile": {
+        "description": "Visualize e edite seus dados pessoais e dependentes"
+      },
+      "carteirinha": {
+        "description": "Sua carteira digital com QR Code de identificação"
+      },
+      "convenios": {
+        "description": "Confira os parceiros e convênios disponíveis para você"
+      },
+      "loja": {
+        "description": "Confira os itens disponíveis na loja"
+      },
+      "interesses": {
+        "description": "Itens que você marcou como interesse"
+      },
+      "agendamentos": {
+        "description": "Solicite e acompanhe seus agendamentos"
+      },
+      "notificacoes": {
+        "description": "Suas notificações e avisos"
+      }
     },
     "gestao_associados": {
       "description": "Consulte, edite e adicione os associados do sistema"
@@ -35,6 +49,9 @@
     },
     "parceiros_convenios": {
       "description": "Consulte, edite e adicione os parceiros e convênios do sistema"
+    },
+    "notificacoes": {
+      "description": "Histórico"
     }
   },
   "common": {
@@ -114,7 +131,8 @@
       "all": "Todos",
       "certificate": "Certificado",
       "version": "Versão",
-      "whatsapp": "WhatsApp"
+      "whatsapp": "WhatsApp",
+      "today": "Hoje"
     },
     "months": {
       "january": "Janeiro",
@@ -415,7 +433,7 @@
     "dependent": "Dependente",
     "dependents": "Dependentes",
     "import_afastamentos": "Afastamentos",
-    "search_placeholder": "Buscar por Associados",
+    "search_placeholder": "Buscar por associados",
     "no_dependents": "Nenhum dependente cadastrado",
     "kinship": "Parentesco",
     "dependent_statuses": {
@@ -466,8 +484,7 @@
       "pt": "Português",
       "en": "English",
       "es": "Español"
-    },
-    "search_placeholder": "Buscar por associados"
+    }
   },
   "charts": {
     "nps": {
@@ -531,66 +548,86 @@
     }
   },
   "loja": {
-    "product":                    "Produto",
-    "products":                   "Produtos",
-    "supplier_price":             "Preço Fornecedor",
-    "supplier":                   "Fornecedor",
-    "stock":                       "Estoque",
-    "interests":                  "Interessados",
-    "interested_btn":             "INTERESSADOS",
-    "interested_in":              "Interessados em",
-    "media":                      "Mídias",
-    "add_media":                  "Adicionar Mídia",
-    "uploading_media":            "Enviando mídia...",
-    "activate":                   "Ativar",
-    "deactivate":                 "Desativar",
-    "variation":                  "Variação",
-    "variation_tamanho":          "Tamanho",
-    "variation_cor":              "Cor",
-    "variation_modelo":           "Modelo",
-    "variation_label":            "Nome",
-    "variation_value":            "Valor (R$)",
-    "variation_stock":            "Estoque",
-    "variation_cor_placeholder":  "Ex: Roxo, Branco...",
+    "product": "Produto",
+    "products": "Produtos",
+    "supplier_price": "Preço Fornecedor",
+    "supplier": "Fornecedor",
+    "stock": "Estoque",
+    "interests": "Interessados",
+    "interested_btn": "INTERESSADOS",
+    "interested_in": "Interessados em",
+    "media": "Mídias",
+    "add_media": "Adicionar Mídia",
+    "uploading_media": "Enviando mídia...",
+    "activate": "Ativar",
+    "deactivate": "Desativar",
+    "variation": "Variação",
+    "variation_tamanho": "Tamanho",
+    "variation_cor": "Cor",
+    "variation_modelo": "Modelo",
+    "variation_label": "Nome",
+    "variation_value": "Valor (R$)",
+    "variation_stock": "Estoque",
+    "variation_cor_placeholder": "Ex: Roxo, Branco...",
     "variation_modelo_placeholder": "Ex: Associado, Premium...",
-    "variation_empty":            "Nenhuma variação adicionada",
-    "add_variation":              "Adicionar",
-    "filter_all":                 "Todos",
-    "filter_recent":              "Recentes",
-    "filter_others":              "Outros"
+    "variation_empty": "Nenhuma variação adicionada",
+    "add_variation": "Adicionar",
+    "filter_all": "Todos",
+    "filter_recent": "Recentes",
+    "filter_others": "Outros"
   },
   "parceiro": {
-    "category":                "Categoria",
-    "company_name":            "Nome da Empresa",
-    "responsible":             "Responsável",
-    "discount_percentage":     "Desconto (%)",
-    "logo":                    "Logo",
-    "website":                 "Website",
-    "zip_code":                "CEP",
-    "address":                 "Endereço",
-    "neighborhood":            "Bairro",
-    "working_hours":           "Horário de Funcionamento",
+    "category": "Categoria",
+    "company_name": "Nome da Empresa",
+    "responsible": "Responsável",
+    "discount_percentage": "Desconto (%)",
+    "logo": "Logo",
+    "website": "Website",
+    "zip_code": "CEP",
+    "address": "Endereço",
+    "neighborhood": "Bairro",
+    "working_hours": "Horário de Funcionamento",
     "working_hours_placeholder": "Ex: Seg-Sex 08h-18h, Sáb 08h-12h",
-    "contract_start":          "Início do Contrato",
-    "contract_end":            "Fim do Contrato",
-    "contract_files":          "Arquivos do Contrato",
-    "add_file":                "Adicionar Arquivo",
-    "services":                "Serviços",
-    "service":                 "Serviço",
-    "service_category":        "Categoria do Serviço",
-    "service_number":          "Número do Serviço",
-    "price":                   "Preço",
-    "associate_price":         "Preço Associado",
-    "supplier_price":          "Preço Fornecedor",
-    "requires_scheduling":     "Requer Agendamento",
-    "search_placeholder":      "Pesquisar por serviço",
-    "cadastro_title":          "Cadastro de Parceiro",
-    "cadastro_parceiro":       "Cadastro de Parceiro",
-    "dados_parceiro":          "Dados de Parceiro",
-    "tab_dados":               "Dados da Empresa",
-    "tab_contato":             "Contato",
-    "tab_endereco":            "Endereço",
-    "tab_horario":             "Horário e Contrato",
-    "tab_servicos":            "Meus Serviços"
+    "contract_start": "Início do Contrato",
+    "contract_end": "Fim do Contrato",
+    "contract_files": "Arquivos do Contrato",
+    "add_file": "Adicionar Arquivo",
+    "services": "Serviços",
+    "service": "Serviço",
+    "service_category": "Categoria do Serviço",
+    "service_number": "Número do Serviço",
+    "price": "Preço",
+    "associate_price": "Preço Associado",
+    "supplier_price": "Preço Fornecedor",
+    "requires_scheduling": "Requer Agendamento",
+    "search_placeholder": "Pesquisar por serviço",
+    "cadastro_title": "Cadastro de Parceiro",
+    "cadastro_parceiro": "Cadastro de Parceiro",
+    "dados_parceiro": "Dados de Parceiro",
+    "tab_dados": "Dados da Empresa",
+    "tab_contato": "Contato",
+    "tab_endereco": "Endereço",
+    "tab_horario": "Horário e Contrato",
+    "tab_servicos": "Meus Serviços"
+  },
+  "notification": {
+    "new": "Nova notificação",
+    "history": "Histórico",
+    "title_label": "Título",
+    "title_placeholder": "Título da notificação",
+    "message_label": "Mensagem",
+    "message_placeholder": "Escreva a mensagem",
+    "recipients": "Destinatários",
+    "all": "Todos",
+    "by_position": "Destinatários Por Cargo",
+    "by_sector": "Destinatários Por Setor",
+    "media": "Mídias",
+    "send": "Enviar Notificação",
+    "sent_to": "Enviado para {count} contatos",
+    "seen_by": "Visto por {count} contatos",
+    "details": "Detalhes",
+    "empty": "Nenhuma notificação encontrada",
+    "recipient_associado": "Associado",
+    "recipient_parceiro": "Parceiro/Convênios"
   }
 }

+ 82 - 0
src/pages/associado/notificacoes/NotificacoesAssociadoPage.vue

@@ -0,0 +1,82 @@
+<template>
+  <div>
+    <DefaultHeaderPage />
+
+    <div v-if="loading" class="flex flex-center q-pa-xl">
+      <q-spinner color="violet-normal" size="50px" />
+    </div>
+
+    <div v-else-if="notifications.length === 0" class="flex flex-center q-pa-xl text-grey-6">
+      {{ $t('notification.empty') }}
+    </div>
+
+    <div v-else class="q-gutter-md">
+      <q-card
+        v-for="item in notifications"
+        :key="item.id"
+        flat
+        bordered
+        class="notificacoes-card"
+        @click="onRead(item)"
+      >
+        <q-card-section horizontal>
+          <q-card-section class="flex items-center q-pa-md">
+            <q-icon
+              name="mdi-bell-outline"
+              size="28px"
+              :color="item.read ? 'grey-5' : 'violet-normal'"
+            />
+          </q-card-section>
+
+          <q-card-section class="flex-grow q-pa-md">
+            <div class="text-weight-bold" :class="item.read ? 'text-grey-7' : 'text-violet-normal'">
+              {{ item.notification?.title }}
+            </div>
+            <div class="text-caption text-grey-7 q-mt-xs">
+              {{ item.notification?.message }}
+            </div>
+          </q-card-section>
+
+          <q-card-section class="flex items-center q-pa-md">
+            <q-badge v-if="!item.read" color="violet-normal" rounded />
+          </q-card-section>
+        </q-card-section>
+      </q-card>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import { getMyUnreadNotifications, markNotificationAsRead } from "src/api/notification";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+
+const loading = ref(true);
+const notifications = ref([]);
+
+const onRead = async (item) => {
+  if (item.read) return;
+  await markNotificationAsRead(item.id);
+  item.read = true;
+};
+
+onMounted(async () => {
+  try {
+    notifications.value = await getMyUnreadNotifications();
+  } finally {
+    loading.value = false;
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.notificacoes-card {
+  border-radius: 8px;
+  cursor: pointer;
+  transition: background-color 0.2s;
+
+  &:hover {
+    background: #f0e8f1;
+  }
+}
+</style>

+ 0 - 112
src/pages/associado/notificacoes/NotificacoesPage.vue

@@ -1,112 +0,0 @@
-<template>
-  <div>
-    <DefaultHeaderPage />
-
-    <div class="q-pa-md">
-      <div v-if="loading" class="flex flex-center q-py-xl">
-        <q-spinner color="primary" size="48px" />
-      </div>
-
-      <div v-else-if="notifications.length === 0" class="text-center text-grey q-py-xl">
-        <q-icon name="mdi-bell-off-outline" size="64px" class="q-mb-sm" />
-        <div>{{ $t("associado.no_notifications") }}</div>
-      </div>
-
-      <div v-else class="column q-gutter-sm">
-        <q-card
-          v-for="notif in notifications"
-          :key="notif.id"
-          flat
-          bordered
-          :class="notif.read ? 'notification-read' : 'notification-unread'"
-        >
-          <q-card-section class="row items-start q-py-sm">
-            <q-icon
-              :name="notif.read ? 'mdi-bell-outline' : 'mdi-bell-ring'"
-              :color="notif.read ? 'grey-5' : 'primary'"
-              size="24px"
-              class="q-mr-md q-mt-xs"
-            />
-            <div class="col">
-              <div class="text-subtitle2" :class="notif.read ? 'text-grey-7' : ''">
-                {{ notif.notification?.title }}
-              </div>
-              <div class="text-body2 text-grey-7 q-mt-xs">
-                {{ notif.notification?.body }}
-              </div>
-              <div class="text-caption text-grey-5 q-mt-xs">
-                {{ formatDate(notif.created_at) }}
-              </div>
-            </div>
-            <q-btn
-              v-if="!notif.read"
-              flat
-              round
-              icon="mdi-check"
-              color="primary"
-              size="sm"
-              :loading="markingId === notif.id"
-              @click="markAsRead(notif)"
-            >
-              <q-tooltip>{{ $t("associado.mark_as_read") }}</q-tooltip>
-            </q-btn>
-          </q-card-section>
-        </q-card>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from "vue";
-import { useQuasar } from "quasar";
-import { useI18n } from "vue-i18n";
-import { getMyUnreadNotifications, markNotificationAsRead } from "src/api/notification";
-
-import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
-
-const $q = useQuasar();
-const { t } = useI18n();
-
-const notifications = ref([]);
-const loading = ref(true);
-const markingId = ref(null);
-
-const formatDate = (dateStr) => {
-  if (!dateStr) return "";
-  const d = new Date(dateStr);
-  return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
-};
-
-const markAsRead = async (notif) => {
-  markingId.value = notif.id;
-  try {
-    await markNotificationAsRead(notif.id);
-    notif.read = true;
-    $q.notify({ type: "positive", message: t("http.success") });
-  } catch {
-    $q.notify({ type: "negative", message: t("http.errors.failed") });
-  } finally {
-    markingId.value = null;
-  }
-};
-
-onMounted(async () => {
-  try {
-    notifications.value = await getMyUnreadNotifications();
-  } catch (e) {
-    console.error(e);
-  } finally {
-    loading.value = false;
-  }
-});
-</script>
-
-<style lang="scss" scoped>
-.notification-unread {
-  border-left: 3px solid $primary;
-}
-.notification-read {
-  opacity: 0.75;
-}
-</style>

+ 60 - 0
src/pages/notificacoes/NotificationsAdminPage.vue

@@ -0,0 +1,60 @@
+<template>
+  <div class="notifications-page">
+    <DefaultHeaderPage />
+
+    <div class="flex q-gutter-xs q-mb-md">
+      <q-chip
+        v-for="tab in tabs"
+        :key="tab.name"
+        clickable
+        :color="activeTab === tab.name ? 'violet-normal' : 'violet-light'"
+        :text-color="activeTab === tab.name ? 'white' : 'violet-normal'"
+        class="notifications-page__tab-chip"
+        @click="activeTab = tab.name"
+      >
+        {{ tab.label }}
+      </q-chip>
+    </div>
+
+    <q-tab-panels v-model="activeTab" animated>
+      <q-tab-panel name="nova" class="q-pa-none">
+        <NewNotificationPanel @sent="onNotificationSent" />
+      </q-tab-panel>
+
+      <q-tab-panel name="historico" class="q-pa-none">
+        <NotificationHistoryPanel ref="historyPanelRef" />
+      </q-tab-panel>
+    </q-tab-panels>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from "vue";
+import { useI18n } from "vue-i18n";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+import NewNotificationPanel from "./components/NewNotificationPanel.vue";
+import NotificationHistoryPanel from "./components/NotificationHistoryPanel.vue";
+
+const { t } = useI18n();
+
+const activeTab = ref("historico");
+const historyPanelRef = ref(null);
+
+const tabs = computed(() => [
+  { name: "nova", label: t("notification.new") },
+  { name: "historico", label: t("notification.history") },
+]);
+
+const onNotificationSent = async () => {
+  activeTab.value = "historico";
+  await historyPanelRef.value?.refresh();
+};
+</script>
+
+<style scoped lang="scss">
+.notifications-page__tab-chip {
+  font-size: 13px;
+  border-radius: 20px;
+  cursor: pointer;
+}
+</style>

+ 259 - 0
src/pages/notificacoes/components/NewNotificationPanel.vue

@@ -0,0 +1,259 @@
+<template>
+  <q-card flat bordered class="new-notification-panel">
+    <q-card-section>
+      <div class="flex items-center gap-sm q-mb-md">
+        <q-icon name="mdi-send-outline" size="20px" color="violet-normal" />
+        <span class="text-body1 text-violet-normal text-weight-medium q-pl-sm">
+          {{ $t('notification.new') }}
+        </span>
+      </div>
+
+      <q-form ref="formRef" greedy>
+        <DefaultInput
+          v-model="form.title"
+          :label="$t('notification.title_label')"
+          :placeholder="$t('notification.title_placeholder')"
+          :rules="[inputRules.required]"
+          bg-color="violet-light"
+          outlined
+        />
+
+
+        <DefaultInput
+          v-model="form.message"
+          :label="$t('notification.message_label')"
+          :placeholder="$t('notification.message_placeholder')"
+          :rules="[inputRules.required]"
+          bg-color="violet-light"
+          outlined
+          class="q-pt-md"
+        />
+
+        <div class="row q-col-gutter-md q-mt-md">
+          <div class="col-7">
+            <div class="q-mb-md">
+              <div class="text-caption text-grey-7 q-mb-sm">
+                {{ $t('notification.recipients') }}
+              </div>
+              <div class="row q-gutter-sm">
+                <q-chip
+                  v-for="opt in recipientOptions"
+                  :key="opt.value"
+                  clickable
+                  :color="form.recipient === opt.value ? 'violet-normal' : 'violet-light'"
+                  :text-color="form.recipient === opt.value ? 'white' : 'violet-normal'"
+                  :icon="opt.icon"
+                  class="notification-chip"
+                  @click="form.recipient = opt.value"
+                >
+                  {{ opt.label }}
+                </q-chip>
+              </div>
+            </div>
+
+            <div class="q-mb-md">
+              <div class="text-caption text-grey-7 q-mb-sm">
+                {{ $t('notification.by_position') }}
+              </div>
+              <div v-if="loadingPositions" class="flex items-center">
+                <q-spinner color="violet-normal" size="18px" />
+              </div>
+              <div v-else class="row q-gutter-sm">
+                <q-chip
+                  v-for="pos in positions"
+                  :key="pos.id"
+                  clickable
+                  :color="form.recipient_position_id === pos.id ? 'violet-normal' : 'violet-light'"
+                  :text-color="form.recipient_position_id === pos.id ? 'white' : 'violet-normal'"
+                  class="notification-chip"
+                  @click="togglePosition(pos.id)"
+                >
+                  {{ pos.name }}
+                </q-chip>
+                <q-chip
+                  clickable
+                  color="negative"
+                  text-color="white"
+                  icon="mdi-close"
+                  class="notification-chip"
+                  @click="form.recipient_position_id = null"
+                />
+              </div>
+            </div>
+
+            <div>
+              <div class="text-caption text-grey-7 q-mb-sm">
+                {{ $t('notification.by_sector') }}
+              </div>
+              <div v-if="loadingSectors" class="flex items-center">
+                <q-spinner color="violet-normal" size="18px" />
+              </div>
+              <div v-else class="row q-gutter-sm">
+                <q-chip
+                  v-for="sec in sectors"
+                  :key="sec.id"
+                  clickable
+                  :color="form.recipient_sector_id === sec.id ? 'violet-normal' : 'violet-light'"
+                  :text-color="form.recipient_sector_id === sec.id ? 'white' : 'violet-normal'"
+                  class="notification-chip"
+                  @click="toggleSector(sec.id)"
+                >
+                  {{ sec.name }}
+                </q-chip>
+                <q-chip
+                  clickable
+                  color="negative"
+                  text-color="white"
+                  icon="mdi-close"
+                  class="notification-chip"
+                  @click="form.recipient_sector_id = null"
+                />
+              </div>
+            </div>
+          </div>
+
+          <div class="col-5 flex column items-end">
+            <div class="text-caption text-grey-7 q-mb-sm">
+              {{ $t('notification.media') }}
+            </div>
+            <DefaultFilePicker
+              v-model="form.image"
+              type="image"
+              class="new-notification-panel__file-picker"
+            />
+          </div>
+        </div>
+
+        <q-btn
+          unelevated
+          class="btn-gradient full-width q-mt-md"
+          :label="$t('notification.send').toUpperCase()"
+          icon="mdi-send-outline"
+          padding="12px"
+          :loading="submitting"
+          @click="onSubmit"
+        />
+      </q-form>
+    </q-card-section>
+  </q-card>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import { useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { permissionStore } from "src/stores/permission";
+import { sendNotification } from "src/api/notification";
+import { getPositions } from "src/api/position";
+import { getSectors } from "src/api/sector";
+import { useInputRules } from "src/composables/useInputRules";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+import DefaultFilePicker from "src/components/defaults/DefaultFilePicker.vue";
+
+const emit = defineEmits(["sent"]);
+
+const $q = useQuasar();
+const { t } = useI18n();
+const permission_store = permissionStore();
+const inputRules = useInputRules();
+
+const formRef = ref(null);
+const submitting = ref(false);
+
+const form = ref({
+  title: "",
+  message: "",
+  recipient: "todos",
+  recipient_position_id: null,
+  recipient_sector_id: null,
+  image: null,
+});
+
+const recipientOptions = computed(() => [
+  { value: "todos", label: t("notification.all"), icon: "mdi-earth" },
+  { value: "associado", label: t("notification.recipient_associado"), icon: "mdi-account-group-outline" },
+  { value: "parceiro", label: t("notification.recipient_parceiro"), icon: "mdi-handshake-outline" },
+]);
+
+const togglePosition = (id) => {
+  form.value.recipient_position_id = form.value.recipient_position_id === id ? null : id;
+};
+
+const toggleSector = (id) => {
+  form.value.recipient_sector_id = form.value.recipient_sector_id === id ? null : id;
+};
+
+const resetForm = () => {
+  form.value = {
+    title: "",
+    message: "",
+    recipient: "todos",
+    recipient_position_id: null,
+    recipient_sector_id: null,
+    image: null,
+  };
+  formRef.value?.resetValidation();
+};
+
+const onSubmit = async () => {
+  if (!permission_store.getAccess("notificacao", "add")) {
+    $q.notify({ type: "negative", message: t("validation.permissions.add") });
+    return;
+  }
+
+  const valid = await formRef.value?.validate();
+  if (!valid) return;
+
+  submitting.value = true;
+  try {
+    await sendNotification(form.value);
+    $q.notify({ type: "positive", message: t("common.actions.save") + "!" });
+    resetForm();
+    emit("sent");
+  } finally {
+    submitting.value = false;
+  }
+};
+
+const positions = ref([]);
+const sectors = ref([]);
+const loadingPositions = ref(true);
+const loadingSectors = ref(true);
+
+onMounted(async () => {
+  await Promise.all([
+    getPositions()
+      .then((r) => { positions.value = r; })
+      .finally(() => { loadingPositions.value = false; }),
+    getSectors()
+      .then((r) => { sectors.value = r; })
+      .finally(() => { loadingSectors.value = false; }),
+  ]);
+});
+</script>
+
+<style scoped lang="scss">
+.new-notification-panel {
+  border-radius: 8px;
+
+  &__file-picker {
+    width: 100%;
+    height: 180px;
+  }
+}
+
+.notification-chip {
+  border-radius: 20px;
+  font-size: 13px;
+  cursor: pointer;
+  transition: background-color 0.2s;
+}
+
+.btn-gradient {
+  background: linear-gradient(90deg, #4d1658 0%, #8b30a5 100%) !important;
+  color: white !important;
+  border-radius: 8px !important;
+}
+
+.btn-gradient :deep(.q-icon) { color: white !important; }
+</style>

+ 140 - 0
src/pages/notificacoes/components/NotificationCard.vue

@@ -0,0 +1,140 @@
+<template>
+  <q-card flat bordered class="notification-card">
+    <div class="notification-card__image">
+      <img
+        v-if="imageUrl"
+        :src="imageUrl"
+        alt=""
+        class="notification-card__img"
+      />
+      <div v-if="!imageUrl" class="notification-card__img-placeholder flex flex-center">
+        <q-icon name="mdi-bell-outline" size="40px" color="violet-normal" />
+      </div>
+    </div>
+
+    <q-card-section class="notification-card__body q-pt-sm q-pb-xs">
+      <div class="notification-card__title text-violet-normal text-weight-bold ellipsis">
+        {{ notification.title }}
+      </div>
+      <div class="notification-card__message text-caption text-grey-7 q-mt-xs">
+        {{ notification.message }}
+      </div>
+    </q-card-section>
+
+    <q-card-actions class="notification-card__actions q-pt-xs q-pb-sm q-px-md flex justify-between items-center">
+      <div class="text-caption text-grey-6">
+        {{ formatDate(notification.created_at) }}
+      </div>
+
+      <q-btn
+        unelevated
+        dense
+        color="violet-normal"
+        text-color="white"
+        :label="$t('notification.details')"
+        size="sm"
+        padding="4px 10px"
+        class="notification-card__details-btn"
+      >
+        <q-menu anchor="bottom right" self="top right" class="notification-card__menu">
+          <q-list dense style="min-width: 200px">
+            <q-item>
+              <q-item-section avatar>
+                <q-icon name="mdi-send-outline" color="violet-normal" size="18px" />
+              </q-item-section>
+              <q-item-section>
+                <q-item-label class="text-body2">
+                  {{ $t('notification.sent_to', { count: notification.sent_count ?? 0 }) }}
+                </q-item-label>
+              </q-item-section>
+            </q-item>
+            <q-item>
+              <q-item-section avatar>
+                <q-icon name="mdi-eye-outline" color="violet-normal" size="18px" />
+              </q-item-section>
+              <q-item-section>
+                <q-item-label class="text-body2">
+                  {{ $t('notification.seen_by', { count: notification.seen_count ?? 0 }) }}
+                </q-item-label>
+              </q-item-section>
+            </q-item>
+          </q-list>
+        </q-menu>
+      </q-btn>
+    </q-card-actions>
+  </q-card>
+</template>
+
+<script setup>
+import { computed } from "vue";
+import { useI18n } from "vue-i18n";
+
+const { t } = useI18n();
+
+const { notification } = defineProps({
+  notification: {
+    type: Object,
+    required: true,
+  },
+});
+
+const imageUrl = computed(() => {
+  if (!notification.image_url) return null;
+  if (notification.image_url.startsWith("http")) return notification.image_url;
+  return process.env.API_URL + notification.image_url;
+});
+
+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" });
+};
+</script>
+
+<style scoped lang="scss">
+.notification-card {
+  border-radius: 8px;
+  overflow: hidden;
+
+  &__image {
+    width: 100%;
+    height: 120px;
+    overflow: hidden;
+  }
+
+  &__img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+
+  &__img-placeholder {
+    width: 100%;
+    height: 100%;
+    background: #f0e8f1;
+  }
+
+  &__title {
+    font-size: 14px;
+    line-height: 1.3;
+  }
+
+  &__message {
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    line-height: 1.4;
+  }
+
+  &__menu {
+    border-radius: 8px;
+  }
+}
+</style>

+ 71 - 0
src/pages/notificacoes/components/NotificationHistoryPanel.vue

@@ -0,0 +1,71 @@
+<template>
+  <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="notifications.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 notifications"
+          :key="item.id"
+          class="col-xl-4 col-lg-4 col-md-4 col-sm-6 col-12"
+        >
+          <NotificationCard :notification="item" />
+        </div>
+      </div>
+
+      <div class="flex flex-center q-mt-lg">
+        <q-pagination
+          v-model="page"
+          :max="totalPages"
+          :max-pages="6"
+          boundary-numbers
+          color="violet-normal"
+          active-color="violet-normal"
+          direction-links
+          @update:model-value="fetchData"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from "vue";
+import { getNotificationsPaginated } from "src/api/notification";
+import NotificationCard from "./NotificationCard.vue";
+
+const PER_PAGE = 12;
+
+const loading = ref(true);
+const notifications = ref([]);
+const page = ref(1);
+const total = ref(0);
+
+const totalPages = computed(() => Math.max(1, Math.ceil(total.value / PER_PAGE)));
+
+const fetchData = async () => {
+  loading.value = true;
+  try {
+    const result = await getNotificationsPaginated({ page: page.value, perPage: PER_PAGE });
+    notifications.value = result.data;
+    total.value = result.total;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const refresh = async () => {
+  page.value = 1;
+  await fetchData();
+};
+
+defineExpose({ refresh });
+
+onMounted(fetchData);
+</script>

+ 3 - 3
src/router/routes/associado.route.js

@@ -67,13 +67,13 @@ export default [
   },
   {
     path: "/associado/notificacoes",
-    name: "NotificacoesPage",
-    component: () => import("pages/associado/notificacoes/NotificacoesPage.vue"),
+    name: "NotificacoesAssociadoPage",
+    component: () => import("pages/associado/notificacoes/NotificacoesAssociadoPage.vue"),
     meta: {
       title: { value: "ui.navigation.notifications", translate: true },
       description: { value: "page.associado.notificacoes.description", translate: true },
       requireAuth: true,
-      breadcrumbs: [{ name: "NotificacoesPage", title: "ui.navigation.notifications", translate: true }],
+      breadcrumbs: [{ name: "NotificacoesAssociadoPage", title: "ui.navigation.notifications", translate: true }],
     },
   },
 ];

+ 14 - 0
src/router/routes/notificacao-admin.route.js

@@ -0,0 +1,14 @@
+export default [
+  {
+    path: "/notificacoes",
+    name: "NotificationsAdminPage",
+    component: () => import("pages/notificacoes/NotificationsAdminPage.vue"),
+    meta: {
+      title: { value: "ui.navigation.notifications", translate: true },
+      description: { value: "page.notificacoes.description", translate: true },
+      requireAuth: true,
+      requiredPermission: "notificacao",
+      breadcrumbs: [{ name: "NotificationsAdminPage", title: "ui.navigation.notifications", translate: true }],
+    },
+  },
+];

+ 6 - 1
src/stores/navigation.js

@@ -16,6 +16,7 @@ export const navigationStore = defineStore("navigation", () => {
   });
 
   const navigationStructure = Object.freeze([
+    //ADMINISTRADOR
     {
       type: "single",
       title: "ui.navigation.dashboard",
@@ -64,7 +65,7 @@ export const navigationStore = defineStore("navigation", () => {
     {
       type: "single",
       title: "ui.navigation.notifications",
-      name: "NotificationsPage",
+      name: "NotificationsAdminPage",
       icon: "mdi-bell-outline",
       permission: false,
       permissionScope: "notificacao",
@@ -79,6 +80,8 @@ export const navigationStore = defineStore("navigation", () => {
       permissionScope: "relatorio",
       allowedTypes: ["administrador"],
     },
+
+    //ASSOCIADO
     {
       type: "single",
       title: "ui.navigation.meu_perfil",
@@ -142,6 +145,8 @@ export const navigationStore = defineStore("navigation", () => {
       permissionScope: "notificacao",
       allowedTypes: ["associado"],
     },
+
+    //PARCEIRO
     {
       type: "single",
       title: "ui.navigation.dashboard",