Pārlūkot izejas kodu

feat: add min price para diaria dinamico

Gustavo Mantovani 2 nedēļas atpakaļ
vecāks
revīzija
c63bebd0f5

+ 5 - 0
src/api/provider.js

@@ -4,3 +4,8 @@ export const updateProvider = async (id, data) => {
   const { data: response } = await api.put(`/provider/${id}`, data);
   return response.payload;
 };
+
+export const getProviderDailyPriceMin = async () => {
+  const { data: response } = await api.get('/provider/daily-price-min');
+  return response.payload ?? response;
+};

+ 145 - 53
src/components/login/LoginStepFivePanel.vue

@@ -1,49 +1,92 @@
 <template>
   <q-card-section class="no-padding">
-    <div class="text-text font12 fontbold q-mb-sm">{{ $t('provider.login.steps.step_5.daily_price_title') }}</div>
+    <div class="text-text font12 fontbold q-mb-sm">
+      {{ $t('provider.login.steps.step_5.daily_price_title') }}
+    </div>
+
     <DefaultCurrencyInput
       v-model="form.daily_price_8h"
-      rounded
-      no-error-icon
       class="bg-surface q-mb-sm custom-currency-input"
+      hide-bottom-space
       input-class="text-text font12 fontmedium"
+      label=""
       placeholder="R$ 0,00"
+      rounded
+      no-error-icon
       :error="!!priceError"
-      hide-bottom-space
-      label=""
+      :error-message="priceError"
+      :rules="[validateDailyPrice]"
     />
 
-    <div class="text-center text-grey-7 font10 fontmedium q-mb-md">{{ $t('provider.login.steps.step_5.daily_price_min_max') }}</div>
+    <div class="text-center text-grey-7 font10 fontmedium q-mb-md">
+      {{ dailyPriceMinMaxText }}
+    </div>
 
     <q-banner class="bg-blue-1 text-primary q-mb-lg q-pa-md bannerRound">
       <template #avatar>
-        <q-icon name="mdi-alert-outline" color="primary" />
+        <q-icon color="primary" name="mdi-alert-outline" />
       </template>
-      <span class="fontbold font12">{{ $t('provider.login.steps.step_5.dont_worry') }}</span> 
+
+      <span class="fontbold font12">
+        {{ $t('provider.login.steps.step_5.dont_worry') }}
+      </span> 
+
       <span class="font12 q-ml-xs">
         {{ $t('provider.login.steps.step_5.change_anytime') }}
       </span>
     </q-banner>
 
-    <div class="text-text text-center font12 fontmedium q-mb-sm">{{ $t('provider.login.steps.step_5.shorter_services') }}</div>
+    <div class="text-text text-center font12 fontmedium q-mb-sm">
+      {{ $t('provider.login.steps.step_5.shorter_services') }}
+    </div>
 
     <div class="row q-col-gutter-sm q-mb-lg q-mt-md">
       <div class="col-4">
-        <div class="text-text text-center fontbold font12">{{ $t('provider.login.steps.step_5.up_to_6h') }}</div>
-        <q-input :model-value="formatCurrency(form.daily_price_6h)" readonly class="bg-surface" input-class="text-text font12 fontmedium"/>
+        <div class="text-text text-center fontbold font12">
+          {{ $t('provider.login.steps.step_5.up_to_6h') }}
+        </div>
+
+        <q-input
+          :model-value="formatCurrency(form.daily_price_6h)"
+          class="bg-surface"
+          input-class="text-text font12 fontmedium"
+          readonly
+        />
       </div>
+
       <div class="col-4">
-        <div class="text-text text-center fontbold font12">{{ $t('provider.login.steps.step_5.up_to_4h') }}</div>
-        <q-input :model-value="formatCurrency(form.daily_price_4h)" readonly class="bg-surface" input-class="text-text font12 fontmedium"/>
+        <div class="text-text text-center fontbold font12">
+          {{ $t('provider.login.steps.step_5.up_to_4h') }}
+        </div>
+
+        <q-input
+          :model-value="formatCurrency(form.daily_price_4h)"
+          class="bg-surface"
+          input-class="text-text font12 fontmedium"
+          readonly
+        />
       </div>
       <div class="col-4 column">
-        <div class="text-text text-center fontbold font12">{{ $t('provider.login.steps.step_5.up_to_2h') }}</div>
-        <q-input :model-value="formatCurrency(form.daily_price_2h)" readonly class="bg-surface" input-class="text-text font12 fontmedium"/>
+        <div class="text-text text-center fontbold font12">
+          {{ $t('provider.login.steps.step_5.up_to_2h') }}
+        </div>
+
+        <q-input
+          :model-value="formatCurrency(form.daily_price_2h)"
+          class="bg-surface"
+          input-class="text-text font12 fontmedium"
+          readonly
+        />
       </div>
     </div>
 
-    <div class="text-text q-mb-xs text-center fontbold 12">{{ $t('provider.login.steps.step_5.other_services') }}</div>
-    <div class="text-grey-7 q-mb-md text-center font10">{{ $t('provider.login.steps.step_5.change_in_profile') }}</div>
+    <div class="text-text q-mb-xs text-center fontbold 12">
+      {{ $t('provider.login.steps.step_5.other_services') }}
+    </div>
+
+    <div class="text-grey-7 q-mb-md text-center font10">
+      {{ $t('provider.login.steps.step_5.change_in_profile') }}
+    </div>
 
     <div v-if="loadingServiceTypes" class="row justify-center q-mb-lg">
       <q-spinner color="primary" size="28px" />
@@ -53,33 +96,57 @@
       <div v-for="service in serviceOptions" :key="service.value" class="col-6">
         <q-checkbox
           :model-value="selectedServices.includes(service.value)"
-          :label="service.label"
-          color="primary"
           class="q-mb-md text-text font12 fontmedium"
+          color="primary"
           keep-color
+          :label="service.label"
           @update:model-value="(checked) => onServiceToggle(service.value, checked)"
         />
       </div>
     </div>
 
-    <div v-else class="text-grey-7 q-mb-lg font12 fontmedium">{{ $t('provider.login.steps.step_5.no_services') }}</div>
+    <div v-else class="text-grey-7 q-mb-lg font12 fontmedium">
+      {{ $t('provider.login.steps.step_5.no_services') }}
+    </div>
 
-    <div class="text-grey-7 text-center font12 fontmedium">{{ $t('provider.login.steps.step_5.search_visibility') }}</div>
+    <div class="text-grey-7 text-center font12 fontmedium">
+      {{ $t('provider.login.steps.step_5.search_visibility') }}
+    </div>
   </q-card-section>
 </template>
 
 <script setup>
 import { computed, onMounted, ref, watch } from 'vue';
+import { getProviderDailyPriceMin } from 'src/api/provider';
 import { getPublicServiceTypes } from 'src/api/serviceType';
 import DefaultCurrencyInput from 'src/components/defaults/DefaultCurrencyInput.vue';
 
 const form = defineModel({ type: Object, required: true });
 
+const DEFAULT_DAILY_PRICE_MIN = 100;
+const DAILY_PRICE_MAX = 500;
+
+const dailyPriceMin = ref(DEFAULT_DAILY_PRICE_MIN);
 const loadingServiceTypes = ref(false);
 const serviceOptions = ref([]);
 
-const priceError = computed(() => {
-  const value = form.value.daily_price_8h;
+const formatCurrency = (value) => {
+  if (value === null || value === undefined || Number.isNaN(Number(value))) {
+    return 'R$ 0,00';
+  }
+
+  return Number(value).toLocaleString('pt-BR', {
+    style: 'currency',
+    currency: 'BRL',
+    minimumFractionDigits: 2,
+  });
+};
+
+const dailyPriceMinMaxText = computed(() => {
+  return `Valor mínimo ${formatCurrency(dailyPriceMin.value)}. Valor máximo ${formatCurrency(DAILY_PRICE_MAX)}.`;
+});
+
+const getDailyPriceError = (value) => {
   if (value === null || value === undefined || value === '') {
     return 'Este campo é obrigatório';
   }
@@ -90,16 +157,18 @@ const priceError = computed(() => {
     return 'Valor inválido';
   }
 
-  if (numericValue < 3) {
-    return 'Valor mínimo R$ 3,00';
+  if (numericValue < dailyPriceMin.value) {
+    return `Valor mínimo ${formatCurrency(dailyPriceMin.value)}`;
   }
 
-  if (numericValue > 500) {
-    return 'Valor máximo R$ 500,00';
+  if (numericValue > DAILY_PRICE_MAX) {
+    return `Valor máximo ${formatCurrency(DAILY_PRICE_MAX)}`;
   }
 
   return '';
-});
+};
+
+const priceError = computed(() => getDailyPriceError(form.value.daily_price_8h));
 
 const selectedServices = computed(() => {
   if (!Array.isArray(form.value.services_types_ids)) {
@@ -109,16 +178,38 @@ const selectedServices = computed(() => {
   return form.value.services_types_ids;
 });
 
-const formatCurrency = (value) => {
-  if (value === null || value === undefined || Number.isNaN(Number(value))) {
-    return 'R$ 0,00';
+const loadServiceTypes = async () => {
+  loadingServiceTypes.value = true;
+
+  try {
+    const serviceTypes = await getPublicServiceTypes();
+
+    serviceOptions.value = (Array.isArray(serviceTypes) ? serviceTypes : [])
+      .filter((item) => item?.is_active !== false)
+      .map((item) => ({
+        label: item.description,
+        value: Number(item.id),
+      }))
+      .filter((item) => Number.isInteger(item.value) && item.value > 0);
+  } catch (error) {
+    serviceOptions.value = [];
+
+    console.error(error);
+  } finally {
+    loadingServiceTypes.value = false;
   }
+};
 
-  return Number(value).toLocaleString('pt-BR', {
-    style: 'currency',
-    currency: 'BRL',
-    minimumFractionDigits: 2,
-  });
+const loadDailyPriceMin = async () => {
+  try {
+    const payload = await getProviderDailyPriceMin();
+
+    dailyPriceMin.value = parseDailyPriceMin(payload);
+  } catch (error) {
+    dailyPriceMin.value = DEFAULT_DAILY_PRICE_MIN;
+
+    console.error(error);
+  }
 };
 
 const normalizeSelectedServiceIds = () => {
@@ -138,31 +229,31 @@ const onServiceToggle = (value, checked) => {
     if (!form.value.services_types_ids.includes(value)) {
       form.value.services_types_ids.push(value);
     }
+
     return;
   }
 
   form.value.services_types_ids = form.value.services_types_ids.filter((serviceId) => serviceId !== value);
 };
 
-const loadServiceTypes = async () => {
-  loadingServiceTypes.value = true;
+const parseDailyPriceMin = (payload) => {
+  const rawValue = payload?.daily_price_min
+    ?? payload?.dailyPriceMin
+    ?? payload?.min
+    ?? payload?.value
+    ?? payload;
 
-  try {
-    const serviceTypes = await getPublicServiceTypes();
+  const value = Number(rawValue);
 
-    serviceOptions.value = (Array.isArray(serviceTypes) ? serviceTypes : [])
-      .filter((item) => item?.is_active !== false)
-      .map((item) => ({
-        label: item.description,
-        value: Number(item.id),
-      }))
-      .filter((item) => Number.isInteger(item.value) && item.value > 0);
-  } catch (error) {
-    serviceOptions.value = [];
-    console.error(error);
-  } finally {
-    loadingServiceTypes.value = false;
+  if (!Number.isFinite(value) || value < 0) {
+    return DEFAULT_DAILY_PRICE_MIN;
   }
+
+  return value;
+};
+
+const validateDailyPrice = () => {
+  return getDailyPriceError(form.value.daily_price_8h) || true;
 };
 
 watch(
@@ -186,6 +277,7 @@ watch(
 
 onMounted(() => {
   loadServiceTypes();
+  loadDailyPriceMin();
 });
 </script>
 <style scoped>
@@ -195,4 +287,4 @@ onMounted(() => {
 :deep(.custom-currency-input .q-field__control) {
   border-radius: 28px;
 }
-</style>
+</style>

+ 55 - 12
src/components/profile/ProfileServiceDataDialog.vue

@@ -26,12 +26,13 @@
               input-class="text-text"
               :error="!!priceError"
               :error-message="priceError"
+              :rules="[validateDailyPrice]"
               hide-bottom-space
               label=""
             />
 
             <div class="text-text text-center q-mb-lg font10 fontmedium">
-              {{ $t('profile.service_data.price_min_max') }}
+              {{ dailyPriceMinMaxText }}
             </div>
 
             <div class="text-text text-center q-mb-sm font12 fontmedium">
@@ -145,7 +146,7 @@ import { useI18n } from 'vue-i18n';
 import { getPublicServiceTypes } from 'src/api/serviceType';
 import { getProviderServiceTypes, createProviderServiceType, deleteProviderServiceType } from 'src/api/providerServiceType';
 import { getUser } from 'src/api/user';
-import { updateProvider } from 'src/api/provider';
+import { getProviderDailyPriceMin, updateProvider } from 'src/api/provider';
 import { userStore } from 'src/stores/user';
 import DefaultCurrencyInput from 'src/components/defaults/DefaultCurrencyInput.vue';
 import LogoDiariaCampos from 'src/assets/logo_diaria_campos_login.svg';
@@ -157,6 +158,9 @@ const $q = useQuasar();
 const { t } = useI18n();
 const user = userStore();
 
+const DEFAULT_DAILY_PRICE_MIN = 100;
+const DAILY_PRICE_MAX = 500;
+
 const form = ref({
   daily_price_8h: null,
   daily_price_6h: null,
@@ -164,21 +168,13 @@ const form = ref({
   daily_price_2h: null,
 });
 
+const dailyPriceMin = ref(DEFAULT_DAILY_PRICE_MIN);
 const serviceOptions = ref([]);
 const providerServiceTypes = ref([]);
 const loadingServiceTypes = ref(false);
 const togglingService = ref({});
 const saving = ref(false);
 
-const priceError = computed(() => {
-  const value = Number(form.value.daily_price_8h);
-  if (!form.value.daily_price_8h && form.value.daily_price_8h !== 0) return '';
-  if (Number.isNaN(value)) return t('validation.rules.required');
-  if (value < 3) return t('profile.service_data.price_min_error');
-  if (value > 500) return t('profile.service_data.price_max_error');
-  return '';
-});
-
 const formatCurrency = (value) => {
   if (value === null || value === undefined || Number.isNaN(Number(value))) return 'R$ 0,00';
   return Number(value).toLocaleString('pt-BR', {
@@ -188,6 +184,43 @@ const formatCurrency = (value) => {
   });
 };
 
+const parseDailyPriceMin = (payload) => {
+  const rawValue = payload?.daily_price_min
+    ?? payload?.dailyPriceMin
+    ?? payload?.min
+    ?? payload?.value
+    ?? payload;
+
+  const value = Number(rawValue);
+
+  if (!Number.isFinite(value) || value < 0) {
+    return DEFAULT_DAILY_PRICE_MIN;
+  }
+
+  return value;
+};
+
+const dailyPriceMinMaxText = computed(() => {
+  return `Valor mínimo ${formatCurrency(dailyPriceMin.value)}. Valor máximo ${formatCurrency(DAILY_PRICE_MAX)}.`;
+});
+
+const getDailyPriceError = (value) => {
+  const numericValue = Number(value);
+
+  if (!value && value !== 0) return '';
+  if (Number.isNaN(numericValue)) return t('validation.rules.required');
+  if (numericValue < dailyPriceMin.value) return `Valor mínimo ${formatCurrency(dailyPriceMin.value)}`;
+  if (numericValue > DAILY_PRICE_MAX) return t('profile.service_data.price_max_error');
+
+  return '';
+};
+
+const priceError = computed(() => getDailyPriceError(form.value.daily_price_8h));
+
+const validateDailyPrice = () => {
+  return getDailyPriceError(form.value.daily_price_8h) || true;
+};
+
 const isServiceSelected = (serviceTypeId) => {
   return providerServiceTypes.value.some((pst) => pst.service_type_id === serviceTypeId);
 };
@@ -221,6 +254,16 @@ const loadData = async () => {
   }
 };
 
+const loadDailyPriceMin = async () => {
+  try {
+    const payload = await getProviderDailyPriceMin();
+    dailyPriceMin.value = parseDailyPriceMin(payload);
+  } catch (error) {
+    dailyPriceMin.value = DEFAULT_DAILY_PRICE_MIN;
+    console.error(error);
+  }
+};
+
 const loadServiceTypes = async () => {
   loadingServiceTypes.value = true;
   try {
@@ -286,7 +329,7 @@ const save = async () => {
 };
 
 onMounted(async () => {
-  await Promise.all([loadData(), loadServiceTypes()]);
+  await Promise.all([loadData(), loadServiceTypes(), loadDailyPriceMin()]);
 });
 </script>