Browse Source

feat: ✨ feat (landingpage e verificacao automatica cadastro) criado novos campos dinamicos na landingpage e aprovacao automatica do associado

foram criados 4 novos campos na landingpage: visao, missao, valores e historia, preenchiveis no sistema web, e tambem foi finalizado o fluxo de cadastro -> aprovacao -> primeiro acesso e efetivacao do usuario, criando a aprovacao automatica caso o primeiro acesso seja do usuario que foi registrado pela importacao de excel

fase:dev | origin:escopo
Gustavo Zanatta 4 days ago
parent
commit
6eccd39e6c

+ 6 - 2
src/api/firstAccess.js

@@ -1,7 +1,11 @@
 import api from "src/api";
 
 export const checkFirstAccess = async (registration) => {
-  const { data } = await api.post("/first-access/check", { registration });
+  const { data } = await api.post(
+    "/first-access/check",
+    { registration },
+    { skipSuccessNotify: true },
+  );
   return { ...data.payload, message: data.message };
 };
 
@@ -17,7 +21,7 @@ export const registerFirstAccess = async (payload) => {
     headers: { "Content-Type": "multipart/form-data" },
     skipSuccessNotify: true,
   });
-  return data.payload;
+  return { ...data.payload, message: data.message };
 };
 
 export const getPublicPositions = async () => {

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

@@ -262,6 +262,7 @@
       "button": "My First Access",
       "title": "First Access",
       "registration": "Badge",
+      "last_name": "Last name",
       "registration_hint": "Enter your badge number to get started.",
       "form_title": "Complete your registration",
       "form_hint": "Fill in the fields below to finish your first access. Grey fields are filled in by the administration.",
@@ -271,7 +272,11 @@
       "finish": "Finish Registration",
       "success": "Registration completed successfully!",
       "success_login": "Registration completed! Sign in to access the system.",
+      "pending_approval": "Registration completed! Your membership is under review by the administration — you will be able to sign in once it is approved.",
       "already_done": "The first access for this badge has already been completed. Please sign in or use \"Forgot your password?\".",
+      "not_found": "Badge not found. Request your membership on the Serprati website and wait for the administration to approve it.",
+      "not_approved": "Your registration is still under review by the administration. You will be able to complete the first access once it is approved.",
+      "refused": "This registration was not approved by the administration. Please contact Serprati.",
       "back_to_login": "Back to login"
     }
   },
@@ -460,9 +465,16 @@
     "section": {
       "hero": "Home Page",
       "stats": "Statistics",
+      "about": "About Us",
       "contacts": "Contacts",
       "benefits": "Benefits"
     },
+    "about": {
+      "vision": "Vision",
+      "mission": "Mission",
+      "values": "Values",
+      "history": "History"
+    },
     "hero": {
       "background_image": "Background Image",
       "upload_image": "Upload Image",
@@ -590,7 +602,8 @@
       "pt": "Português",
       "en": "English",
       "es": "Español"
-    }
+    },
+    "first_access_password_hint": "Leave the password blank: the member sets their own password when completing the First Access, which is located by the badge number."
   },
   "access_log": {
     "title": "Access Logs",

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

@@ -263,6 +263,7 @@
       "button": "Mi Primer Acceso",
       "title": "Primer Acceso",
       "registration": "Credencial",
+      "last_name": "Apellido",
       "registration_hint": "Ingrese el número de su credencial para comenzar.",
       "form_title": "Complete su registro",
       "form_hint": "Complete los datos a continuación para finalizar su primer acceso. Los campos en gris son completados por la administración.",
@@ -272,7 +273,11 @@
       "finish": "Finalizar Registro",
       "success": "¡Registro completado con éxito!",
       "success_login": "¡Registro completado! Inicie sesión para acceder al sistema.",
+      "pending_approval": "¡Registro completado! Su asociación está en análisis por la administración — podrá acceder en cuanto sea aprobada.",
       "already_done": "El primer acceso de esta credencial ya fue realizado. Inicie sesión normalmente o use \"¿Olvidó su contraseña?\".",
+      "not_found": "Credencial no encontrada. Solicite su asociación en el sitio de Serprati y espere la aprobación de la administración.",
+      "not_approved": "Su registro aún está en análisis por la administración. Podrá concluir el primer acceso en cuanto sea aprobado.",
+      "refused": "Este registro no fue aprobado por la administración. Póngase en contacto con Serprati.",
       "back_to_login": "Volver al inicio de sesión"
     }
   },
@@ -461,9 +466,16 @@
     "section": {
       "hero": "Página de Inicio",
       "stats": "Estadísticas",
+      "about": "Sobre Nosotros",
       "contacts": "Contactos",
       "benefits": "Beneficios"
     },
+    "about": {
+      "vision": "Visión",
+      "mission": "Misión",
+      "values": "Valores",
+      "history": "Historia"
+    },
     "hero": {
       "background_image": "Imagen de Fondo",
       "upload_image": "Subir Imagen",
@@ -591,7 +603,8 @@
       "pt": "Português",
       "en": "English",
       "es": "Español"
-    }
+    },
+    "first_access_password_hint": "Deje la contraseña en blanco: el asociado define su propia contraseña al completar el Primer Acceso, localizado por el número de credencial."
   },
   "access_log": {
     "title": "Registros de Acceso",

+ 14 - 1
src/i18n/locales/pt.json

@@ -263,6 +263,7 @@
       "button": "Meu Primeiro Acesso",
       "title": "Primeiro Acesso",
       "registration": "Crachá",
+      "last_name": "Sobrenome",
       "registration_hint": "Informe o número do seu crachá para começar.",
       "form_title": "Complete seu cadastro",
       "form_hint": "Preencha os dados abaixo para concluir seu primeiro acesso. Os campos em cinza são preenchidos pela administração.",
@@ -272,7 +273,11 @@
       "finish": "Concluir Cadastro",
       "success": "Cadastro concluído com sucesso!",
       "success_login": "Cadastro concluído! Faça login para acessar o sistema.",
+      "pending_approval": "Cadastro concluído! Sua associação está em análise pela administração — você poderá acessar assim que for aprovada.",
       "already_done": "O primeiro acesso deste crachá já foi realizado. Faça login normalmente ou use \"Esqueceu a senha?\".",
+      "not_found": "Crachá não encontrado. Solicite sua associação pelo site da Serprati e aguarde a liberação da administração.",
+      "not_approved": "Seu cadastro ainda está em análise pela administração. Você poderá concluir o primeiro acesso assim que for aprovado.",
+      "refused": "Este cadastro não foi aprovado pela administração. Entre em contato com a Serprati.",
       "back_to_login": "Voltar ao login"
     }
   },
@@ -461,9 +466,16 @@
     "section": {
       "hero": "Página Inicial",
       "stats": "Estatísticas",
+      "about": "Sobre Nós",
       "contacts": "Contatos",
       "benefits": "Benefícios"
     },
+    "about": {
+      "vision": "Visão",
+      "mission": "Missão",
+      "values": "Valores",
+      "history": "História"
+    },
     "hero": {
       "background_image": "Imagem de Fundo",
       "upload_image": "Enviar Imagem",
@@ -591,7 +603,8 @@
       "pt": "Português",
       "en": "English",
       "es": "Español"
-    }
+    },
+    "first_access_password_hint": "Deixe a senha em branco: o associado define a própria senha ao concluir o Primeiro Acesso, localizado pelo número do crachá."
   },
   "access_log": {
     "title": "Logs de Acesso",

+ 41 - 0
src/pages/company-settings/CompanySettingsPage.vue

@@ -113,6 +113,43 @@
                 </div>
               </div>
 
+              <div class="col-12">
+                <div class="text-subtitle2 q-mb-sm">{{ $t("company_settings.section.about") }}</div>
+              </div>
+
+              <DefaultInput
+                v-model="form.about_vision"
+                v-model:error="validationErrors.about_vision"
+                :label="$t('company_settings.about.vision')"
+                type="textarea"
+                :rows="4"
+                class="col-md-4 col-12"
+              />
+              <DefaultInput
+                v-model="form.about_mission"
+                v-model:error="validationErrors.about_mission"
+                :label="$t('company_settings.about.mission')"
+                type="textarea"
+                :rows="4"
+                class="col-md-4 col-12"
+              />
+              <DefaultInput
+                v-model="form.about_values"
+                v-model:error="validationErrors.about_values"
+                :label="$t('company_settings.about.values')"
+                type="textarea"
+                :rows="4"
+                class="col-md-4 col-12"
+              />
+              <DefaultInput
+                v-model="form.about_history"
+                v-model:error="validationErrors.about_history"
+                :label="$t('company_settings.about.history')"
+                type="textarea"
+                :rows="8"
+                class="col-12"
+              />
+
               <div class="col-12">
                 <div class="text-subtitle2 q-mb-sm">{{ $t("company_settings.section.contacts") }}</div>
               </div>
@@ -279,6 +316,10 @@ const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
   stat2_label: "",
   stat3_value: "",
   stat3_label: "",
+  about_vision: "",
+  about_mission: "",
+  about_values: "",
+  about_history: "",
   contact_email: "",
   contact_phone: "",
   contact_location: "",

+ 6 - 6
src/pages/gestao-associados/components/AddEditAssociadoDialog.vue

@@ -40,6 +40,7 @@
           <DefaultInput
             v-model="form.registration"
             v-model:error="validationErrors.registration"
+            :rules="[inputRules.required]"
             :label="$t('associado.registration')"
             :placeholder="$t('common.placeholders.registration')"
             class="col-md-4 col-12"
@@ -69,21 +70,20 @@
           <DefaultPasswordInput
             v-model="form.password"
             v-model:error="validationErrors.password"
-            :rules="associado ? [inputRules.password] : [inputRules.required, inputRules.password]"
+            :rules="[inputRules.password]"
             :label="$t('common.terms.password')"
             :placeholder="$t('auth.password_hint')"
             class="col-md-6 col-12"
           />
           <DefaultPasswordInput
             v-model="confirmPassword"
-            :rules="
-              associado
-                ? [inputRules.samePassword(form.password)]
-                : [inputRules.required, inputRules.samePassword(form.password)]
-            "
+            :rules="[inputRules.samePassword(form.password)]"
             :label="$t('auth.confirm_password')"
             class="col-md-6 col-12"
           />
+          <div v-if="!associado" class="col-12 text-caption text-grey-7">
+            {{ $t("associado.first_access_password_hint") }}
+          </div>
         </q-card-section>
         <q-card-actions>
           <q-space />

+ 32 - 10
src/pages/login/FirstAccessFormPage.vue

@@ -57,6 +57,17 @@
               :label="$t('auth.first_access.registration')"
             />
 
+            <DefaultInput
+              v-model="form.cpf"
+              v-model:error="validationErrors.cpf"
+              class="col-12 col-sm-6"
+              :readonly="isLocked('cpf')"
+              :bg-color="isLocked('cpf') ? 'grey-2' : undefined"
+              :mask="masks.Brasil.cpf"
+              :label="$t('common.terms.cpf')"
+              :rules="[inputRules.required, inputRules.cpf]"
+            />
+
             <DefaultInput
               v-model="form.name"
               v-model:error="validationErrors.name"
@@ -68,14 +79,13 @@
             />
 
             <DefaultInput
-              v-model="form.cpf"
-              v-model:error="validationErrors.cpf"
+              v-model="form.last_name"
+              v-model:error="validationErrors.last_name"
               class="col-12 col-sm-6"
-              :readonly="isLocked('cpf')"
-              :bg-color="isLocked('cpf') ? 'grey-2' : undefined"
-              :mask="masks.Brasil.cpf"
-              :label="$t('common.terms.cpf')"
-              :rules="[inputRules.required, inputRules.cpf]"
+              :readonly="isLocked('last_name')"
+              :bg-color="isLocked('last_name') ? 'grey-2' : undefined"
+              :label="$t('auth.first_access.last_name')"
+              :rules="[inputRules.required]"
             />
 
             <DefaultInput
@@ -212,8 +222,9 @@ const sectorOptions = ref([]);
 
 const form = ref({
   registration: null,
-  name: null,
   cpf: null,
+  name: null,
+  last_name: null,
   email: null,
   phone: null,
   position_id: null,
@@ -236,6 +247,7 @@ const {
 
 const prefillRules = {
   name: [inputRules.required],
+  last_name: [inputRules.required],
   cpf: [inputRules.required, inputRules.cpf],
   email: [inputRules.required, inputRules.email],
   phone: [inputRules.required],
@@ -277,7 +289,7 @@ const onSubmit = async () => {
 
   const password = form.value.password;
 
-  const user = await submitForm(() =>
+  const result = await submitForm(() =>
     registerFirstAccess({
       ...form.value,
       token: token.value,
@@ -285,10 +297,20 @@ const onSubmit = async () => {
     }),
   );
 
-  if (!user) return;
+  if (!result) return;
 
   clearFirstAccessData();
 
+  if (!result.can_login) {
+    $q.notify({
+      type: "warning",
+      message: result.message ?? t("auth.first_access.pending_approval"),
+      timeout: 8000,
+    });
+    router.push({ name: "LoginPage" });
+    return;
+  }
+
   try {
     await login(form.value.registration, password, "associado", {
       skipSuccessNotify: true,

+ 13 - 2
src/pages/login/FirstAccessPage.vue

@@ -80,14 +80,25 @@ const {
   execute: submitForm,
 } = useSubmitHandler({ formRef });
 
+const ALLOWED_STATUS = "pending_first_access";
+
+const BLOCKED_MESSAGES = {
+  already_done: "auth.first_access.already_done",
+  not_found: "auth.first_access.not_found",
+  not_approved: "auth.first_access.not_approved",
+  refused: "auth.first_access.refused",
+};
+
 const onSubmit = async () => {
   const payload = await submitForm(() => checkFirstAccess(form.value.registration));
   if (!payload) return;
 
-  if (payload.status === "already_done") {
+  if (payload.status !== ALLOWED_STATUS) {
     $q.notify({
       type: "warning",
-      message: payload.message ?? t("auth.first_access.already_done"),
+      message:
+        payload.message ??
+        t(BLOCKED_MESSAGES[payload.status] ?? "auth.first_access.not_found"),
       timeout: 6000,
     });
     return;

+ 17 - 3
src/pages/users/components/AddEditUserDialog.vue

@@ -29,11 +29,20 @@
             :placeholder="$t('common.placeholders.email')"
             class="col-12"
           />
+          <DefaultInput
+            v-if="isAssociado"
+            v-model="form.registration"
+            v-model:error="validationErrors.registration"
+            :rules="[inputRules.required]"
+            :label="$t('associado.registration')"
+            :placeholder="$t('common.placeholders.registration')"
+            class="col-12"
+          />
           <DefaultPasswordInput
             v-model="form.password"
             v-model:error="validationErrors.password"
             :rules="
-              user
+              user || isAssociado
                 ? [inputRules.password]
                 : [inputRules.required, inputRules.password]
             "
@@ -44,13 +53,16 @@
           <DefaultPasswordInput
             v-model="confirmPassword"
             :rules="
-              user
+              user || isAssociado
                 ? [inputRules.samePassword(form.password)]
                 : [inputRules.required, inputRules.samePassword(form.password)]
             "
             :label="$t('auth.confirm_password')"
             class="col-md-6 col-12"
           />
+          <div v-if="isAssociado && !user" class="col-12 text-caption text-grey-7">
+            {{ $t("associado.first_access_password_hint") }}
+          </div>
         </q-card-section>
         <q-card-actions>
           <q-space />
@@ -73,7 +85,7 @@
   </q-dialog>
 </template>
 <script setup>
-import { ref, useTemplateRef, watch } from "vue";
+import { computed, ref, useTemplateRef, watch } from "vue";
 import { useInputRules } from "src/composables/useInputRules";
 import { useDialogPluginComponent } from "quasar";
 import { useI18n } from "vue-i18n";
@@ -114,11 +126,13 @@ const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
   name: user ? user?.name : "",
   email: user ? user?.email : "",
   type: user ? user?.type : "",
+  registration: user ? (user?.registration ?? "") : "",
   password: "",
 });
 
 const selectedUserType = ref(null);
 const confirmPassword = ref("");
+const isAssociado = computed(() => form.type === "associado");
 
 const {
   loading,