Explorar el Código

implementacao imagens na s3+ ajustes finais

Gustavo Zanatta hace 3 semanas
padre
commit
d8ca9d5e7a

+ 0 - 24
src/api/storeItem.js

@@ -1,29 +1,5 @@
 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;

+ 14 - 0
src/api/user.js

@@ -30,6 +30,20 @@ export const userTypes = async () => {
   return data.payload;
 };
 
+export const uploadMyAvatar = async (file) => {
+  const formData = new FormData();
+  formData.append("photo", file);
+  const { data } = await api.post("/user/my/avatar", formData, {
+    headers: { "Content-Type": "multipart/form-data" },
+  });
+  return data.payload;
+};
+
+export const deleteMyAvatar = async () => {
+  const { data } = await api.delete("/user/my/avatar");
+  return data.payload;
+};
+
 export const getAssociados = async () => {
   const users = await getUsers();
   return users.filter((u) => u.type === "associado");

+ 1 - 1
src/components/layout/LeftMenuLayoutMobile.vue

@@ -101,7 +101,7 @@ import { useRoute, useRouter } from "vue-router";
 import { navigationStore } from "src/stores/navigation";
 import { version } from "src/../package.json";
 
-import Logo from "src/assets/logo.png";
+import Logo from "src/assets/logo_serprati.svg";
 import LogoSoftparLight from "src/assets/softpar_logo_light.svg";
 import LogoSoftparDark from "src/assets/softpar_logo_dark.svg";
 

+ 137 - 161
src/i18n/locales/en.json

@@ -4,27 +4,13 @@
       "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"
@@ -49,15 +35,6 @@
     },
     "parceiros_convenios": {
       "description": "View, edit and add partners and agreements to the system"
-    },
-    "notificacoes": {
-      "description": "History"
-    },
-    "relatorios": {
-      "description": "View management reports"
-    },
-    "agendamentos": {
-      "description": "Manage associate appointments"
     }
   },
   "common": {
@@ -132,14 +109,14 @@
       "day": "Day",
       "hour": "Hour",
       "hour2": "Time",
+      "today": "Today",
       "minute": "Minute",
       "second": "Second",
       "year": "Year",
       "all": "All",
       "certificate": "Certificate",
       "version": "Version",
-      "whatsapp": "WhatsApp",
-      "today": "Today"
+      "whatsapp": "WhatsApp"
     },
     "months": {
       "january": "January",
@@ -431,6 +408,9 @@
   },
   "associado": {
     "associado": "Associate",
+    "cpf": "CPF",
+    "phone": "Phone",
+    "email": "Email",
     "personal_data": "Personal Data",
     "registration": "Registration",
     "cracha": "Badge",
@@ -443,6 +423,7 @@
     "import_afastamentos": "Absences",
     "search_placeholder": "Search Members",
     "no_dependents": "No dependents registered",
+    "remove_photo": "Do you want to remove your profile photo?",
     "kinship": "Kinship",
     "dependent_statuses": {
       "approved": "Approved",
@@ -493,9 +474,7 @@
       "en": "English",
       "es": "Español"
     },
-    "cpf": "CPF",
-    "phone": "Phone",
-    "email": "E-mail"
+    "search_placeholder": "Search Members"
   },
   "charts": {
     "nps": {
@@ -559,144 +538,141 @@
     }
   },
   "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",
-    "btn_want": "I WANT IT",
-    "btn_interested": "INTERESTED"
+    "variation_empty":            "No variation added",
+    "add_variation":              "Add",
+    "filter_all":                 "All",
+    "filter_recent":              "Recent",
+    "filter_others":              "Others",
+    "btn_interested":             "I'm interested",
+    "btn_want":                   "I want"
   },
   "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",
-    "usuario_parceiro": "Partner User",
-    "home": "Partner Portal",
-    "parceiro": {
-      "dashboard": {
-        "title": "Partner Portal",
-        "authorization": "Consultations for Authorization",
-        "scheduling": "Consultations for Scheduling",
-        "completed": "Completed Consultations",
-        "not_authorized": "Unauthorized Consultations"
-      }
+    "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",
+    "usuario_parceiro":        "Partner User",
+    "linked_user":             "Linked User",
+    "home":                    "Home",
+    "dashboard": {
+      "title":         "Dashboard",
+      "authorization": "Pending Authorizations",
+      "scheduling":    "Appointments",
+      "completed":     "Completed",
+      "not_authorized":"Not Authorized"
     }
   },
-  "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"
-  },
-  "relatorio": {
-    "novos_associados": "New Members",
-    "contatos": "Contacts",
-    "exclusoes_mes": "Monthly Exclusions",
-    "exportar_csv": "Export Excel",
-    "col": {
-      "cadastro": "Registration Date",
-      "exclusao": "Exclusion Date"
-    },
-    "exportar_excel": "Export Excel"
-  },
   "agendamento": {
-    "nova_solicitacao": "New Request",
-    "visao_geral": "Overview",
-    "aprovados_automaticamente": "Auto Approved",
-    "associado": "Associate",
-    "solicitar": "Request",
-    "confirm_approve": "Are you sure you want to approve this appointment?",
-    "confirm_reject": "Are you sure you want to reject this appointment?",
+    "associado":                  "Associate",
+    "solicitar":                  "Request",
+    "nova_solicitacao":           "New Request",
+    "visao_geral":                "Overview",
+    "aprovados_automaticamente":  "Auto Approved",
+    "confirm_approve":            "Confirm approval of this appointment?",
+    "confirm_reject":             "Confirm rejection of this appointment?",
     "status": {
-      "pendente": "Waiting",
-      "confirmado": "Approved",
-      "recusado": "Rejected",
-      "cancelado": "Cancelled",
-      "concluido": "Completed"
+      "pendente":   "Pending",
+      "confirmado": "Confirmed",
+      "recusado":   "Rejected",
+      "cancelado":  "Cancelled",
+      "concluido":  "Completed"
     },
     "col": {
-      "pedido": "Order",
-      "parceiro": "Partner",
-      "servico": "Service",
-      "solicitacao": "Request Date"
+      "pedido":     "Order",
+      "parceiro":   "Partner",
+      "servico":    "Service",
+      "solicitacao":"Request"
     }
   },
+  "notification": {
+    "empty":                "No notifications at the moment",
+    "new":                  "New",
+    "history":              "History",
+    "title_label":          "Title",
+    "title_placeholder":    "Notification title",
+    "message_label":        "Message",
+    "message_placeholder":  "Notification message",
+    "recipients":           "Recipients",
+    "by_position":          "By Position",
+    "by_sector":            "By Sector",
+    "media":                "Media",
+    "send":                 "Send",
+    "all":                  "All",
+    "recipient_associado":  "Associates",
+    "recipient_parceiro":   "Partners",
+    "details":              "Details",
+    "sent_to":              "Sent to {count} user(s)",
+    "seen_by":              "Seen by {count} user(s)"
+  },
   "associate_validation": {
-    "title": "Validate Membership Card",
-    "description": "Enter associate information to verify status.",
-    "search_placeholder": "EX: Name, CPF, Registration",
-    "search": "Search",
-    "associate_found": "Associate Found",
-    "name": "Name",
-    "cpf": "CPF",
-    "registration": "Registration",
-    "qr_code": "Read QR CODE",
-    "not_found": "Associate not found"
+    "title":               "Validate Card",
+    "qr_code":             "QR Code",
+    "description":         "Search the associate by badge or CPF",
+    "search_placeholder":  "Badge or CPF",
+    "search":              "Search",
+    "associate_found":     "Associate Found",
+    "name":                "Name",
+    "cpf":                 "CPF",
+    "registration":        "Registration"
+  },
+  "relatorio": {
+    "exportar_excel":    "Export Excel",
+    "novos_associados":  "New Associates",
+    "contatos":          "Contacts",
+    "exclusoes_mes":     "Monthly Exclusions",
+    "col": {
+      "cadastro": "Registration",
+      "exclusao": "Exclusion"
+    }
   }
 }

+ 138 - 163
src/i18n/locales/es.json

@@ -4,27 +4,13 @@
       "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"
@@ -49,15 +35,6 @@
     },
     "parceiros_convenios": {
       "description": "Consulte, edite y agregue los socios y convenios del sistema"
-    },
-    "notificacoes": {
-      "description": "Historial"
-    },
-    "relatorios": {
-      "description": "Visualice reportes gerenciales del sistema"
-    },
-    "agendamentos": {
-      "description": "Gestione las citas de los asociados"
     }
   },
   "common": {
@@ -131,15 +108,15 @@
       "week": "Semana",
       "day": "Día",
       "hour": "Hora",
-      "hour2": "Horário",
+      "hour2": "Horario",
+      "today": "Hoy",
       "minute": "Minuto",
       "second": "Segundo",
       "year": "Año",
       "all": "Todos",
       "certificate": "Certificado",
       "version": "Versión",
-      "whatsapp": "WhatsApp",
-      "today": "Hoy"
+      "whatsapp": "WhatsApp"
     },
     "months": {
       "january": "Enero",
@@ -431,6 +408,9 @@
   },
   "associado": {
     "associado": "Asociado",
+    "cpf": "CPF",
+    "phone": "Teléfono",
+    "email": "Correo electrónico",
     "personal_data": "Datos Personales",
     "registration": "Matrícula",
     "cracha": "Identificador",
@@ -441,8 +421,9 @@
     "dependent": "Dependiente",
     "dependents": "Dependientes",
     "import_afastamentos": "Ausencias",
-    "search_placeholder": "Buscar por asociados",
+    "search_placeholder": "Buscar Asociados",
     "no_dependents": "Ningún dependiente registrado",
+    "remove_photo": "¿Deseas eliminar tu foto de perfil?",
     "kinship": "Parentesco",
     "dependent_statuses": {
       "approved": "Aprobado",
@@ -493,9 +474,7 @@
       "en": "English",
       "es": "Español"
     },
-    "cpf": "CPF",
-    "phone": "Teléfono",
-    "email": "E-mail"
+    "search_placeholder": "Buscar por asociados"
   },
   "charts": {
     "nps": {
@@ -559,144 +538,140 @@
     }
   },
   "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",
-    "btn_want": "LO QUIERO",
-    "btn_interested": "INTERESADO"
+    "variation_empty":            "Sin variaciones agregadas",
+    "add_variation":              "Agregar",
+    "filter_all":                 "Todos",
+    "filter_recent":              "Recientes",
+    "filter_others":              "Otros",
+    "btn_interested":             "Ya me interesa",
+    "btn_want":                   "Lo quiero"
   },
   "parceiro": {
-    "category": "Categoría",
-    "company_name": "Nombre de la Empresa",
-    "responsible": "Responsable",
-    "linked_user": "Usuario Vinculado",
-    "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",
-    "usuario_parceiro": "Usuario Socio",
-    "home": "Portal del Socio",
-    "parceiro": {
-      "dashboard": {
-        " title": "Portal del Socio",
-        "authorization": "Consultas para Autorización",
-        "scheduling": "Consultas para Agendamiento",
-        "completed": "Consultas Realizadas",
-        "not_authorized": "Consultas No Autorizadas"
-      }
+    "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",
+    "usuario_parceiro":        "Usuario Socio",
+    "linked_user":             "Usuario Vinculado",
+    "home":                    "Inicio",
+    "dashboard": {
+      "title":         "Dashboard",
+      "authorization": "Autorizaciones Pendientes",
+      "scheduling":    "Citas",
+      "completed":     "Completados",
+      "not_authorized":"No Autorizados"
     }
   },
-  "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"
-  },
-  "relatorio": {
-    "novos_associados": "Nuevos Asociados",
-    "contatos": "Contactos",
-    "exclusoes_mes": "Exclusiones del mes",
-    "exportar_csv": "Exportar Excel",
-    "col": {
-      "cadastro": "Registro",
-      "exclusao": "Exclusión"
-    },
-    "exportar_excel": "Exportar Excel"
-  },
   "agendamento": {
-    "nova_solicitacao": "Nueva Solicitud",
-    "visao_geral": "Visión General",
-    "aprovados_automaticamente": "Aprobados Automáticamente",
-    "associado": "Asociado",
-    "solicitar": "Solicitar",
-    "confirm_approve": "¿Estás seguro de que deseas aprobar esta cita?",
-    "confirm_reject": "¿Estás seguro de que deseas rechazar esta cita?",
+    "associado":                  "Asociado",
+    "solicitar":                  "Solicitar",
+    "nova_solicitacao":           "Nueva Solicitud",
+    "visao_geral":                "Vista General",
+    "aprovados_automaticamente":  "Aprobados Automáticamente",
+    "confirm_approve":            "¿Confirmar aprobación de esta cita?",
+    "confirm_reject":             "¿Confirmar rechazo de esta cita?",
     "status": {
-      "pendente": "Esperando",
-      "confirmado": "Aprobado",
-      "recusado": "Rechazado",
-      "cancelado": "Cancelado",
-      "concluido": "Completado"
+      "pendente":   "Pendiente",
+      "confirmado": "Confirmado",
+      "recusado":   "Rechazado",
+      "cancelado":  "Cancelado",
+      "concluido":  "Completado"
     },
     "col": {
-      "pedido": "Pedido",
-      "parceiro": "Socio",
-      "servico": "Servicio",
-      "solicitacao": "Solicitud"
+      "pedido":     "Pedido",
+      "parceiro":   "Socio",
+      "servico":    "Servicio",
+      "solicitacao":"Solicitud"
     }
   },
+  "notification": {
+    "empty":                "Sin notificaciones en este momento",
+    "new":                  "Nueva",
+    "history":              "Historial",
+    "title_label":          "Título",
+    "title_placeholder":    "Título de la notificación",
+    "message_label":        "Mensaje",
+    "message_placeholder":  "Mensaje de la notificación",
+    "recipients":           "Destinatarios",
+    "by_position":          "Por Cargo",
+    "by_sector":            "Por Sector",
+    "media":                "Multimedia",
+    "send":                 "Enviar",
+    "all":                  "Todos",
+    "recipient_associado":  "Asociados",
+    "recipient_parceiro":   "Socios",
+    "details":              "Detalles",
+    "sent_to":              "Enviado a {count} usuario(s)",
+    "seen_by":              "Visto por {count} usuario(s)"
+  },
   "associate_validation": {
-    "title": "Validar Credencial",
-    "description": "Ingrese la información del asociado para verificar el estado.",
-    "search_placeholder": "EJ: Nombre, CPF, Matrícula",
-    "search": "Buscar",
-    "associate_found": "Asociado Encontrado",
-    "name": "Nombre",
-    "cpf": "CPF",
-    "registration": "Matrícula",
-    "qr_code": "Leer QR CODE",
-    "not_found": "Asociado no encontrado"
+    "title":               "Validar Tarjeta",
+    "qr_code":             "Código QR",
+    "description":         "Busque el asociado por credencial o CPF",
+    "search_placeholder":  "Credencial o CPF",
+    "search":              "Buscar",
+    "associate_found":     "Asociado Encontrado",
+    "name":                "Nombre",
+    "cpf":                 "CPF",
+    "registration":        "Matrícula"
+  },
+  "relatorio": {
+    "exportar_excel":    "Exportar Excel",
+    "novos_associados":  "Nuevos Asociados",
+    "contatos":          "Contactos",
+    "exclusoes_mes":     "Exclusiones del Mes",
+    "col": {
+      "cadastro": "Registro",
+      "exclusao": "Exclusión"
+    }
   }
 }

+ 137 - 160
src/i18n/locales/pt.json

@@ -4,27 +4,13 @@
       "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"
@@ -49,15 +35,6 @@
     },
     "parceiros_convenios": {
       "description": "Consulte, edite e adicione os parceiros e convênios do sistema"
-    },
-    "notificacoes": {
-      "description": "Histórico"
-    },
-    "relatorios": {
-      "description": "Visualize relatórios gerenciais do sistema"
-    },
-    "agendamentos": {
-      "description": "Gerencie os agendamentos dos associados"
     }
   },
   "common": {
@@ -132,14 +109,14 @@
       "day": "Dia",
       "hour": "Hora",
       "hour2": "Horário",
+      "today": "Hoje",
       "minute": "Minuto",
       "second": "Segundo",
       "year": "Ano",
       "all": "Todos",
       "certificate": "Certificado",
       "version": "Versão",
-      "whatsapp": "WhatsApp",
-      "today": "Hoje"
+      "whatsapp": "WhatsApp"
     },
     "months": {
       "january": "Janeiro",
@@ -431,6 +408,9 @@
   },
   "associado": {
     "associado": "Associado",
+    "cpf": "CPF",
+    "phone": "Telefone",
+    "email": "E-mail",
     "personal_data": "Dados Pessoais",
     "registration": "Matrícula",
     "cracha": "Crachá",
@@ -441,8 +421,9 @@
     "dependent": "Dependente",
     "dependents": "Dependentes",
     "import_afastamentos": "Afastamentos",
-    "search_placeholder": "Buscar por associados",
+    "search_placeholder": "Buscar por Associados",
     "no_dependents": "Nenhum dependente cadastrado",
+    "remove_photo": "Deseja remover sua foto de perfil?",
     "kinship": "Parentesco",
     "dependent_statuses": {
       "approved": "Aprovado",
@@ -493,9 +474,7 @@
       "en": "English",
       "es": "Español"
     },
-    "cpf": "CPF",
-    "phone": "Telefone",
-    "email": "E-mail"
+    "search_placeholder": "Buscar por associados"
   },
   "charts": {
     "nps": {
@@ -559,143 +538,141 @@
     }
   },
   "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",
-    "btn_want": "EU QUERO",
-    "btn_interested": "INTERESSADO"
+    "variation_empty":            "Nenhuma variação adicionada",
+    "add_variation":              "Adicionar",
+    "filter_all":                 "Todos",
+    "filter_recent":              "Recentes",
+    "filter_others":              "Outros",
+    "btn_interested":             "Já tenho interesse",
+    "btn_want":                   "Eu quero"
   },
   "parceiro": {
-    "category": "Categoria",
-    "company_name": "Nome da Empresa",
-    "responsible": "Responsável",
-    "linked_user": "Usuário Vinculado",
-    "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",
-    "usuario_parceiro": "Usuário Parceiro",
-    "home": "Portal do Parceiro",
+    "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",
+    "usuario_parceiro":        "Usuário Parceiro",
+    "linked_user":             "Usuário Vinculado",
+    "home":                    "Início",
     "dashboard": {
-      "title": "Portal do Parceiro",
-      "authorization": "Consultas para Autorização",
-      "scheduling": "Consultas para Agendamento",
-      "completed": "Consultas Realizadas",
-      "not_authorized": "Consultas Não Autorizadas"
+      "title":         "Dashboard",
+      "authorization": "Autorizações Pendentes",
+      "scheduling":    "Agendamentos",
+      "completed":     "Concluídos",
+      "not_authorized":"Não Autorizados"
     }
   },
-  "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"
-  },
-  "relatorio": {
-    "novos_associados": "Novos Associados",
-    "contatos": "Contatos",
-    "exclusoes_mes": "Exclusões no mês",
-    "exportar_csv": "Exportar Excel",
-    "col": {
-      "cadastro": "Cadastro",
-      "exclusao": "Exclusão"
-    },
-    "exportar_excel": "Exportar Excel"
-  },
   "agendamento": {
-    "nova_solicitacao": "Nova Solicitação",
-    "visao_geral": "Visão Geral de Agendamentos",
-    "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?",
+    "associado":                  "Associado",
+    "solicitar":                  "Solicitar",
+    "nova_solicitacao":           "Nova Solicitação",
+    "visao_geral":                "Visão Geral",
+    "aprovados_automaticamente":  "Aprovados Automaticamente",
+    "confirm_approve":            "Confirmar aprovação deste agendamento?",
+    "confirm_reject":             "Confirmar recusa deste agendamento?",
     "status": {
-      "pendente": "Aguardando",
-      "confirmado": "Aprovado",
-      "recusado": "Recusado",
-      "cancelado": "Cancelado",
-      "concluido": "Concluído"
+      "pendente":   "Pendente",
+      "confirmado": "Confirmado",
+      "recusado":   "Recusado",
+      "cancelado":  "Cancelado",
+      "concluido":  "Concluído"
     },
     "col": {
-      "pedido": "Pedido",
-      "parceiro": "Parceiro",
-      "servico": "Serviço",
-      "solicitacao": "Solicitação"
+      "pedido":     "Pedido",
+      "parceiro":   "Parceiro",
+      "servico":    "Serviço",
+      "solicitacao":"Solicitação"
     }
   },
+  "notification": {
+    "empty":                "Nenhuma notificação no momento",
+    "new":                  "Nova",
+    "history":              "Histórico",
+    "title_label":          "Título",
+    "title_placeholder":    "Título da notificação",
+    "message_label":        "Mensagem",
+    "message_placeholder":  "Mensagem da notificação",
+    "recipients":           "Destinatários",
+    "by_position":          "Por Cargo",
+    "by_sector":            "Por Setor",
+    "media":                "Mídia",
+    "send":                 "Enviar",
+    "all":                  "Todos",
+    "recipient_associado":  "Associados",
+    "recipient_parceiro":   "Parceiros",
+    "details":              "Detalhes",
+    "sent_to":              "Enviado para {count} usuário(s)",
+    "seen_by":              "Visto por {count} usuário(s)"
+  },
   "associate_validation": {
-    "title": "Validar Carteirinha",
-    "description": "Digite as informações do associado para verificar o status.",
-    "search_placeholder": "EX: Nome, CPF, Matrícula",
-    "search": "Buscar",
-    "associate_found": "Associado Encontrado",
-    "name": "Nome",
-    "cpf": "CPF",
-    "registration": "Matrícula",
-    "qr_code": "Ler QR CODE",
-    "not_found": "Associado não encontrado"
+    "title":               "Validar Carteirinha",
+    "qr_code":             "QR Code",
+    "description":         "Busque o associado pelo crachá ou CPF",
+    "search_placeholder":  "Crachá ou CPF",
+    "search":              "Buscar",
+    "associate_found":     "Associado Encontrado",
+    "name":                "Nome",
+    "cpf":                 "CPF",
+    "registration":        "Matrícula"
+  },
+  "relatorio": {
+    "exportar_excel":    "Exportar Excel",
+    "novos_associados":  "Novos Associados",
+    "contatos":          "Contatos",
+    "exclusoes_mes":     "Exclusões do Mês",
+    "col": {
+      "cadastro": "Cadastro",
+      "exclusao": "Exclusão"
+    }
   }
 }

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

@@ -17,7 +17,7 @@
             class="card-avatar"
             :size="isVertical ? '100px' : '76px'"
           >
-            <img v-if="user?.avatar" :src="user.avatar" />
+            <img v-if="user?.photo_url" :src="user.photo_url" />
             <q-icon v-else name="mdi-account" color="white" :size="isVertical ? '64px' : '40px'" />
           </q-avatar>
 

+ 70 - 32
src/pages/associado/profile/ProfilePage.vue

@@ -8,7 +8,8 @@
         <div class="profile-header row items-center q-pa-lg">
           <div class="profile-avatar-wrap q-mr-md">
             <q-avatar size="76px" class="profile-avatar">
-              <q-icon name="mdi-account" size="44px" color="white" />
+              <img v-if="user?.photo_url" :src="user.photo_url" />
+              <q-icon v-else name="mdi-account" size="44px" color="white" />
             </q-avatar>
             <q-btn
               round
@@ -18,12 +19,20 @@
               class="profile-avatar-edit"
               color="white"
               text-color="violet-normal"
-              disable
+              :loading="uploadingAvatar"
+              @click="triggerAvatarUpload"
+            />
+            <input
+              ref="avatarInputRef"
+              type="file"
+              accept="image/*"
+              style="display: none"
+              @change="onAvatarSelected"
             />
           </div>
           <div class="column q-gutter-xs">
             <span class="text-white text-h6 text-weight-bold">{{ user?.name || '—' }}</span>
-            <div>
+            <div class="row items-center q-gutter-xs">
               <q-badge
                 :color="statusBadgeColor"
                 class="text-capitalize"
@@ -31,6 +40,17 @@
               >
                 {{ statusLabel }}
               </q-badge>
+              <q-btn
+                v-if="user?.photo_url"
+                flat
+                round
+                dense
+                size="xs"
+                icon="mdi-image-remove"
+                color="white"
+                :loading="removingAvatar"
+                @click="onRemoveAvatar"
+              />
             </div>
             <span class="text-white profile-validity text-caption">
               {{ $t('associado.validity') }} {{ user?.expiry_date || '—' }}
@@ -105,11 +125,12 @@
 </template>
 
 <script setup>
-import { ref, computed, onMounted, defineAsyncComponent } from "vue";
+import { ref, computed, onMounted, defineAsyncComponent, useTemplateRef } from "vue";
 import { useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
 import { userStore } from "src/stores/user";
 import { getDependentsByUser } from "src/api/profile";
+import { uploadMyAvatar, deleteMyAvatar } from "src/api/user";
 
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 
@@ -123,6 +144,9 @@ const store = userStore();
 
 const user = ref(null);
 const dependents = ref([]);
+const uploadingAvatar = ref(false);
+const removingAvatar = ref(false);
+const avatarInputRef = useTemplateRef("avatarInputRef");
 
 const statusBadgeColor = computed(() => {
   switch (user.value?.status) {
@@ -189,6 +213,48 @@ onMounted(async () => {
   await loadDependents();
 });
 
+const triggerAvatarUpload = () => {
+  avatarInputRef.value?.click();
+};
+
+const onAvatarSelected = async (event) => {
+  const file = event.target.files?.[0];
+  if (!file) return;
+  uploadingAvatar.value = true;
+  try {
+    const updated = await uploadMyAvatar(file);
+    store.user = updated;
+    user.value = updated;
+    $q.notify({ type: "positive", message: t("http.success") });
+  } catch {
+    $q.notify({ type: "negative", message: t("http.errors.failed") });
+  } finally {
+    uploadingAvatar.value = false;
+    event.target.value = "";
+  }
+};
+
+const onRemoveAvatar = () => {
+  $q.dialog({
+    title: t("common.ui.messages.confirm_action"),
+    message: t("associado.remove_photo"),
+    cancel: true,
+    persistent: true,
+  }).onOk(async () => {
+    removingAvatar.value = true;
+    try {
+      const updated = await deleteMyAvatar();
+      store.user = updated;
+      user.value = updated;
+      $q.notify({ type: "positive", message: t("http.success") });
+    } catch {
+      $q.notify({ type: "negative", message: t("http.errors.failed") });
+    } finally {
+      removingAvatar.value = false;
+    }
+  });
+};
+
 const onAddDependent = () => {
   $q.dialog({
     component: AddEditDependentDialog,
@@ -198,34 +264,6 @@ const onAddDependent = () => {
     },
   }).onOk(() => loadDependents());
 };
-
-// const onEditDependent = (dep) => {
-//   $q.dialog({
-//     component: AddEditDependentDialog,
-//     componentProps: {
-//       dependent: dep,
-//       userId: user.value.id,
-//       title: () => t("common.actions.edit") + " " + t("associado.dependent"),
-//     },
-//   }).onOk(() => loadDependents());
-// };
-
-// const onDeleteDependent = (dep) => {
-//   $q.dialog({
-//     title: t("common.ui.messages.confirm_action"),
-//     message: t("common.ui.messages.are_you_sure_delete"),
-//     cancel: true,
-//     persistent: true,
-//   }).onOk(async () => {
-//     try {
-//       await deleteDependent(dep.id);
-//       await loadDependents();
-//       $q.notify({ type: "positive", message: t("http.success") });
-//     } catch {
-//       $q.notify({ type: "negative", message: t("http.errors.failed") });
-//     }
-//   });
-// };
 </script>
 
 <style lang="scss" scoped>

+ 1 - 2
src/pages/loja/LojaPage.vue

@@ -86,14 +86,12 @@
                     </div>
                     <div class="row no-wrap items-center" style="gap: 2px">
                       <q-btn
-                        v-if="isActive(item)"
                         flat round dense size="sm"
                         color="negative"
                         icon="mdi-pause-circle-outline"
                         @click="onSetStatus(item, 'inactive')"
                       />
                       <q-btn
-                        v-else
                         flat round dense size="sm"
                         color="positive"
                         icon="mdi-play-circle-outline"
@@ -313,6 +311,7 @@ const onSetStatus = async (item) => {
     const updated = await toggleStoreItemStatus(item.id);
     const idx = items.value.findIndex((i) => i.id === item.id);
     if (idx !== -1) items.value[idx] = { ...items.value[idx], ...updated };
+    $q.notify({ type: "positive", message: t("http.success") });
   } catch {
     $q.notify({ type: "negative", message: t("http.errors.failed") });
   }