Prechádzať zdrojové kódy

feat: add validation error handling and display for ProposalPricingTab fields

alvesantos 12 hodín pred
rodič
commit
3957324610

+ 41 - 25
src/pages/packages/components/AddEditPackageDialog.vue

@@ -78,8 +78,10 @@
                   <ProposalPricingTab
                     v-model="form.pavao"
                     v-model:enabled="form.pavao_enabled"
+                    field-prefix="pavao"
                     :product-options="productOptions"
                     section-label="Pavão"
+                    :validation-errors="validationErrors"
                   />
                 </div>
 
@@ -90,9 +92,11 @@
                   <ProposalPricingTab
                     v-model="form.irrecusavel"
                     v-model:enabled="form.irrecusavel_enabled"
+                    field-prefix="irrecusavel"
                     :product-options="productOptions"
                     section-label="Irrecusável"
                     show-condition
+                    :validation-errors="validationErrors"
                   />
                 </div>
               </div>
@@ -319,37 +323,49 @@ const onOKClick = async () => {
     quantity_classes: form.quantity_classes,
   };
 
-  await execute(() => {
-    if (!props.package?.id) {
-      return createUnitPackage(payload);
-    }
+  try {
+    await execute(() => {
+      if (!props.package?.id) {
+        return createUnitPackage(payload);
+      }
 
-    const changedFields = getUpdatedFields.value;
-    const updatePayload = {};
+      const changedFields = getUpdatedFields.value;
+      const updatePayload = {};
 
-    const directKeys = ["name", "quantity_classes"];
+      const directKeys = ["name", "quantity_classes"];
 
-    for (const key of directKeys) {
-      if (key in changedFields) {
-        updatePayload[key] = payload[key];
+      for (const key of directKeys) {
+        if (key in changedFields) {
+          updatePayload[key] = payload[key];
+        }
       }
-    }
-
-    if ("class_duration_hours" in changedFields) {
-      updatePayload.class_duration_minutes =
-        payload.class_duration_minutes;
-    }
 
-    // Pavão/Irrecusável têm validação cruzada entre campos (par exclusivo de
-    // checkbox) e um sinal explícito de "removido" (null) — vão sempre inteiros.
-    updatePayload.pavao = payload.pavao;
-    updatePayload.irrecusavel = payload.irrecusavel;
+      if ("class_duration_hours" in changedFields) {
+        updatePayload.class_duration_minutes =
+          payload.class_duration_minutes;
+      }
 
-    return updateUnitPackage(
-      props.package.id,
-      updatePayload,
-    );
-  });
+      // Pavão/Irrecusável têm validação cruzada entre campos (par exclusivo de
+      // checkbox) e um sinal explícito de "removido" (null) — vão sempre inteiros.
+      updatePayload.pavao = payload.pavao;
+      updatePayload.irrecusavel = payload.irrecusavel;
+
+      return updateUnitPackage(
+        props.package.id,
+        updatePayload,
+      );
+    });
+  } finally {
+    // Se o erro veio de dentro de Pavão/Irrecusável, troca pra aba certa —
+    // senão a mensagem fica escondida numa tab que não está visível.
+    const errorKeys = Object.keys(validationErrors);
+
+    if (errorKeys.some((key) => key.startsWith("pavao."))) {
+      currentTab.value = "pavao";
+    } else if (errorKeys.some((key) => key.startsWith("irrecusavel."))) {
+      currentTab.value = "irrecusavel";
+    }
+  }
 };
 
 onMounted(async () => {

+ 23 - 0
src/pages/packages/components/ProposalPricingTab.vue

@@ -24,6 +24,10 @@
       @update:installments="setExclusive('registration', 'installments')"
     />
 
+    <div v-if="sectionError('registration')" class="col-12 text-negative text-caption">
+      {{ sectionError('registration') }}
+    </div>
+
     <template v-if="modelValue.registration_installments_allowed">
       <DefaultInput
         v-model="modelValue.registration_max_installments"
@@ -55,6 +59,10 @@
       @update:installments="setExclusive('classes', 'installments')"
     />
 
+    <div v-if="sectionError('classes')" class="col-12 text-negative text-caption">
+      {{ sectionError('classes') }}
+    </div>
+
     <template v-if="modelValue.classes_installments_allowed">
       <DefaultInput
         v-model="modelValue.classes_max_installments"
@@ -144,6 +152,10 @@
       @update:installments="setExclusive('materials', 'installments')"
     />
 
+    <div v-if="sectionError('materials')" class="col-12 text-negative text-caption">
+      {{ sectionError('materials') }}
+    </div>
+
     <template v-if="modelValue.materials_installments_allowed">
       <DefaultInput
         v-model="modelValue.materials_max_installments"
@@ -215,6 +227,10 @@ const modelValue = defineModel({ type: Object, required: true });
 const enabled = defineModel("enabled", { type: Boolean, default: false });
 
 const props = defineProps({
+  fieldPrefix: {
+    default: "",
+    type: String,
+  },
   productOptions: {
     default: () => [],
     type: Array,
@@ -227,8 +243,15 @@ const props = defineProps({
     default: false,
     type: Boolean,
   },
+  validationErrors: {
+    default: () => ({}),
+    type: Object,
+  },
 });
 
+const sectionError = (section) =>
+  props.validationErrors[`${props.fieldPrefix}.${section}_included_in_course`];
+
 const materialsTotalValue = computed(() =>
   modelValue.value.materials.reduce(
     (sum, material) =>