Kaynağa Gözat

feat: enhance proposal pricing tab with toggle for enabling/disabling options and update package card to display pricing badges

alvesantos 21 saat önce
ebeveyn
işleme
41b269be10

+ 18 - 183
src/pages/packages/components/AddEditPackageDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <div style="width: 100%; max-width: 700px">
+    <div style="width: 100%; max-width: 960px">
       <q-card class="overflow-hidden" style="width: 100%">
         <DefaultDialogHeader
           :title="props.package ? 'Editar Proposta Comercial' : 'Nova Proposta Comercial'"
@@ -65,130 +65,6 @@
                     A duração padrão das aulas é de 2 horas e pode ser alterada
                     se necessário. É possível configurar no máximo dois dias.
                   </div>
-
-                  <DefaultCurrencyInput
-                    v-model="form.contract_register_value"
-                    v-model:error="validationErrors.contract_register_value"
-                    class="col-4"
-                    label="R$ Matrícula"
-                    :error-message="validationErrors.contract_register_value"
-                  />
-
-                  <DefaultCurrencyInput
-                    v-model="form.contract_value"
-                    v-model:error="validationErrors.contract_value"
-                    class="col-4"
-                    label="R$ Total do Contrato"
-                    :error-message="validationErrors.contract_value"
-                  />
-
-                  <DefaultInput
-                    v-model="form.contrat_discount_value"
-                    v-model:error="validationErrors.contrat_discount_value"
-                    class="col-4"
-                    label="Desconto em %"
-                    min="0"
-                    type="number"
-                    :error-message="validationErrors.contrat_discount_value"
-                    :rules="[inputRules.minValue(0)]"
-                  />
-
-                  <div
-                    v-for="(material, index) in form.materials"
-                    :key="index"
-                    class="col-12"
-                  >
-                    <div class="row q-col-gutter-sm items-center">
-                      <DefaultSelect
-                        v-model="material.product_id"
-                        v-model:error="
-                          validationErrors[`materials.${index}.product_id`]
-                        "
-                        class="col"
-                        emit-value
-                        label="Material"
-                        map-options
-                        :error-message="
-                          validationErrors[`materials.${index}.product_id`]
-                        "
-                        :options="productOptions"
-                        :rules="[
-                          inputRules.whenValue(
-                            () => material.price,
-                            inputRules.required,
-                          ),
-                        ]"
-                        @update:model-value="onProductSelected(material)"
-                      />
-
-                      <DefaultInput
-                        v-model="material.quantity"
-                        v-model:error="
-                          validationErrors[`materials.${index}.quantity`]
-                        "
-                        class="col-2"
-                        label="Qtd"
-                        min="1"
-                        type="number"
-                        :error-message="
-                          validationErrors[`materials.${index}.quantity`]
-                        "
-                        :rules="[
-                          inputRules.whenValue(
-                            () => material.product_id,
-                            inputRules.requiredNumber,
-                          ),
-                          inputRules.whenValue(
-                            () => material.product_id,
-                            inputRules.integer,
-                          ),
-                          inputRules.whenValue(
-                            () => material.product_id,
-                            inputRules.minValue(1),
-                          ),
-                        ]"
-                      />
-
-                      <DefaultCurrencyInput
-                        v-model="material.price"
-                        v-model:error="
-                          validationErrors[`materials.${index}.price`]
-                        "
-                        class="col-3"
-                        label="R$ Unitário"
-                        :error-message="
-                          validationErrors[`materials.${index}.price`]
-                        "
-                        :rules="[
-                          inputRules.whenValue(
-                            () => material.product_id,
-                            inputRules.requiredNumber,
-                          ),
-                        ]"
-                      />
-
-                      <div class="col-auto">
-                        <q-btn
-                          v-if="index === form.materials.length - 1"
-                          color="primary"
-                          icon="mdi-plus"
-                          style="border-radius: 8px; height: 40px; width: 40px"
-                          unelevated
-                          @click="addMaterial"
-                        />
-
-                        <q-btn
-                          v-else
-                          color="negative"
-                          dense
-                          flat
-                          icon="mdi-delete-outline"
-                          round
-                          @click="removeMaterial(index)"
-                        />
-                      </div>
-                    </div>
-                  </div>
                 </div>
               </div>
 
@@ -198,7 +74,9 @@
               >
                 <ProposalPricingTab
                   v-model="form.pavao"
+                  v-model:enabled="form.pavao_enabled"
                   :product-options="productOptions"
+                  section-label="Pavão"
                 />
               </div>
 
@@ -208,7 +86,9 @@
               >
                 <ProposalPricingTab
                   v-model="form.irrecusavel"
+                  v-model:enabled="form.irrecusavel_enabled"
                   :product-options="productOptions"
+                  section-label="Irrecusável"
                   show-condition
                 />
               </div>
@@ -258,11 +138,9 @@ import { useScroll } from "src/composables/useScroll";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
-import DefaultCurrencyInput from "src/components/defaults/DefaultCurrencyInput.vue";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 import DefaultForm from "src/components/defaults/DefaultForm.vue";
 import DefaultInput from "src/components/defaults/DefaultInput.vue";
-import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 import ProposalPricingTab from "./ProposalPricingTab.vue";
 
 defineEmits([...useDialogPluginComponent.emits]);
@@ -310,19 +188,11 @@ const defaultPricingBlock = (withCondition = false) => ({
 const { form, getUpdatedFields, setUpdateFormAsOriginal } = useForm(
   {
     class_duration_hours: (props.package?.class_duration_minutes ?? 120) / 60,
-    contract_register_value: props.package?.contract_register_value ?? null,
-    contract_value: props.package?.contract_value ?? null,
-    contrat_discount_value: props.package?.contrat_discount_value ?? null,
     irrecusavel: defaultPricingBlock(true),
-    materials: [
-      {
-        price: null,
-        product_id: null,
-        quantity: 1,
-      },
-    ],
+    irrecusavel_enabled: false,
     name: props.package?.name ?? null,
     pavao: defaultPricingBlock(),
+    pavao_enabled: false,
     quantity_classes: props.package?.quantity_classes ?? null,
   },
   { containerRef: scrollAreaRef },
@@ -356,28 +226,6 @@ const tabs = [
   { label: "Irrecusável", name: "irrecusavel" },
 ];
 
-const addMaterial = () => {
-  form.materials.push({
-    price: null,
-    product_id: null,
-    quantity: 1,
-  });
-};
-
-const onProductSelected = (material) => {
-  const option = productOptions.value.find(
-    (item) => item.value === material.product_id,
-  );
-
-  if (option) {
-    material.price = option.price_sale;
-  }
-};
-
-const removeMaterial = (index) => {
-  form.materials.splice(index, 1);
-};
-
 const mapPricingBlockFromApi = (data, existingMaterials) => ({
   classes_discount_percentage: data.classes_discount_percentage,
   classes_included_in_course: data.classes_included_in_course,
@@ -451,13 +299,8 @@ const loadData = async () => {
   if (props.package?.id) {
     const packageData = await getPackage(props.package.id);
 
-    if (packageData.materials?.length) {
-      form.materials = packageData.materials.map((material) => ({
-        price: material.price,
-        product_id: material.product_id,
-        quantity: material.quantity,
-      }));
-    }
+    form.pavao_enabled = !!packageData.pavao;
+    form.irrecusavel_enabled = !!packageData.irrecusavel;
 
     if (packageData.pavao) {
       form.pavao = mapPricingBlockFromApi(packageData.pavao, form.pavao.materials);
@@ -472,29 +315,17 @@ const loadData = async () => {
 };
 
 const onOKClick = async () => {
-  const validMaterials = form.materials.filter(
-    (material) => material.product_id,
-  );
-
   const payload = {
     class_duration_minutes: Math.round(
       Number(form.class_duration_hours) * 60,
     ),
 
-    contract_register_value: form.contract_register_value,
-    contract_value: form.contract_value,
-    contrat_discount_value: form.contrat_discount_value,
-
-    irrecusavel: toPricingPayload(form.irrecusavel, { withCondition: true }),
-
-    materials: validMaterials.map((material) => ({
-      price: Number(material.price),
-      product_id: material.product_id,
-      quantity: Number(material.quantity),
-    })),
+    irrecusavel: form.irrecusavel_enabled
+      ? toPricingPayload(form.irrecusavel, { withCondition: true })
+      : null,
 
     name: form.name,
-    pavao: toPricingPayload(form.pavao),
+    pavao: form.pavao_enabled ? toPricingPayload(form.pavao) : null,
     quantity_classes: form.quantity_classes,
   };
 
@@ -511,10 +342,14 @@ const onOKClick = async () => {
       }
 
       // Pavão/Irrecusável têm validação cruzada entre campos (par exclusivo
-      // de checkbox) que não é seguro fatiar num diff parcial — vão inteiros.
+      // de checkbox) e um sinal explícito de "removido" (null) — não é
+      // seguro fatiar isso num diff parcial, vão sempre inteiros.
       updated.pavao = payload.pavao;
       updated.irrecusavel = payload.irrecusavel;
 
+      delete updated.pavao_enabled;
+      delete updated.irrecusavel_enabled;
+
       return updatePackage(props.package.id, updated);
     }
 

+ 11 - 5
src/pages/packages/components/PackageCard.vue

@@ -12,12 +12,18 @@
         Duração padrão: {{ (pkg.class_duration_minutes ?? 120) / 60 }}h
       </div>
 
-      <div class="column" style="gap: 2px">
-        <span class="text-body2">Contrato {{ formatToBRLCurrency(pkg.contract_value) }}</span>
-        <span class="text-body2">
-          Material {{ formatToBRLCurrency(pkg.contract_material_value) }} - Incluso
+      <div class="row q-gutter-xs">
+        <q-badge v-if="pkg.pavao" color="primary" outline>
+          Pavão {{ formatToBRLCurrency(pkg.pavao.total_value) }}
+        </q-badge>
+
+        <q-badge v-if="pkg.irrecusavel" color="secondary" outline>
+          Irrecusável {{ formatToBRLCurrency(pkg.irrecusavel.total_value) }}
+        </q-badge>
+
+        <span v-if="!pkg.pavao && !pkg.irrecusavel" class="text-caption text-grey-5">
+          Sem precificação definida
         </span>
-        <span class="text-body2">Matrícula {{ formatToBRLCurrency(pkg.contract_register_value) }}</span>
       </div>
     </q-card-section>
   </q-card>

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

@@ -1,5 +1,14 @@
 <template>
   <div class="row q-col-gutter-md">
+    <div class="col-12">
+      <q-toggle
+        v-model="enabled"
+        color="primary"
+        :label="`Esta proposta possui ${sectionLabel}`"
+      />
+    </div>
+
+    <template v-if="enabled">
     <!-- Matrícula -->
     <DefaultCurrencyInput
       v-model="modelValue.registration_value"
@@ -185,6 +194,7 @@
         type="textarea"
       />
     </template>
+    </template>
   </div>
 </template>
 
@@ -202,12 +212,17 @@ import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 const { inputRules } = useInputRules();
 
 const modelValue = defineModel({ type: Object, required: true });
+const enabled = defineModel("enabled", { type: Boolean, default: false });
 
 const props = defineProps({
   productOptions: {
     default: () => [],
     type: Array,
   },
+  sectionLabel: {
+    default: "esta modalidade",
+    type: String,
+  },
   showCondition: {
     default: false,
     type: Boolean,