Quellcode durchsuchen

refactor: centraliza rules para nao deixar implementacoes proprias em cada componente

Gustavo Mantovani vor 2 Tagen
Ursprung
Commit
6bef2ae7ef

+ 1 - 1
src/components/defaults/DefaultCurrencyInput.vue

@@ -73,7 +73,7 @@ watch(
 
 const minRule = inputRules.minValue(0);
 const finalRules = computed(() => [
-  ...rules,
+  ...inputRules.forValue(rules, () => numberValue.value),
   () => minRule(numberValue.value),
 ]);
 </script>

+ 5 - 4
src/components/financial/SettleAccountReceivableDialog.vue

@@ -21,9 +21,7 @@
               label="Data do pagamento"
               lazy-rules="ondemand"
               :error-message="validationErrors.payment_date"
-              :rules="[
-                (value) => !!value || 'Informe a data do pagamento',
-              ]"
+              :rules="[inputRules.required]"
             />
           </q-card-section>
         </q-scroll-area>
@@ -58,6 +56,7 @@ import { ref } from "vue";
 import { settleFranchiseeReceivable } from "src/api/franchisee_account_receive";
 import { useDialogPluginComponent } from "quasar";
 import { useForm } from "src/composables/useForm";
+import { useInputRules } from "src/composables/useInputRules";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
@@ -76,6 +75,8 @@ const { receivable } = defineProps({
 const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
   useDialogPluginComponent();
 
+const { inputRules } = useInputRules();
+
 const formRef = ref(null);
 
 const today = new Date();
@@ -106,4 +107,4 @@ const onSubmit = () =>
       payment_date: form.payment_date_iso,
     }),
   );
-</script>
+</script>

+ 5 - 4
src/components/financial/SettleCompanyPayableDialog.vue

@@ -21,9 +21,7 @@
               label="Data do pagamento"
               lazy-rules="ondemand"
               :error-message="validationErrors.payment_date"
-              :rules="[
-                (value) => !!value || 'Informe a data do pagamento',
-              ]"
+              :rules="[inputRules.required]"
             />
 
             <DefaultCurrencyInput
@@ -82,6 +80,7 @@ import { computed, ref } from "vue";
 import { settleCompanyPayable } from "src/api/company_payable";
 import { useDialogPluginComponent } from "quasar";
 import { useForm } from "src/composables/useForm";
+import { useInputRules } from "src/composables/useInputRules";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 
 import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
@@ -100,6 +99,8 @@ const { payable } = defineProps({
 const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
   useDialogPluginComponent();
 
+const { inputRules } = useInputRules();
+
 const formRef = ref(null);
 
 const today = new Date();
@@ -149,4 +150,4 @@ const onSubmit = async () => {
     }),
   );
 };
-</script>
+</script>

+ 126 - 14
src/composables/useInputRules.js

@@ -1,8 +1,17 @@
+import { format, isValid, parse } from "date-fns";
 import { useI18n } from "vue-i18n";
 
 export const useInputRules = () => {
   const { t } = useI18n();
 
+  const hasValue = (value) => {
+    if (value === null || value === undefined) return false;
+    if (typeof value === "string") return value.trim().length > 0;
+    if (Array.isArray(value)) return value.length > 0;
+
+    return true;
+  };
+
   const cepPattern = /^[0-9]{5}-[0-9]{3}$/;
 
   const emailPattern =
@@ -11,14 +20,54 @@ export const useInputRules = () => {
   const passwordPattern = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/;
 
   const inputRules = {
+    afterTime: (getStartTime) => (value) => {
+      const startTime =
+        typeof getStartTime === "function" ? getStartTime() : getStartTime;
+
+      return (
+        !hasValue(startTime) ||
+        !hasValue(value) ||
+        value > startTime ||
+        t("validation.rules.after_time")
+      );
+    },
+
     cep: (value) => {
       if (!value) return true;
+
       return cepPattern.test(value) || t("validation.rules.cep");
     },
 
     cnpj: (value) => !value || isValidCNPJ(value) || t("validation.rules.cnpj"),
+
     cpf: (value) => !value || isValidCPF(value) || t("validation.rules.cpf"),
 
+    date: (value) => {
+      if (!hasValue(value)) return true;
+
+      const formats = ["dd/MM/yyyy", "yyyy-MM-dd"];
+
+      const valid = formats.some((dateFormat) => {
+        const parsedDate = parse(value, dateFormat, new Date());
+
+        return (
+          isValid(parsedDate) &&
+          format(parsedDate, dateFormat) === value
+        );
+      });
+
+      return valid || t("validation.rules.date");
+    },
+
+    durationHours: (value) => {
+      const minutes = Number(value) * 60;
+
+      return (
+        (Number.isInteger(minutes) && minutes >= 1 && minutes <= 1440) ||
+        t("validation.rules.duration_hours")
+      );
+    },
+
     email: (value) =>
       !value || emailPattern.test(value) || t("validation.rules.email"),
 
@@ -33,20 +82,51 @@ export const useInputRules = () => {
       );
     },
 
-    min: (min) => (value) =>
-      value.length >= min ||
-      `${t("validation.rules.min")} ${min} ${t("validation.rules.characters")}`,
+    forValue: (rules, getValue) =>
+      rules.map((rule) => {
+        const wrapped = (...args) => rule(getValue(), ...args);
+
+        if (rule?.$id) wrapped.$id = rule.$id;
+
+        return wrapped;
+      }),
+
+    integer: (value) =>
+      !hasValue(value) ||
+      Number.isInteger(Number(value)) ||
+      t("validation.rules.integer"),
+
+    integerRange:
+      (min, max = null) =>
+      (value) => {
+        if (!hasValue(value)) return true;
+
+        const number = Number(value);
+        const withinRange = number >= min && (max == null || number <= max);
+
+        if (Number.isInteger(number) && withinRange) return true;
+
+        return max == null
+          ? t("validation.rules.integer_min", { min })
+          : t("validation.rules.integer_range", { min, max });
+      },
 
     max: (max) => (value) =>
-      value.length <= max ||
+      !hasValue(value) ||
+      String(value).length <= max ||
       `${t("validation.rules.max")} ${max} ${t("validation.rules.characters")}`,
 
-    minValue: (min) => (value) =>
-      value >= min || `${t("validation.rules.min")} ${min}`,
-
     maxValue: (max) => (value) =>
       value <= max || `${t("validation.rules.max")} ${max}`,
 
+    min: (min) => (value) =>
+      !hasValue(value) ||
+      String(value).length >= min ||
+      `${t("validation.rules.min")} ${min} ${t("validation.rules.characters")}`,
+
+    minValue: (min) => (value) =>
+      value >= min || `${t("validation.rules.min")} ${min}`,
+
     notSameDocument: (allDocuments) => (value) => {
       if (!value) return true;
 
@@ -65,16 +145,44 @@ export const useInputRules = () => {
       return true;
     },
 
+    oneOf: (allowedValues) => (value) =>
+      !hasValue(value) ||
+      allowedValues.includes(value) ||
+      t("validation.rules.one_of"),
+
     password: (value) =>
       !value || passwordPattern.test(value) || t("validation.rules.password"),
 
+    positive: (value) =>
+      Number(value) > 0 || t("validation.rules.positive"),
+
     samePassword: (otherValue) => (value) =>
       value === otherValue || t("validation.rules.same_password"),
 
+    slug: (value) =>
+      !hasValue(value) ||
+      /^[A-Z0-9_]+$/.test(value) ||
+      t("validation.rules.slug"),
+
+    time: (value) => {
+      if (!hasValue(value)) return true;
+
+      const match = /^(\d{2}):(\d{2})$/.exec(value);
+
+      return (
+        (!!match && Number(match[1]) <= 23 && Number(match[2]) <= 59) ||
+        t("validation.rules.time")
+      );
+    },
+
+    whenValue: (getDependency, rule) => (value) =>
+      !hasValue(getDependency()) || rule(value),
+
     //
 
-    required: (value) => !!value || t("validation.rules.required"),
-    requiredHideMessage: (value) => !!value,
+    required: (value) => hasValue(value) || t("validation.rules.required"),
+
+    requiredHideMessage: (value) => hasValue(value),
 
     requiredNumber: (value) =>
       (value !== null &&
@@ -85,8 +193,8 @@ export const useInputRules = () => {
   };
 
   inputRules.required.$id = "required";
-  inputRules.requiredNumber.$id = "required";
   inputRules.requiredHideMessage.$id = "required";
+  inputRules.requiredNumber.$id = "required";
 
   return {
     inputRules,
@@ -133,7 +241,7 @@ const isValidCNPJ = (cnpj) => {
   if (result !== parseInt(digits.charAt(1))) return false;
 
   return true;
-}
+};
 
 const isValidCPF = (cpf) => {
   if (!cpf) return false;
@@ -145,7 +253,9 @@ const isValidCPF = (cpf) => {
 
   let sum = 0;
 
-  for (let i = 0; i < 9; i++) sum += parseInt(cpf.charAt(i)) * (10 - i);
+  for (let i = 0; i < 9; i++) {
+    sum += parseInt(cpf.charAt(i)) * (10 - i);
+  }
 
   let rev = 11 - (sum % 11);
 
@@ -154,7 +264,9 @@ const isValidCPF = (cpf) => {
 
   sum = 0;
 
-  for (let i = 0; i < 10; i++) sum += parseInt(cpf.charAt(i)) * (11 - i);
+  for (let i = 0; i < 10; i++) {
+    sum += parseInt(cpf.charAt(i)) * (11 - i);
+  }
 
   rev = 11 - (sum % 11);
 
@@ -162,4 +274,4 @@ const isValidCPF = (cpf) => {
   if (rev !== parseInt(cpf.charAt(10))) return false;
 
   return true;
-}
+};

+ 9 - 0
src/i18n/locales/en.json

@@ -173,14 +173,23 @@
   },
   "validation": {
     "rules": {
+      "after_time": "The end time must be later than the start time",
       "required": "This field is required",
       "email": "This field must be a valid email | These fields must be valid emails",
       "date": "This field must be a valid date",
+      "duration_hours": "The duration must be between 1 minute and 24 hours",
+      "integer": "The value must be an integer",
+      "integer_min": "The value must be an integer greater than or equal to {min}",
+      "integer_range": "The value must be an integer between {min} and {max}",
       "min": "This field must have at least",
       "max": "This field must have at most",
       "characters": "characters",
+      "one_of": "Select a valid option",
       "password": "Password must have at least 6 characters, one uppercase letter, one lowercase letter and one number",
+      "positive": "The value must be greater than zero",
       "same_password": "Passwords must match",
+      "slug": "Use only uppercase letters, numbers, and underscores",
+      "time": "This field must be a valid time",
       "not_same_document": "The document must be unique for each participant",
       "cpf": "This field must be a valid CPF",
       "cnpj": "This field must be a valid CNPJ",

+ 9 - 0
src/i18n/locales/es.json

@@ -173,14 +173,23 @@
   },
   "validation": {
     "rules": {
+      "after_time": "La hora de finalización debe ser posterior a la hora de inicio",
       "required": "Este campo es obligatorio",
       "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",
+      "duration_hours": "La duración debe estar entre 1 minuto y 24 horas",
+      "integer": "El valor debe ser un número entero",
+      "integer_min": "El valor debe ser un número entero mayor o igual a {min}",
+      "integer_range": "El valor debe ser un número entero entre {min} y {max}",
       "min": "Este campo debe tener al menos",
       "max": "Este campo debe tener como máximo",
       "characters": "caracteres",
+      "one_of": "Seleccione una opción válida",
       "password": "La contraseña debe tener al menos 6 caracteres, una letra mayúscula, una letra minúscula y un número",
+      "positive": "El valor debe ser mayor que cero",
       "same_password": "Las contraseñas deben coincidir",
+      "slug": "Use solo letras mayúsculas, números y guiones bajos",
+      "time": "Este campo debe ser una hora válida",
       "not_same_document": "El documento debe ser único para cada participante",
       "cpf": "Este campo debe ser un CPF válido",
       "cnpj": "Este campo debe ser un CNPJ válido",

+ 9 - 0
src/i18n/locales/pt.json

@@ -173,14 +173,23 @@
   },
   "validation": {
     "rules": {
+      "after_time": "O horário final deve ser posterior ao horário inicial",
       "required": "Este campo é obrigatório",
       "email": "Este campo deve ser um e-mail válido | Estes campos devem ser e-mails válidos",
       "date": "Este campo deve ser uma data válida",
+      "duration_hours": "A duração deve estar entre 1 minuto e 24 horas",
+      "integer": "O valor deve ser um número inteiro",
+      "integer_min": "O valor deve ser um número inteiro maior ou igual a {min}",
+      "integer_range": "O valor deve ser um número inteiro entre {min} e {max}",
       "min": "Este campo deve ter no mínimo",
       "max": "Este campo deve ter no máximo",
       "characters": "caracteres",
+      "one_of": "Selecione uma opção válida",
       "password": "A senha deve ter pelo menos 6 caracteres, uma letra maiúscula, uma letra minúscula e um número",
+      "positive": "O valor deve ser maior que zero",
       "same_password": "As senhas devem ser iguais",
+      "slug": "Use apenas letras maiúsculas, números e sublinhado",
+      "time": "Este campo deve ser um horário válido",
       "not_same_document": "O documento deve ser único para cada participante",
       "cpf": "Este campo deve ser um CPF válido",
       "cnpj": "Este campo deve ser um CNPJ válido",

+ 46 - 13
src/pages/financial/InvoiceIssuancePage.vue

@@ -5,19 +5,19 @@
     <div class="q-px-md">
       <DefaultTable
         v-model:rows="rows"
-        no-api-call
         :add-item="canAdd"
-        title="Emissão de Notas"
-        description="notas"
-        :female="true"
         :columns="columns"
+        :female="true"
+        description="notas"
+        no-api-call
+        title="Emissão de Notas"
         @on-add-item="handleAddItem"
       >
         <template #body-cell-actions="{ row }">
           <q-td align="center">
             <q-btn
-              outline
               icon="mdi-file-outline"
+              outline
               style="width: 36px"
               @click.prevent.stop="handleView(row)"
             />
@@ -29,23 +29,56 @@
 </template>
 
 <script setup>
+import { permissionStore } from "src/stores/permission";
 import { ref } from "vue";
+
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 import DefaultTable from "src/components/defaults/DefaultTable.vue";
-import { permissionStore } from "src/stores/permission";
 
 const rows = ref([]);
+
 const canAdd = permissionStore().getAccess("franchisor_financial", "add");
 
 const columns = [
-  { name: "nf", label: "NF", field: "nf", align: "left" },
-  { name: "name", label: "Nome", field: "name", align: "left" },
-  { name: "due_date", label: "Data de Vencimento", field: "due_date", align: "left" },
-  { name: "value", label: "Valor", field: "value", align: "left" },
-  { name: "status", label: "Status", field: "status", align: "left" },
-  { name: "actions", label: "Ações", field: "actions", align: "center" },
+  {
+    align: "left",
+    field: "nf",
+    label: "NF",
+    name: "nf",
+  },
+  {
+    align: "left",
+    field: "name",
+    label: "Nome",
+    name: "name",
+  },
+  {
+    align: "left",
+    field: "due_date",
+    label: "Data de Vencimento",
+    name: "due_date",
+  },
+  {
+    align: "left",
+    field: "value",
+    label: "Valor",
+    name: "value",
+  },
+  {
+    align: "left",
+    field: "status",
+    label: "Status",
+    name: "status",
+  },
+  {
+    align: "center",
+    field: "actions",
+    label: "Ações",
+    name: "actions",
+  },
 ];
 
 const handleAddItem = () => {};
+
 const handleView = () => {};
-</script>
+</script>

+ 4 - 27
src/pages/kanban/components/AddEditKanbanDialog.vue

@@ -45,7 +45,7 @@
                     class="col-12"
                     label="Título da Tarefa"
                     :error-message="validationErrors.title"
-                    :rules="[inputRules.required, maxLengthRule(255)]"
+                    :rules="[inputRules.required, inputRules.max(255)]"
                   />
 
                   <DefaultSelect
@@ -67,7 +67,7 @@
                     class="col-12 col-sm-6"
                     label="Prazo de Entrega"
                     :error-message="validationErrors.due_date"
-                    :rules="[dateRule]"
+                    :rules="[inputRules.date]"
                   />
 
                   <UnitSelect
@@ -108,7 +108,7 @@
                     class="col-12 col-sm-6"
                     label="Setor"
                     :error-message="validationErrors.sector"
-                    :rules="[maxLengthRule(255)]"
+                    :rules="[inputRules.max(255)]"
                   />
 
                   <DefaultInput
@@ -434,24 +434,6 @@ const buildPayload = () => ({
   title: form.title,
 });
 
-const dateRule = (value) => {
-  if (!value) return true;
-
-  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
-
-  if (!match) return "Informe uma data válida";
-
-  const [, year, month, day] = match.map(Number);
-  const date = new Date(year, month - 1, day);
-
-  return (
-    (date.getFullYear() === year &&
-      date.getMonth() === month - 1 &&
-      date.getDate() === day) ||
-    "Informe uma data válida"
-  );
-};
-
 const handleValidationError = async (component) => {
   currentTab.value = "atividade";
 
@@ -475,11 +457,6 @@ const loadReplies = async () => {
   replies.value = await getKanbanReplies(card.id);
 };
 
-const maxLengthRule = (maximum) => (value) =>
-  !value ||
-  String(value).length <= maximum ||
-  `O campo deve ter no máximo ${maximum} caracteres`;
-
 const onAddComment = () => {
   $q.dialog({
     cancel: {
@@ -630,4 +607,4 @@ onMounted(() => {
   loadMedias();
   loadReplies();
 });
-</script>
+</script>

+ 38 - 65
src/pages/packages/components/AddEditPackageDialog.vue

@@ -27,7 +27,7 @@
                     class="col-12"
                     label="Nome do Pacote"
                     :error-message="validationErrors.name"
-                    :rules="[inputRules.required, maxLengthRule(255)]"
+                    :rules="[inputRules.required, inputRules.max(255)]"
                   />
 
                   <DefaultInput
@@ -38,7 +38,11 @@
                     min="1"
                     type="number"
                     :error-message="validationErrors.quantity_classes"
-                    :rules="[requiredValueRule, integerMinRule(1)]"
+                    :rules="[
+                      inputRules.requiredNumber,
+                      inputRules.integer,
+                      inputRules.minValue(1),
+                    ]"
                   />
 
                   <DefaultInput
@@ -51,7 +55,10 @@
                     type="number"
                     :error-message="validationErrors.class_duration_minutes"
                     :min="0.5"
-                    :rules="[requiredValueRule, classDurationRule]"
+                    :rules="[
+                      inputRules.requiredNumber,
+                      inputRules.durationHours,
+                    ]"
                   />
 
                   <div class="col-12 package-duration-hint">
@@ -65,7 +72,6 @@
                     class="col-4"
                     label="R$ Matrícula"
                     :error-message="validationErrors.contract_register_value"
-                    :rules="[requiredValueRule]"
                   />
 
                   <DefaultCurrencyInput
@@ -74,7 +80,6 @@
                     class="col-4"
                     label="R$ Total do Contrato"
                     :error-message="validationErrors.contract_value"
-                    :rules="[requiredValueRule]"
                   />
 
                   <DefaultInput
@@ -85,7 +90,7 @@
                     min="0"
                     type="number"
                     :error-message="validationErrors.contrat_discount_value"
-                    :rules="[optionalMinRule(0)]"
+                    :rules="[inputRules.minValue(0)]"
                   />
 
                   <div
@@ -107,7 +112,12 @@
                           validationErrors[`materials.${index}.product_id`]
                         "
                         :options="productOptions"
-                        :rules="[materialProductRule(material)]"
+                        :rules="[
+                          inputRules.whenValue(
+                            () => material.price,
+                            inputRules.required,
+                          ),
+                        ]"
                         @update:model-value="onProductSelected(material)"
                       />
 
@@ -123,7 +133,20 @@
                         :error-message="
                           validationErrors[`materials.${index}.quantity`]
                         "
-                        :rules="[materialQuantityRule(material)]"
+                        :rules="[
+                          inputRules.whenValue(
+                            () => material.product_id,
+                            inputRules.requiredNumber,
+                          ),
+                          inputRules.whenValue(
+                            () => material.product_id,
+                            inputRules.integer,
+                          ),
+                          inputRules.whenValue(
+                            () => material.product_id,
+                            inputRules.minValue(1),
+                          ),
+                        ]"
                       />
 
                       <DefaultCurrencyInput
@@ -136,7 +159,12 @@
                         :error-message="
                           validationErrors[`materials.${index}.price`]
                         "
-                        :rules="[materialPriceRule(material)]"
+                        :rules="[
+                          inputRules.whenValue(
+                            () => material.product_id,
+                            inputRules.requiredNumber,
+                          ),
+                        ]"
                       />
 
                       <div class="col-auto">
@@ -629,48 +657,6 @@ const addMaterial = () => {
   });
 };
 
-const classDurationRule = (value) => {
-  const minutes = Number(value) * 60;
-
-  return (
-    (Number.isInteger(minutes) && minutes >= 1 && minutes <= 1440) ||
-    "A duração deve estar entre 1 minuto e 24 horas"
-  );
-};
-
-const hasValue = (value) =>
-  value !== null && value !== undefined && value !== "";
-
-const integerMinRule = (minimum) => (value) =>
-  (Number.isInteger(Number(value)) && Number(value) >= minimum) ||
-  `O valor deve ser um número inteiro maior ou igual a ${minimum}`;
-
-const isMaterialActive = (material) =>
-  material.product_id !== null && material.product_id !== undefined;
-
-const materialPriceRule = (material) => (value) =>
-  !isMaterialActive(material) ||
-  (value !== null &&
-    value !== undefined &&
-    value !== "" &&
-    Number(value) >= 0) ||
-  "Informe um preço maior ou igual a 0";
-
-const materialProductRule = (material) => () =>
-  !hasValue(material.price) ||
-  isMaterialActive(material) ||
-  "Selecione o material";
-
-const materialQuantityRule = (material) => (value) =>
-  !isMaterialActive(material) ||
-  (Number.isInteger(Number(value)) && Number(value) >= 1) ||
-  "A quantidade deve ser um número inteiro maior ou igual a 1";
-
-const maxLengthRule = (maximum) => (value) =>
-  !value ||
-  String(value).length <= maximum ||
-  `O campo deve ter no máximo ${maximum} caracteres`;
-
 const onProductSelected = (material) => {
   const option = productOptions.value.find(
     (item) => item.value === material.product_id,
@@ -681,23 +667,10 @@ const onProductSelected = (material) => {
   }
 };
 
-const optionalMinRule = (minimum) => (value) =>
-  value === null ||
-  value === undefined ||
-  value === "" ||
-  Number(value) >= minimum ||
-  `O valor mínimo é ${minimum}`;
-
 const removeMaterial = (index) => {
   form.materials.splice(index, 1);
 };
 
-const requiredValueRule = (value) =>
-  (value !== null && value !== undefined && value !== "") ||
-  inputRules.required(null);
-
-requiredValueRule.$id = "required";
-
 const setAllGroupsVisible = (visible) => {
   groups.value.forEach((group) => {
     group.visible = visible;
@@ -890,4 +863,4 @@ onMounted(loadData);
   padding-top: 4px;
   padding-bottom: 2px;
 }
-</style>
+</style>

+ 9 - 20
src/pages/products/components/AddProductDialog.vue

@@ -17,7 +17,7 @@
                 class="col-12"
                 label="Nome do Produto"
                 :error-message="validationErrors.name"
-                :rules="[inputRules.required, maxLengthRule(255)]"
+                :rules="[inputRules.required, inputRules.max(255)]"
               />
 
               <DefaultInput
@@ -28,7 +28,7 @@
                 label="Descrição"
                 type="textarea"
                 :error-message="validationErrors.description"
-                :rules="[maxLengthRule(2000)]"
+                :rules="[inputRules.max(2000)]"
               />
 
               <DefaultCurrencyInput
@@ -37,7 +37,7 @@
                 class="col-4"
                 label="Valor Unitário"
                 :error-message="validationErrors.price_sale"
-                :rules="[requiredValueRule]"
+                :rules="[inputRules.requiredNumber]"
               />
 
               <DefaultInput
@@ -48,7 +48,11 @@
                 min="0"
                 type="number"
                 :error-message="validationErrors.quantity"
-                :rules="[requiredValueRule, quantityRule]"
+                :rules="[
+                  inputRules.requiredNumber,
+                  inputRules.integer,
+                  inputRules.minValue(0),
+                ]"
               />
 
               <DefaultInput
@@ -153,21 +157,6 @@ const totalValueFormatted = computed(() =>
   formatToBRLCurrency(totalValue.value),
 );
 
-const maxLengthRule = (maximum) => (value) =>
-  !value ||
-  String(value).length <= maximum ||
-  `O campo deve ter no máximo ${maximum} caracteres`;
-
-const quantityRule = (value) =>
-  (Number.isInteger(Number(value)) && Number(value) >= 0) ||
-  "A quantidade deve ser um número inteiro maior ou igual a 0";
-
-const requiredValueRule = (value) =>
-  (value !== null && value !== undefined && value !== "") ||
-  inputRules.required(null);
-
-requiredValueRule.$id = "required";
-
 const onOKClick = async () => {
   await execute(() =>
     createProduct({
@@ -178,4 +167,4 @@ const onOKClick = async () => {
     }),
   );
 };
-</script>
+</script>

+ 3 - 3
src/pages/support/components/AddEditTicketDialog.vue

@@ -45,7 +45,7 @@
                     class="col-12"
                     label="Título da Tarefa"
                     :error-message="validationErrors.title"
-                    :rules="[inputRules.required, maxLengthRule(255)]"
+                    :rules="[inputRules.required, inputRules.max(255)]"
                   />
 
                   <DefaultSelect
@@ -85,7 +85,7 @@
                     class="col-6"
                     label="Setor"
                     :error-message="validationErrors.sector"
-                    :rules="[maxLengthRule(255)]"
+                    :rules="[inputRules.max(255)]"
                   />
 
                   <UnitSelect
@@ -450,4 +450,4 @@ watch(
 );
 
 onMounted(loadReplies);
-</script>
+</script>