Explorar o código

fix: :bug: localização e crud basico de usuario

Denis hai 1 ano
pai
achega
916a1d36f1

+ 10 - 9
src/boot/i18n.js

@@ -3,15 +3,16 @@ import { createI18n } from "vue-i18n";
 import { Cookies } from "quasar";
 import messages from "src/i18n";
 
-export default boot(({ app }) => {
-  const i18n = createI18n({
-    locale: Cookies.get("locale")
-      ? Cookies.get("locale")
-      : window.navigator.language,
-    globalInjection: true,
-    messages,
-  });
+const i18n = createI18n({
+  locale: Cookies.get("locale")
+    ? Cookies.get("locale")
+    : window.navigator.language,
+  globalInjection: true,
+  messages,
+});
 
-  // Set i18n instance on app
+export default boot(({ app }) => {
   app.use(i18n);
 });
+
+export { i18n };

+ 2 - 1
src/css/app.scss

@@ -1,4 +1,5 @@
-// app global css in SCSS form
+// app global css in SCSS format
+
 .input-disable {
   .q-field--outlined .q-field__control::before {
     border: 1px solid #b9b9b9 !important;

+ 10 - 0
src/css/quasar.variables.scss

@@ -23,3 +23,13 @@ $positive: #21ba45;
 $negative: #c10015;
 $info: #31ccec;
 $warning: #f2c037;
+
+$page: #f5f5f5;
+
+.body--light {
+  --q-page: #f5f5f5;
+}
+
+.body--dark {
+  --q-page: #1d1d1d;
+}

+ 2 - 0
src/css/table.scss

@@ -9,6 +9,7 @@
   }
 
   .body--light & {
+    background-color: #eef4f8;
     --table-bg-color: #f9f9f9;
     --table-border-color: #e0e0e0;
     --table-header-color: #717171;
@@ -45,6 +46,7 @@
   }
 
   .q-table__top {
+    padding-top: 16px;
     padding-left: 0px;
     padding-right: 0px;
     padding-bottom: 16px;

+ 10 - 32
src/helpers/utils.js

@@ -1,3 +1,5 @@
+import { useI18n } from "vue-i18n";
+
 /**
  * Este é um arquivo que contem funções utilitárias.
  * Siga o padrão de descrição de funções.
@@ -7,15 +9,15 @@
  * @description Regras de validação de inputs.
  */
 const inputRules = {
-  required: (value) => !!value || "Campo Obrigatório",
-  requiredNumber: (value) => !isNaN(value) || "Campo Obrigatório",
+  required: (value) => !!value || useI18n().t("rules.required"),
+  requiredNumber: (value) => !isNaN(value) || useI18n().t("rules.required"),
   requiredHideMessage: (value) => !!value,
-  min: (value) => value.length >= 3 || "Min de caracteres é 3",
+  min: (value) => value.length >= 3 || useI18n().t("rules.min", { min: 3 }),
   email: (value) => {
     const pattern =
       /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
     if (!value || value == "") return true;
-    return pattern.test(value) || "Email inválido";
+    return pattern.test(value) || useI18n().t("rules.email");
   },
   emails: (value) => {
     if (!value || value == "") return true;
@@ -25,13 +27,11 @@ const inputRules = {
       .filter((email) => email);
     return (
       emails.every((email) => inputRules.email(email) == true) ||
-      "Emails inválidos"
+      useI18n().t("rules.email", 2)
     );
   },
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Corta uma string em um determinado tamanho.
  * @param {string} string string a ser cortada.
@@ -46,8 +46,6 @@ const excerpt = (string, size = 30) => {
   return string;
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Formata uma data de DD/MM/YYYY para YYYY-MM-DD
  * @param {string} date data.
@@ -56,17 +54,15 @@ const excerpt = (string, size = 30) => {
  * @returns {string} data formatada.
  */
 const formatDateDMYtoYMD = (date, time) => {
-  if (!date) throw new Error("data não pode ser nula");
+  if (!date) throw new Error(useI18n().t("rules.required"));
   const testDate =
     /^([0-2][0-9]|(3)[0-1])(\/)(((0)[0-9])|((1)[0-2]))(\/)\d{4}$/;
-  if (testDate.test(date) === false) throw new Error("data inválida");
+  if (testDate.test(date) === false) throw new Error(useI18n().t("rules.date"));
 
   const [day, month, year] = date.split("/");
   return `${year}-${month}-${day} ${time ? time : ""}`;
 };
 
-//  ------------------------------------------------------------------------------------------
-
 /**
  * @description Converte uma data e hora para o formato brasileiro.
  * @param {string} dateTimeString data e hora.
@@ -94,7 +90,7 @@ const convertDateTime = (dateTimeString) => {
   return formattedDateTime;
 };
 
-/*
+/**
  * @description Formata uma data de YYYY-MM-DD para DD/MM/YYYY
  * @param {string} dateTime data e hora.
  * @returns {string} data e hora no formato brasileiro.
@@ -114,8 +110,6 @@ const formatDateYMDtoDMY = (dateTime) => {
   return formattedDate;
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Checa a moeda selecionada.
  * @param {number} moeda moeda selecionada.
@@ -162,8 +156,6 @@ const checaMoeda = (moeda) => {
   return currencyOptions;
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Filtra a moeda.
  * @param {number} value valor.
@@ -180,8 +172,6 @@ const filterCurrency = (value) => {
   return value;
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Filtra a unidade de medida.
  * @param {number} value valor.
@@ -191,8 +181,6 @@ const filterUnidadeMedida = (value) => {
   return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Valida se a data é válida.
  * @param {string} date data.
@@ -203,8 +191,6 @@ const validaData = (date) => {
   return regex.test(date);
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Valida se a hora é válida.
  * @param {string} time hora.
@@ -215,8 +201,6 @@ const validaHora = (time) => {
   return regex.test(time);
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Valida se a data e hora são válidas.
  * @param {string} dataHora data e hora.
@@ -228,8 +212,6 @@ const validaDataHora = (dataHora) => {
   return regex.test(dataHora);
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Formata a quantidade.
  * @param {number} value valor.
@@ -245,8 +227,6 @@ const formatQuantity = (value) => {
   return value;
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Formata a moeda.
  * @param {number} value valor.
@@ -264,8 +244,6 @@ const formatCurrency = (value) => {
   return value;
 };
 
-// ------------------------------------------------------------------------------------------
-
 /**
  * @description Verifica se os emails são válidos.
  * @param {string} data emails.

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

@@ -1,6 +1,7 @@
 {
   "general": {
     "add": "Add",
+    "edit": "Edit",
     "options": "Options",
     "welcome": "Welcome",
     "version": "Version",
@@ -10,7 +11,9 @@
     "confirm_password": "Confirm Password",
     "search": "Search",
     "title": "Title",
-    "rows_per_page": "Rows per page"
+    "rows_per_page": "Rows per page",
+    "save": "Save",
+    "cancel": "Cancel"
   },
   "errors": {
     "404": "Page not found",
@@ -27,6 +30,9 @@
     "users": "Users"
   },
   "users": {
+    "user": "{something} user | {something} users",
+    "name": "Name",
+    "name_and_surname": "Name and Surname",
     "password": "Password",
     "getUser": "Get User",
     "createUser": "Create User",
@@ -42,5 +48,12 @@
     "edit": "You don't have permission to edit this",
     "delete": "You don't have permission to delete this",
     "create": "You don't have permission to create this"
+  },
+  "rules": {
+    "required": "This field is required",
+    "min": "This field must be at least {min} characters",
+    "max": "This field must be at most {max} characters",
+    "email": "This field must be a valid email | These fields must be valid emails",
+    "date": "This field must be a valid date"
   }
 }

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

@@ -1,6 +1,7 @@
 {
   "general": {
     "add": "Añadir",
+    "edit": "Editar",
     "options": "Opciones",
     "welcome": "Bienvenido",
     "version": "Versión",
@@ -9,7 +10,10 @@
     "inactive": "Inactivo",
     "confirm_password": "Confirmar contraseña",
     "search": "Buscar",
-    "rows_per_page": "Filas por página"
+    "title": "Título",
+    "rows_per_page": "Filas por página",
+    "save": "Guardar",
+    "cancel": "Cancelar"
   },
   "errors": {
     "404": "Página no encontrada",
@@ -26,6 +30,9 @@
     "users": "Usuarios"
   },
   "users": {
+    "user": "{something} usuario | {something} usuarios",
+    "name": "Nombre",
+    "name_and_surname": "Nombre y apellido",
     "password": "Contraseña",
     "getUser": "Obtener usuario",
     "createUser": "Crear usuario",
@@ -41,5 +48,12 @@
     "edit": "No tienes permiso para editar esto",
     "delete": "No tienes permiso para eliminar esto",
     "create": "No tienes permiso para crear esto"
+  },
+  "rules": {
+    "required": "Este campo es obligatorio",
+    "min": "Este campo debe tener al menos {min} caracteres",
+    "max": "Este campo debe tener como máximo {max} caracteres",
+    "email": "Este campo debe ser un correo electrónico válido | Estos campos deben ser correos electrónicos válidos",
+    "date": "Este campo debe ser una fecha válida"
   }
 }

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

@@ -1,6 +1,7 @@
 {
   "general": {
     "add": "Adicionar",
+    "edit": "Editar",
     "options": "Opções",
     "welcome": "Bem-vindo",
     "version": "Versão",
@@ -9,7 +10,10 @@
     "inactive": "Inativo",
     "confirm_password": "Confirmar senha",
     "search": "Buscar",
-    "rows_per_page": "Linhas por página"
+    "title": "Título",
+    "rows_per_page": "Linhas por página",
+    "save": "Salvar",
+    "cancel": "Cancelar"
   },
   "errors": {
     "404": "Página não encontrada",
@@ -26,6 +30,9 @@
     "users": "Usuários"
   },
   "users": {
+    "user": "{something} usuario | {something} usuarios",
+    "name_and_surname": "Nome e sobrenome",
+    "name": "Nome",
     "password": "Senha",
     "getUser": "Obter usuário",
     "createUser": "Criar usuário",
@@ -41,5 +48,12 @@
     "edit": "Você não tem permissão para editar isso",
     "delete": "Você não tem permissão para excluir isso",
     "create": "Você não tem permissão para criar isso"
+  },
+  "rules": {
+    "required": "Este campo é obrigatório",
+    "min": "Este campo deve ter pelo menos {min} caracteres",
+    "max": "Este campo deve ter no máximo {max} caracteres",
+    "email": "Este campo deve ser um email válido | Estes campos devem ser emails válidos",
+    "date": "Este campo deve ser uma data válida"
   }
 }

+ 2 - 0
src/layouts/MainLayout.vue

@@ -98,6 +98,8 @@ import { useI18n } from "vue-i18n";
 import darkLogo from "/src/assets/softpar_logo.png";
 // import lightLogo from "/src/assets/logo_softpar_azul.png";
 
+import LeftMenuLayout from "src/components/geral/LeftMenuLayout.vue";
+
 const $q = useQuasar();
 const { availableLocales, locale } = useI18n();
 const selectedLocale = ref(locale.value);

+ 1 - 4
src/pages/LoginPage.vue

@@ -24,7 +24,6 @@
             filled
             type="email"
             label="Email"
-            :rules="[inputRules.required, inputRules.email]"
             lazy-rules
             autofocus
           />
@@ -35,7 +34,6 @@
             filled
             :type="isPwd ? 'password' : 'text'"
             class="q-mt-xs"
-            :rules="[inputRules.required, inputRules.min]"
             lazy-rules
           >
             <template #append>
@@ -72,7 +70,6 @@
 <script setup>
 import { ref, onMounted } from "vue";
 import { useQuasar } from "quasar";
-import { inputRules } from "src/helpers/utils.js";
 import { useAuth } from "src/composables/useAuth";
 import { useRouter } from "vue-router";
 
@@ -113,7 +110,7 @@ const submitLogin = async () => {
 
     submitting.value = false;
 
-    router.push({ name: "Home" });
+    router.push({ name: "HomePage" });
   } catch (error) {
     submitting.value = false;
   }

+ 87 - 0
src/pages/users/components/AddEditUserDialog.vue

@@ -0,0 +1,87 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin">
+      <DefaultDialogHeader :title="props.title" @close="onDialogCancel" />
+      <q-card-section>
+        <q-form ref="formRef" class="row q-col-gutter-sm">
+          <q-input
+            v-model="name"
+            :label="$t('users.name')"
+            :hint="$t('users.name_and_surname')"
+            :rules="[inputRules.required]"
+            class="col-6"
+          />
+          <q-input
+            v-model="email"
+            label="Email"
+            :rules="[inputRules.email]"
+            class="col-6"
+          />
+          <q-input
+            v-model="password"
+            :label="$t('users.password')"
+            :rules="[inputRules.min(6)]"
+            class="col-6"
+          />
+        </q-form>
+      </q-card-section>
+      <q-card-actions align="center">
+        <q-btn color="primary" label="Cancel" @click="onDialogCancel" />
+        <q-space />
+        <q-btn color="primary" label="OK" @click="onOKClick" />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+<script setup>
+import { ref } from "vue";
+import { inputRules } from "src/helpers/utils";
+import { useDialogPluginComponent } from "quasar";
+import { useI18n } from "vue-i18n";
+
+import DefaultDialogHeader from "src/components/geral/DefaultDialogHeader.vue";
+
+defineEmits([
+  // REQUIRED; need to specify some events that your
+  // component will emit through useDialogPluginComponent()
+  ...useDialogPluginComponent.emits,
+]);
+
+const props = defineProps({
+  user: {
+    type: Object,
+    default: null,
+  },
+  title: {
+    type: Function,
+    default: () => useI18n().t("general.title"),
+  },
+});
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+
+const formRef = ref(null);
+
+const name = ref(props.user?.name || "");
+const email = ref(props.user?.email || "");
+const password = ref("");
+
+// this is part of our example (so not required)
+const onOKClick = () => {
+  if (!formRef.value.validate()) {
+    return;
+  }
+
+  const payload = {
+    name: name.value,
+    email: email.value,
+    password: password.value,
+  };
+  // on OK, it is REQUIRED to
+  // call onDialogOK (with optional payload)
+  onDialogOK({ ...payload });
+  // or with payload: onDialogOK({ ... })
+  // ...and it will also hide the dialog automatically
+};
+</script>

+ 4 - 6
src/router/index.js

@@ -6,11 +6,9 @@ import {
   createWebHashHistory,
 } from "vue-router";
 import routes from "./routes";
-import { Cookies, Notify } from "quasar";]
-import { useI18n } from "vue-i18n";
+import { Cookies, Notify } from "quasar";
 import { permissionStore } from "src/stores/permission";
-
-const {t} = useI18n();
+import { useI18n } from "vue-i18n";
 /*
  * If not building with SSR mode, you can
  * directly export the Router instantiation;
@@ -41,13 +39,13 @@ export default route(function (/* { store, ssrContext } */) {
     const { getAccess } = permissionStore();
     const access_token = Cookies.get("access_token");
     if (to.meta.requireAuth && !access_token) {
-      return next({ name: "Login" });
+      return next({ name: "LoginPage" });
     }
     if (to.meta.requiredPermission) {
       const permission = getAccess(to.meta.requiredPermission, "view");
       if (!permission) {
         Notify.create({
-          message: t("permissions.view"),
+          message: useI18n().t("permissions.view"),
           type: "negative",
         });
         return next(from);

+ 2 - 2
src/router/routes.js

@@ -15,7 +15,7 @@ const routes = [
       {
         path: "",
         name: "HomePage",
-        component: () => import("pages/HomePage.vue"),
+        component: () => import("src/pages/home/HomePage.vue"),
         meta: {
           requireAuth: true,
         },
@@ -29,7 +29,7 @@ const routes = [
     children: [
       {
         path: "",
-        name: "Login",
+        name: "LoginPage",
         component: () => import("pages/LoginPage.vue"),
         meta: {
           title: "Login",

+ 1 - 1
src/router/routes/users.route.js

@@ -2,7 +2,7 @@ const routes = [
   {
     path: "/users",
     name: "UsersPage",
-    component: () => import("pages/UsersPage.vue"),
+    component: () => import("pages/users/UsersPage.vue"),
     meta: {
       requireAuth: true,
       requiredPermission: "config.user",