Quellcode durchsuchen

refactor: passa os uploads e validacoes de erro para dentro das tabs ao inves de jogar para o componente pai

Gustavo Mantovani vor 1 Woche
Ursprung
Commit
e5ef50ec4c

+ 0 - 25
src/pages/unit/UnitActionPage.vue

@@ -6,10 +6,7 @@
 
     <UnitDataTab
       v-show="activeTab === 'unit_data'"
-      v-model:form="form"
       :unit-id="unitId"
-      :get-form-as-form-data="getFormAsFormData"
-      :set-update-form-as-original="setUpdateFormAsOriginal"
     />
 
     <template v-if="unitId">
@@ -33,34 +30,12 @@ import HistoryTab from "src/pages/unit/tabs/HistoryTab.vue";
 import MediasTab from "src/pages/unit/tabs/MediasTab.vue";
 import { ref, computed } from "vue";
 import { useRoute } from "vue-router";
-import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 
 const route = useRoute();
 const unitId = computed(() =>
   route.params.id ? Number(route.params.id) : null,
 );
 
-const { form, getFormAsFormData, setUpdateFormAsOriginal } =
-  useFormUpdateTracker({
-    name: null,
-    fantasy_name: "",
-    social_reason: null,
-    cnpj: null,
-    state_registration: null,
-    name_responsible: null,
-    street: null,
-    address_number: null,
-    postal_code: null,
-    neighborhood: null,
-    complement: null,
-    city_id: null,
-    state_id: null,
-    email: null,
-    secondary_email: null,
-    phone_number: null,
-    cell_number: null,
-  });
-
 const activeTab = ref("unit_data");
 
 const allTabs = [

+ 75 - 22
src/pages/unit/tabs/FinancialTab.vue

@@ -1,5 +1,7 @@
 <template>
-  <div class="q-pa-md column q-gutter-lg">
+  <div class="q-pa-md">
+    <q-scroll-area ref="scrollAreaRef" class="unit-form-tab-scroll-area">
+      <DefaultForm ref="formRef" class="column q-gutter-lg" @submit="onSave">
     <!-- Dados Bancários -->
     <div>
       <div class="text-subtitle1 text-weight-medium q-mb-sm">
@@ -9,18 +11,24 @@
       <div class="row q-col-gutter-sm">
         <DefaultInput
           v-model="form.account_holder"
+          v-model:error="validationErrors.account_holder"
+          :error-message="validationErrors.account_holder"
           label="Titular da Conta"
           class="col-12 col-md-4"
           outlined
         />
         <DefaultInput
           v-model="form.agency"
+          v-model:error="validationErrors.agency"
+          :error-message="validationErrors.agency"
           label="Agência"
           class="col-12 col-md-4"
           outlined
         />
         <DefaultInput
           v-model="form.account"
+          v-model:error="validationErrors.account"
+          :error-message="validationErrors.account"
           label="Conta"
           class="col-12 col-md-4"
           outlined
@@ -28,18 +36,24 @@
 
         <DefaultInput
           v-model="form.bank"
+          v-model:error="validationErrors.bank"
+          :error-message="validationErrors.bank"
           label="Banco"
           class="col-12 col-md-3"
           outlined
         />
         <DefaultInput
           v-model="form.pix_key"
+          v-model:error="validationErrors.pix_key"
+          :error-message="validationErrors.pix_key"
           label="Chave Pix"
           class="col-12 col-md-3"
           outlined
         />
         <DefaultSelect
           v-model="form.tax_regime"
+          v-model:error="validationErrors.tax_regime"
+          :error-message="validationErrors.tax_regime"
           label="Regime Tributário"
           :options="taxRegimeOptions"
           class="col-12 col-md-3"
@@ -49,6 +63,8 @@
         />
         <DefaultSelect
           v-model="form.account_type"
+          v-model:error="validationErrors.account_type"
+          :error-message="validationErrors.account_type"
           label="Tipo de Conta"
           :options="accountTypeOptions"
           class="col-12 col-md-3"
@@ -68,6 +84,8 @@
       <div class="row q-col-gutter-sm">
         <DefaultSelect
           v-model="form.billing_method"
+          v-model:error="validationErrors.billing_method"
+          :error-message="validationErrors.billing_method"
           label="Forma de Cobrança"
           :options="billingMethodOptions"
           class="col-12 col-md-6"
@@ -77,14 +95,19 @@
         />
         <DefaultInputDatePicker
           v-model="form.due_date"
+          v-model:error="validationErrors.due_date"
+          :error-message="validationErrors.due_date"
           label="Data de Vencimento"
           class="col-12 col-md-6"
         />
         <DefaultInput
           v-model="form.financial_email"
+          v-model:error="validationErrors.financial_email"
+          :error-message="validationErrors.financial_email"
           label="E-mail Financeiro"
           class="col-12"
           outlined
+          :rules="[inputRules.email]"
         />
       </div>
     </div>
@@ -98,31 +121,43 @@
       <div class="row q-col-gutter-sm">
         <DefaultInput
           v-model.number="form.max_freeze_count"
+          v-model:error="validationErrors.max_freeze_count"
+          :error-message="validationErrors.max_freeze_count"
           label="Máximo de Meses para Trancar"
           type="number"
           class="col-12 col-md-4"
           outlined
+          :rules="[inputRules.minValue(0)]"
         />
         <DefaultInput
           v-model.number="form.default_discount"
+          v-model:error="validationErrors.default_discount"
+          :error-message="validationErrors.default_discount"
           label="Desconto Padrão (%)"
           type="number"
           class="col-12 col-md-4"
           outlined
+          :rules="[inputRules.minValue(0), inputRules.maxValue(100)]"
         />
         <DefaultInput
           v-model.number="form.default_interest"
+          v-model:error="validationErrors.default_interest"
+          :error-message="validationErrors.default_interest"
           label="Juros Padrão (% a.m.)"
           type="number"
           class="col-12 col-md-4"
           outlined
+          :rules="[inputRules.minValue(0)]"
         />
         <DefaultInput
           v-model.number="form.default_fine"
+          v-model:error="validationErrors.default_fine"
+          :error-message="validationErrors.default_fine"
           label="Multa Padrão (%)"
           type="number"
           class="col-12 col-md-4"
           outlined
+          :rules="[inputRules.minValue(0), inputRules.maxValue(100)]"
         />
         <div class="col-12 col-md-4 flex items-center">
           <q-toggle v-model="form.charge_roi" label="Cobrar ROI" color="primary" />
@@ -139,10 +174,12 @@
       <q-btn
         label="Salvar"
         color="primary-2"
-        :loading="saving"
-        @click="onSave"
+        type="submit"
+        :loading="loading"
       />
     </div>
+      </DefaultForm>
+    </q-scroll-area>
   </div>
 </template>
 
@@ -152,6 +189,10 @@ import DefaultInput from "src/components/defaults/DefaultInput.vue";
 import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
 import { getFinancialByUnit, upsertFinancial } from "src/api/unit_financial";
+import { useSubmitHandler } from "src/composables/useSubmitHandler";
+import { useInputRules } from "src/composables/useInputRules";
+import { useScroll } from "src/composables/useScroll";
+import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 
 const props = defineProps({
   unitId: { type: Number, default: null },
@@ -198,10 +239,12 @@ const defaultForm = () => ({
   default_fine: null,
 });
 
-const form = ref(defaultForm());
-const saving = ref(false);
-
-let originalForm = defaultForm();
+const formRef = ref(null);
+const scrollAreaRef = ref(null);
+const { inputRules } = useInputRules();
+const { scrollToComponent } = useScroll();
+const { form, getUpdatedFields, resetUpdateForm, setUpdateFormAsOriginal } =
+  useFormUpdateTracker(defaultForm(), { containerRef: scrollAreaRef });
 
 async function fetchData() {
   if (!props.unitId) return;
@@ -212,34 +255,44 @@ async function loadFinancial() {
   try {
     const data = await getFinancialByUnit(props.unitId);
     if (data) {
-      Object.assign(form.value, data);
-      originalForm = { ...data };
+      Object.assign(form, data);
+      setUpdateFormAsOriginal();
     }
   } catch (e) {
     console.error(e);
   }
 }
 
+const { loading, validationErrors, execute } = useSubmitHandler({
+  formRef,
+  containerRef: scrollAreaRef,
+  scrollFn: scrollToComponent,
+  onSuccess: (saved) => {
+    Object.assign(form, saved);
+    setUpdateFormAsOriginal();
+  },
+});
+
 async function onSave() {
   if (!props.unitId) return;
-  saving.value = true;
-  try {
-    const saved = await upsertFinancial({
+  await execute(() =>
+    upsertFinancial({
       unit_id: props.unitId,
-      ...form.value,
-    });
-    Object.assign(form.value, saved);
-    originalForm = { ...saved };
-  } catch (e) {
-    console.error(e);
-  } finally {
-    saving.value = false;
-  }
+      ...getUpdatedFields.value,
+    }),
+  );
 }
 
 function resetForm() {
-  Object.assign(form.value, defaultForm(), originalForm);
+  resetUpdateForm();
 }
 
 onMounted(fetchData);
 </script>
+
+<style scoped>
+.unit-form-tab-scroll-area {
+  height: calc(100vh - 280px);
+  min-height: 320px;
+}
+</style>

+ 65 - 35
src/pages/unit/tabs/UnitDataTab.vue

@@ -1,6 +1,7 @@
 <template>
   <div class="q-pa-md">
-    <DefaultForm ref="formRef">
+    <q-scroll-area ref="scrollAreaRef" class="unit-form-tab-scroll-area">
+      <DefaultForm ref="formRef">
       <div class="column justify-center items-center q-mb-lg">
         <AvatarImageComponent ref="avatarRef" @update:file="onAvatarChange" />
 
@@ -193,7 +194,8 @@
           </div>
         </div>
       </div>
-    </DefaultForm>
+      </DefaultForm>
+    </q-scroll-area>
   </div>
 </template>
 
@@ -207,6 +209,8 @@ import StateSelect from "src/components/selects/StateSelect.vue";
 import CitySelect from "src/components/selects/CitySelect.vue";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 import { useInputRules } from "src/composables/useInputRules";
+import { useScroll } from "src/composables/useScroll";
+import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 import { createUnit, getUnit, updateUnit } from "src/api/unit";
 import masks from "src/helpers/masks";
 
@@ -215,20 +219,31 @@ const props = defineProps({
     type: Number,
     default: null,
   },
-  getFormAsFormData: {
-    type: Function,
-    required: true,
-  },
-  setUpdateFormAsOriginal: {
-    type: Function,
-    required: true,
-  },
 });
 
-const form = defineModel("form", { type: Object, required: true });
+const initialForm = {
+  name: null,
+  fantasy_name: "",
+  social_reason: null,
+  cnpj: null,
+  state_registration: null,
+  name_responsible: null,
+  street: null,
+  address_number: null,
+  postal_code: null,
+  neighborhood: null,
+  complement: null,
+  city_id: null,
+  state_id: null,
+  email: null,
+  secondary_email: null,
+  phone_number: null,
+  cell_number: null,
+};
 
 const router = useRouter();
 const formRef = ref(null);
+const scrollAreaRef = ref(null);
 const avatarRef = ref(null);
 const stateSelectRef = ref(null);
 const citySelectRef = ref(null);
@@ -237,17 +252,24 @@ const selectedState = ref(null);
 const selectedCity = ref(null);
 
 const { inputRules } = useInputRules();
+const { scrollToComponent } = useScroll();
+const {
+  form,
+  getFormAsFormData,
+  getUpdatedFieldsAsFormData,
+  setUpdateFormAsOriginal,
+} = useFormUpdateTracker(initialForm, { containerRef: scrollAreaRef });
 
 watch(selectedState, (state) => {
-  form.value.state_id = state?.value ?? null;
+  form.state_id = state?.value ?? null;
 });
 
 watch(selectedCity, (city) => {
-  form.value.city_id = city?.value ?? null;
+  form.city_id = city?.value ?? null;
 });
 
 function onAvatarChange(file) {
-  form.value.avatar = file;
+  form.avatar = file;
 }
 
 onMounted(async () => {
@@ -256,28 +278,28 @@ onMounted(async () => {
   try {
     const unit = await getUnit(props.unitId);
 
-    form.value.name = unit.name;
+    form.name = unit.name;
     const fantasyName = String(unit.fantasy_name ?? "").trim();
-    form.value.fantasy_name = ["null", "undefined"].includes(
+    form.fantasy_name = ["null", "undefined"].includes(
       fantasyName.toLowerCase(),
     )
       ? ""
       : fantasyName;
-    form.value.social_reason = unit.social_reason;
-    form.value.cnpj = unit.cnpj;
-    form.value.state_registration = unit.state_registration;
-    form.value.name_responsible = unit.name_responsible;
-    form.value.street = unit.street;
-    form.value.address_number = unit.address_number;
-    form.value.postal_code = unit.postal_code;
-    form.value.neighborhood = unit.neighborhood;
-    form.value.complement = unit.complement;
-    form.value.email = unit.email;
-    form.value.secondary_email = unit.secondary_email;
-    form.value.phone_number = unit.phone_number;
-    form.value.cell_number = unit.cell_number;
-    form.value.state_id = unit.state_id;
-    form.value.city_id = unit.city_id;
+    form.social_reason = unit.social_reason;
+    form.cnpj = unit.cnpj;
+    form.state_registration = unit.state_registration;
+    form.name_responsible = unit.name_responsible;
+    form.street = unit.street;
+    form.address_number = unit.address_number;
+    form.postal_code = unit.postal_code;
+    form.neighborhood = unit.neighborhood;
+    form.complement = unit.complement;
+    form.email = unit.email;
+    form.secondary_email = unit.secondary_email;
+    form.phone_number = unit.phone_number;
+    form.cell_number = unit.cell_number;
+    form.state_id = unit.state_id;
+    form.city_id = unit.city_id;
 
     if (unit.state_id) {
       stateSelectRef.value?.selectStateById(unit.state_id);
@@ -289,7 +311,7 @@ onMounted(async () => {
       avatarRef.value?.setImageUrl(unit.avatar_url);
     }
 
-    props.setUpdateFormAsOriginal();
+    setUpdateFormAsOriginal();
   } catch (e) {
     console.error(e);
   }
@@ -297,6 +319,8 @@ onMounted(async () => {
 
 const { loading, validationErrors, execute } = useSubmitHandler({
   formRef,
+  containerRef: scrollAreaRef,
+  scrollFn: scrollToComponent,
   onSuccess: (result) => {
     if (props.unitId) {
       router.push({ name: "FranchiseePage" });
@@ -308,11 +332,10 @@ const { loading, validationErrors, execute } = useSubmitHandler({
 
 async function onSave() {
   await execute(() => {
-    const formData = props.getFormAsFormData();
     if (props.unitId) {
-      return updateUnit(props.unitId, formData);
+      return updateUnit(props.unitId, getUpdatedFieldsAsFormData());
     }
-    return createUnit(formData);
+    return createUnit(getFormAsFormData());
   });
 }
 
@@ -320,3 +343,10 @@ function onCancel() {
   router.push({ name: "FranchiseePage" });
 }
 </script>
+
+<style scoped>
+.unit-form-tab-scroll-area {
+  height: calc(100vh - 280px);
+  min-height: 320px;
+}
+</style>