Преглед изворни кода

feat: :sparkles: feat(agendamento-sob-medida): finalizando fluxo frontend com dashboard, modal e i18n

Realizada a finalização do fluxo Sob Medida no frontend, incluindo criação do componente FinalSuccessModal para exibição do modal de sucesso após a criação do agendamento e integração do redirecionamento para dashboard, além da refatoração do script setup, organização do template, melhorias de CSS e implementação da internacionalização (PT, EN e ES).

fase:dev | origin:escopo
kayo henrique пре 2 недеља
родитељ
комит
7a6e79d30e

Разлика између датотеке није приказан због своје велике величине
+ 6 - 0
public/diarinho-2.svg


+ 3 - 0
public/star.svg

@@ -0,0 +1,3 @@
+<svg width="30" height="29" viewBox="0 0 30 29" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M14.8114 0L19.3696 9.23966L29.6213 10.6954L22.2142 17.9095L23.9235 28.0979L14.8114 23.2897L5.63581 28.0993L7.40711 17.911L0 10.6969L10.2517 9.24109L14.8114 0Z" fill="#C67FFA"/>
+</svg>

+ 3 - 0
public/star1.svg

@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M7.4057 0L9.6848 4.62019L14.8128 5.34808L11.1092 8.95477L11.9639 14.0493L7.4057 11.6448L2.81755 14.0493L3.70357 8.95477L0 5.34808L5.12801 4.62019L7.4057 0Z" fill="#C67FFA"/>
+</svg>

+ 3 - 0
public/star2.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M5.87014 0L7.67634 3.66296L11.7403 4.23986L8.80521 7.09945L9.48255 11.1385L5.87014 9.23183L2.23282 11.1385L2.93507 7.09945L0 4.23986L4.06396 3.66296L5.87014 0Z" fill="#C67FFA"/>
+</svg>

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

@@ -362,6 +362,32 @@
     "no_price": "to negotiate",
     "no_results": "No cleaners found for this search."
   },
+  "custom_schedule": {
+    "success_title_line1": "SOB MEDIDA",
+    "success_title_line2": "SOLICITADO!",
+    "success_subtitle_before": "Em breve ",
+    "success_subtitle_highlight": "diaristas enviarão propostas",
+    "success_subtitle_after": " para atender ao seu serviço.",
+    "close": "Fechar",
+    "star_alt": "estrela",
+    "mascot_alt": "Mascote Sob Medida"
+  },
+  "sob_medida": {
+  "page_title": "Custom Service",
+  "your_order": "Your request",
+  "quantity_service": "Service quantity",
+  "service_type": "Service type",
+  "preferred_specialty": "Preferred specialty?",
+  "description_label": "Describe request details",
+  "optional": "(optional)",
+  "description_placeholder": "Hello, I would like a dedicated professional who will...",
+  "price_range_title": "Price range for 8 hours",
+  "price_range_helper": "Select the full price range to receive housekeeper proposals.",
+  "date_and_time": "Date and time",
+  "success_message": "Custom request saved successfully!",
+  "residential": "Residential",
+  "commercial": "Commercial"
+},
   "dashboard_client": {
     "header": {
       "rating": "Rating",

+ 47 - 6
src/i18n/locales/es.json

@@ -362,6 +362,32 @@
     "no_price": "a combinar",
     "no_results": "No se encontraron limpiadores para esta búsqueda."
   },
+  "custom_schedule": {
+    "success_title_line1": "SOB MEDIDA",
+    "success_title_line2": "SOLICITADO!",
+    "success_subtitle_before": "Em breve ",
+    "success_subtitle_highlight": "diaristas enviarão propostas",
+    "success_subtitle_after": " para atender ao seu serviço.",
+    "close": "Fechar",
+    "star_alt": "estrela",
+    "mascot_alt": "Mascote Sob Medida"
+  },
+  "sob_medida": {
+    "page_title": "Servicio Personalizado",
+    "your_order": "Tu solicitud",
+    "quantity_service": "Cantidad de servicio",
+    "service_type": "Tipo de servicio",
+    "preferred_specialty": "¿Especialidad preferida?",
+    "description_label": "Describe los detalles del servicio",
+    "optional": "(opcional)",
+    "description_placeholder": "Hola, deseo un profesional dedicado que hará...",
+    "price_range_title": "Rango de precio por 8 horas",
+    "price_range_helper": "Selecciona el rango completo de precio para recibir propuestas de profesionales.",
+    "date_and_time": "Fecha y hora",
+    "success_message": "¡Solicitud personalizada guardada con éxito!",
+    "residential": "Residencial",
+    "commercial": "Comercial"
+  },
   "dashboard_client": {
     "header": {
       "rating": "Calificación",
@@ -579,7 +605,6 @@
     "8": "Día completo (hasta 8h)",
     "unknown": "Sin información"
   },
-
   "scheduling_page": {
     "title": "Agendamiento",
     "about_provider": "Sobre el profesional",
@@ -603,10 +628,26 @@
       "slot_required": "Seleccione un horario para continuar."
     },
     "service_types": {
-      "integral":      { "label": "Integral",        "hours": "hasta 8h de servicio", "description": "Ideal para limpieza con mayores demandas y espacios más amplios." },
-      "padrao":        { "label": "Estándar",        "hours": "hasta 6h de servicio", "description": "Ideal para limpiezas residenciales y comerciales con rutina de limpieza tradicional." },
-      "meio_periodo":  { "label": "Medio tiempo",    "hours": "hasta 4h de servicio", "description": "Ideal para espacios más pequeños, estudios u oficinas." },
-      "diaria_rapida": { "label": "Limpieza Rápida", "hours": "hasta 2h de servicio", "description": "Ideal para habitaciones de hotel, pequeños ambientes o servicios específicos." }
+      "integral": {
+        "label": "Integral",
+        "hours": "hasta 8h de servicio",
+        "description": "Ideal para limpieza con mayores demandas y espacios más amplios."
+      },
+      "padrao": {
+        "label": "Estándar",
+        "hours": "hasta 6h de servicio",
+        "description": "Ideal para limpiezas residenciales y comerciales con rutina de limpieza tradicional."
+      },
+      "meio_periodo": {
+        "label": "Medio tiempo",
+        "hours": "hasta 4h de servicio",
+        "description": "Ideal para espacios más pequeños, estudios u oficinas."
+      },
+      "diaria_rapida": {
+        "label": "Limpieza Rápida",
+        "hours": "hasta 2h de servicio",
+        "description": "Ideal para habitaciones de hotel, pequeños ambientes o servicios específicos."
+      }
     },
     "order_summary": {
       "title": "Resumen del pedido",
@@ -622,4 +663,4 @@
       "week_limit_error": "Límite de 2 citas por semana con el mismo profesional alcanzado."
     }
   }
-}
+}

+ 47 - 7
src/i18n/locales/pt.json

@@ -362,6 +362,32 @@
     "no_price": "a combinar",
     "no_results": "Nenhum diarista encontrado para essa busca."
   },
+  "custom_schedule": {
+    "success_title_line1": "SOB MEDIDA",
+    "success_title_line2": "SOLICITADO!",
+    "success_subtitle_before": "Em breve ",
+    "success_subtitle_highlight": "diaristas enviarão propostas",
+    "success_subtitle_after": " para atender ao seu serviço.",
+    "close": "Fechar",
+    "star_alt": "estrela",
+    "mascot_alt": "Mascote Sob Medida"
+  },
+  "sob_medida": {
+    "page_title": "Serviço Sob Medida",
+    "your_order": "Seu pedido",
+    "quantity_service": "Quantidade de serviço",
+    "service_type": "Tipo de serviço",
+    "preferred_specialty": "Especialidade preferencial?",
+    "description_label": "Descreva detalhes do pedido",
+    "optional": "(opcional)",
+    "description_placeholder": "Olá, desejo profissional dedicado que irá fazer...",
+    "price_range_title": "Faixa de preço por 8 horas",
+    "price_range_helper": "Selecione a faixa de preço integral para receber propostas de diaristas.",
+    "date_and_time": "Data e hora",
+    "success_message": "Pedido sob medida salvo com sucesso!",
+    "residential": "Residencial",
+    "commercial": "Comercial"
+  },
   "dashboard_client": {
     "header": {
       "rating": "Avaliação",
@@ -579,7 +605,6 @@
     "8": "Dia completo (até 8h)",
     "unknown": "Sem informação"
   },
-
   "scheduling_page": {
     "title": "Agendamento",
     "about_provider": "Sobre o profissional",
@@ -603,10 +628,26 @@
       "slot_required": "Selecione um horário para continuar."
     },
     "service_types": {
-      "integral":      { "label": "Integral",      "hours": "até 8h de serviço", "description": "Ideal para limpeza com demandas maiores e espaços mais amplos." },
-      "padrao":        { "label": "Padrão",         "hours": "até 6h de serviço", "description": "Ideal para limpezas residenciais e comerciais que buscam uma rotina de limpeza tradicional." },
-      "meio_periodo":  { "label": "Meio período",   "hours": "até 4h de serviço", "description": "Ideal para limpezas de espaços menores, estúdios ou escritórios." },
-      "diaria_rapida": { "label": "Diária Rápida",  "hours": "até 2h de serviço", "description": "Ideal para limpezas de quartos de hotéis, pequenos ambientes ou serviços específicos." }
+      "integral": {
+        "label": "Integral",
+        "hours": "até 8h de serviço",
+        "description": "Ideal para limpeza com demandas maiores e espaços mais amplos."
+      },
+      "padrao": {
+        "label": "Padrão",
+        "hours": "até 6h de serviço",
+        "description": "Ideal para limpezas residenciais e comerciais que buscam uma rotina de limpeza tradicional."
+      },
+      "meio_periodo": {
+        "label": "Meio período",
+        "hours": "até 4h de serviço",
+        "description": "Ideal para limpezas de espaços menores, estúdios ou escritórios."
+      },
+      "diaria_rapida": {
+        "label": "Diária Rápida",
+        "hours": "até 2h de serviço",
+        "description": "Ideal para limpezas de quartos de hotéis, pequenos ambientes ou serviços específicos."
+      }
     },
     "order_summary": {
       "title": "Resumo do pedido",
@@ -622,5 +663,4 @@
       "week_limit_error": "Limite de 2 agendamentos por semana com o mesmo profissional atingido."
     }
   }
-
-}
+}

+ 16 - 0
src/pages/dashboard/DashboardPage.vue

@@ -25,9 +25,13 @@ import DashboardNextSchedules from 'src/components/dashboard/DashboardNextSchedu
 import DashboardLastDoneSchedules from 'src/components/dashboard/DashboardLastDoneSchedules.vue';
 import DashboardFavoriteProviders from 'src/components/dashboard/DashboardFavoriteProviders.vue';
 import DashboardProvidersClose from 'src/components/dashboard/DashboardProvidersClose.vue';
+import FinalSuccesModal from '../schedules/components/FinalSuccesModal.vue';
+import { useRouter } from 'vue-router'
 import { onMounted, ref } from 'vue';
 import { dadosDashboard } from 'src/api/dashboard';
+import { useQuasar } from 'quasar';
 
+const router = useRouter()
 const headerBar = ref({});
 const summaryInfos = ref({});
 const nextSchedules = ref([]);
@@ -35,6 +39,9 @@ const lastDoneSchedules = ref([]);
 const favoriteProviders = ref([]);
 const providersClose = ref([]);
 const loading = ref(true);
+const $q = useQuasar();
+
+const showSuccessModal = ref(router.currentRoute.value.fullPath.includes('showSuccessModal') || false);
 onMounted( async () => {
   const response = await dadosDashboard();
   if(response) {
@@ -45,6 +52,15 @@ onMounted( async () => {
     favoriteProviders.value = response.favoriteProviders;
     providersClose.value = response.providersClose;
   }
+  if( showSuccessModal.value ) {
+    $q.dialog({
+       component: FinalSuccesModal   
+       })
+
+    showSuccessModal.value = false;
+  }
+
+
   loading.value = false;
 });
 </script>

+ 216 - 174
src/pages/schedules/SobMedidaPage.vue

@@ -2,98 +2,108 @@
 <template>
   <q-page class="sob-medida-page">
     <div class="page-shell">
-      <span class="page-title gradient-diarista">Serviço Sob Medida</span>
-
-     <q-card flat bordered class="figma-card compact-card">
-  <div class="card-title text-center gradient-diarista">
-    Seu pedido
-  </div>
-
-  <div class="field-label text-center gradient-diarista">
-    Quantidade de serviço
-  </div>
-
-
-<div class="quantity-stepper">
-  <q-btn
-    round
-    flat
-    icon="remove"
-    class="quantity-btn"
-    @click="decreaseQuantity"
-  />
-
-  <span class="quantity-value gradient-diarista">
-    {{ quantity }}
-  </span>
-
-  <q-btn
-    round
-    flat
-    icon="add"
-    class="quantity-btn"
-    @click="increaseQuantity"
-  />
-
-</div>
- <div class="options-grid gradient-diarista">
-  <div
-    v-for="option in addressTypesOptions"
-    :key="option.value"
-    class="option-col"
-  >
-    <q-radio
-      v-model="selectedOption"
-      :val="option.value"
-      :label="option.label"
-      color="purple"
-      dense
-    />
-  </div>
-</div>
-
- <div class="field-label text-center gradient-diarista">
-  Tipo de serviço
-</div>
-
-<div class="service-type-inline">
-  <div
-    v-for="serviceType in serviceTypes"
-    :key="serviceType.id"
-    class="option-col"
-  >
-    <q-radio
-      v-model="selectedServiceType"
-      :val="serviceType.id"
-      :label="serviceType.description"
-      color="purple"
-      dense
-    />
-  </div>
-</div>
-
-<div class="field-label text-center gradient-diarista">Especialidade preferencial?</div>
-<div class="options-grid">
-<div class="options-grid">
-  <div
-    v-for="item in specialties"
-    :key="item.id"
-    class="option-col"
-  >
-    <q-checkbox
-      v-model="selectedSpecialties"
-      :val="item.id"
-      :label="item.description"
-      color="purple"
-      dense
-    />
-  </div>
-</div>
-</div>
+      <span class="page-title gradient-diarista">
+        {{ $t('sob_medida.page_title') }}
+      </span>
+
+      <!-- CARD PEDIDO -->
+      <q-card flat bordered class="figma-card compact-card">
+        <div class="card-title text-center gradient-diarista">
+          {{ $t('sob_medida.your_order') }}
+        </div>
+
+        <!-- quantidade -->
+        <div class="field-label text-center gradient-diarista">
+          {{ $t('sob_medida.quantity_service') }}
+        </div>
+
+        <div class="quantity-stepper">
+          <q-btn
+            round
+            flat
+            icon="remove"
+            class="quantity-btn"
+            @click="decreaseQuantity"
+          />
+
+          <span class="quantity-value gradient-diarista">
+            {{ quantity }}
+          </span>
+
+          <q-btn
+            round
+            flat
+            icon="add"
+            class="quantity-btn"
+            @click="increaseQuantity"
+          />
+        </div>
+
+        <!-- endereço -->
+        <div class="options-grid gradient-diarista">
+          <div
+            v-for="option in addressTypesOptions"
+            :key="option.value"
+            class="option-col"
+          >
+            <q-radio
+              v-model="selectedOption"
+              :val="option.value"
+              :label="option.label"
+              color="purple"
+              dense
+            />
+          </div>
+        </div>
+
+        <!-- tipo serviço -->
+        <div class="field-label text-center gradient-diarista">
+          {{ $t('sob_medida.service_type') }}
+        </div>
+
+        <div class="service-type-inline">
+          <div
+            v-for="serviceType in serviceTypes"
+            :key="serviceType.id"
+            class="option-col"
+          >
+            <q-radio
+              v-model="selectedServiceType"
+              :val="serviceType.id"
+              :label="serviceType.description"
+              color="purple"
+              dense
+            />
+          </div>
+        </div>
+
+        <!-- especialidades -->
+        <div class="field-label text-center gradient-diarista">
+          {{ $t('sob_medida.preferred_specialty') }}
+        </div>
+
+        <div class="options-grid specialties-grid">
+          <div
+            v-for="item in specialties"
+            :key="item.id"
+            class="specialty-col"
+          >
+            <q-checkbox
+              v-model="selectedSpecialties"
+              :val="item.id"
+              :label="item.description"
+              color="purple"
+              dense
+            />
+          </div>
+        </div>
 
+        <!-- descrição -->
         <div class="field-label text-center gradient-diarista">
-          Descreva detalhes do pedido
-          <span class="optional">(opcional)</span>
+          {{ $t('sob_medida.description_label') }}
+          <span class="optional">
+            {{ $t('sob_medida.optional') }}
+          </span>
         </div>
 
         <q-input
@@ -104,62 +114,67 @@
           color="dark"
           input-class="text-black"
           class="description-box"
-          placeholder="Olá, desejo profissional dedicado que irá fazer..."
+          :placeholder="$t('sob_medida.description_placeholder')"
         />
       </q-card>
 
-      <!-- Faixa -->
-      <div class="section-title gradient-diarista">Faixa de preço por 8 horas</div>
-<q-card flat bordered class="figma-card compact-card">
-  <div class="range-container">
-    <!-- bolha min -->
-    <div
-      class="price-pin"
-      :style="{ left: minPosition + '%' }"
-    >
-      <span>{{ priceRange.min }}</span>
-    </div>
+      <!-- FAIXA -->
+      <div class="section-title gradient-diarista">
+        {{ $t('sob_medida.price_range_title') }}
+      </div>
+
+      <q-card flat bordered class="figma-card compact-card">
+        <div class="range-container">
+          <div
+            class="price-pin"
+            :style="{ left: minPosition + '%' }"
+          >
+            <span>{{ priceRange.min }}</span>
+          </div>
+
+          <div
+            class="price-pin"
+            :style="{ left: maxPosition + '%' }"
+          >
+            <span>{{ priceRange.max }}</span>
+          </div>
+
+          <q-range
+            v-model="priceRange"
+            :min="PRICE_LIMITS.min"
+            :max="PRICE_LIMITS.max"
+            color="secondary"
+            class="price-range"
+          />
+        </div>
 
-    <!-- bolha max -->
-    <div
-      class="price-pin"
-      :style="{ left: maxPosition + '%' }"
-    >
-      <span>{{ priceRange.max }}</span>
-    </div>
+        <div class="range-helper gradient-diarista">
+          {{ $t('sob_medida.price_range_helper') }}
+        </div>
+      </q-card>
+
+      <!-- DATA -->
+      <div class="section-title gradient-diarista">
+        {{ $t('sob_medida.date_and_time') }}
+      </div>
 
-    <q-range
-      v-model="priceRange"
-      :min="PRICE_LIMITS.min"
-      :max="PRICE_LIMITS.max"
-      color="secondary"
-      class="price-range"
-    />
-  </div>
-
-  <div class="range-helper gradient-diarista">
-    Selecione a faixa de preço integral para receber propostas de diaristas.
-  </div>
-</q-card>
-
-            <!-- Data -->
-      <div class="section-title gradient-diarista">Data e hora</div>
       <q-card flat bordered class="figma-card date-card">
         <q-date
-  v-model="selectedDate"
-  minimal
-  color="purple"
-  class="figma-date calendar-custom "
-/>
+          v-model="selectedDate"
+          minimal
+          color="purple"
+          class="figma-date calendar-custom"
+        />
       </q-card>
-
     </div>
   </q-page>
 </template>
 
 <script setup>
+// IMPORTS
 import { ref, computed, watch, onMounted } from 'vue'
 import { useQuasar } from 'quasar'
+import { useRouter } from 'vue-router'
 
 import ServiceSelectionSheet from 'src/pages/search/components/ServiceSelectionSheet.vue'
 import ServiceTimeSelectionDialog from 'src/pages/search/components/ServiceTimeSelectionDialog.vue'
@@ -168,28 +183,24 @@ import { createCustomSchedule } from 'src/api/customSchedules'
 import { getPrimaryAddress } from 'src/api/address'
 import { getPublicServiceTypes } from 'src/api/serviceTypes'
 import { getPublicSpecialties } from 'src/api/specialties'
+import {useI18n} from 'vue-i18n'
 import { userStore } from 'src/stores/user'
 import { calculateDailyPrices } from 'src/helpers/utils'
 
+// SETUP
 const $q = useQuasar()
+const router = useRouter()
 const user = userStore()
+const { t } = useI18n()
 
-const addressTypesOptions = computed(() => [
-  { label: 'Residencial', value: 'home' },
-  { label: 'Comercial', value: 'commercial' }
-])
-
-// listas vindas da API
+// REFS
 const serviceTypes = ref([])
 const specialties = ref([])
+const address = ref(null)
 
-// seleções do usuário
 const selectedServiceType = ref(null)
 const selectedSpecialties = ref([])
 const selectedOption = ref('home')
-
-// campos do formulário
-const address = ref([])
 const description = ref('')
 const selectedDate = ref(null)
 const quantity = ref(1)
@@ -204,6 +215,16 @@ const priceRange = ref({
   max: 300
 })
 
+// COMPUTED
+const addressTypesOptions = computed(() => [
+  { label: t('sob_medida.residential'), value: 'home' },
+  { label: t('sob_medida.commercial'), value: 'commercial' }
+])
+
+const providerPrices = computed(() =>
+  calculateDailyPrices(priceRange.value.max * quantity.value)
+)
+
 const minPosition = computed(() =>
   ((priceRange.value.min - PRICE_LIMITS.min) /
     (PRICE_LIMITS.max - PRICE_LIMITS.min)) * 100
@@ -214,28 +235,12 @@ const maxPosition = computed(() =>
     (PRICE_LIMITS.max - PRICE_LIMITS.min)) * 100
 )
 
-onMounted(async () => {
-  const { data } = await getPrimaryAddress(user.user.client.id, 'client')
-  address.value = data.payload
-
-  const publicServiceTypes = await getPublicServiceTypes()
-  serviceTypes.value = publicServiceTypes
-
-  const publicSpecialties = await getPublicSpecialties()
-  specialties.value = publicSpecialties
-})
-
-
-watch(selectedDate, (newDate, oldDate) => {
-  if (!newDate || newDate === oldDate) return
-  openServiceSelection()
-})
-
-function openServiceSelection () {
+// FUNCTIONS
+const openServiceSelection = () => {
   $q.dialog({
     component: ServiceSelectionSheet,
     componentProps: {
-      provider: calculateDailyPrices(priceRange.value.max * quantity.value),
+      provider: providerPrices.value,
       selectedDate: selectedDate.value
     }
   }).onOk((payload) => {
@@ -245,27 +250,22 @@ function openServiceSelection () {
   })
 }
 
-function openServiceTimeSelection (serviceType) {
+const openServiceTimeSelection = (serviceType) => {
   $q.dialog({
     component: ServiceTimeSelectionDialog,
     componentProps: {
       serviceType,
-      provider: calculateDailyPrices(priceRange.value.max * quantity.value),
+      provider: providerPrices.value,
       selectedDate: selectedDate.value
     }
   }).onOk(saveFinalOrder)
 }
 
-async function saveFinalOrder (payloadFinal) {
+const saveFinalOrder = async (payloadFinal) => {
   let [startHour, endHour] = payloadFinal.slot.split('-')
 
-  if(startHour < 10) {
-    startHour = '0' + startHour
-  }
-
-  if(endHour < 10) {
-    endHour = '0' + endHour
-  }
+  startHour = String(startHour).padStart(2, '0')
+  endHour = String(endHour).padStart(2, '0')
 
   const payload = {
     client_id: user.user.client.id,
@@ -275,7 +275,7 @@ async function saveFinalOrder (payloadFinal) {
     period_type: String(payloadFinal.serviceType.hoursCount),
     start_time: `${startHour}:00`,
     end_time: `${endHour}:00`,
-    address_type: selectedOption.value.toLowerCase(),
+    address_type: selectedOption.value,
     service_type_id: selectedServiceType.value,
     description: description.value,
     min_price: priceRange.value.min,
@@ -288,17 +288,34 @@ async function saveFinalOrder (payloadFinal) {
 
   $q.notify({
     type: 'positive',
-    message: 'Pedido sob medida salvo com sucesso!'
+    message: t('sob_medida.success_message')
   })
+
+  router.push('/#showSuccessModal')
 }
 
-function increaseQuantity () {
+const increaseQuantity = () => {
   quantity.value++
 }
 
-function decreaseQuantity () {
+const decreaseQuantity = () => {
   if (quantity.value > 1) quantity.value--
 }
+
+// WATCH
+watch(selectedDate, (newDate, oldDate) => {
+  if (!newDate || newDate === oldDate) return
+  openServiceSelection()
+})
+
+// LIFECYCLE
+onMounted(async () => {
+  const { data } = await getPrimaryAddress(user.user.client.id, 'client')
+  address.value = data.payload
+
+  serviceTypes.value = await getPublicServiceTypes()
+  specialties.value = await getPublicSpecialties()
+})
 </script>
 
 <style scoped lang="scss">
@@ -532,6 +549,31 @@ function decreaseQuantity () {
   justify-content: center;
   margin: 0 auto 16px;
 }
+
+
+.specialties-grid {
+  justify-items: center;
+  gap: 10px 18px;
+  margin: 0 auto 16px;
+}
+
+.specialty-col {
+  min-width: 0;
+  display: flex;
+  justify-content: center;
+}
+
+.specialties-grid :deep(.q-checkbox) {
+  justify-content: flex-start;
+}
+
+.specialties-grid :deep(.q-checkbox__label) {
+  color: #000 !important;
+  font-size: 13px;
+  line-height: 1.2;
+  white-space: normal;
+}
+
 /* hover individual do balão */
 .price-pin:hover {
   background: linear-gradient(180deg, #7b68ff, #5f46d8);

+ 113 - 0
src/pages/schedules/components/FinalSuccesModal.vue

@@ -0,0 +1,113 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="handleClose">
+    <q-card class="success-card bg-white">
+      <!-- fechar -->
+      <q-btn
+        flat
+        round
+        dense
+        icon="mdi-close-circle-outline"
+        color="grey-5"
+        class="absolute-top-right q-mt-sm q-mr-sm"
+        @click="handleClose"
+      />
+
+      <q-card-section class="text-center q-pt-lg q-pb-md">
+        <!-- estrelas -->
+        <div class="stars-wrapper">
+          <img src="/public/star1.svg" class="star-small" alt="estrela" />
+          <img src="/public/star.svg" class="star-big" alt="estrela" />
+          <img src="/public/star2.svg" class="star-small" alt="estrela" />
+        </div>
+
+        <!-- mascote -->
+        <img
+          src="/public/diarinho-2.svg"
+          alt="Mascote Sob Medida"
+          class="success-image"
+        />
+
+        <!-- titulo -->
+        <div class="success-title gradient-diarista q-mt-md">
+          {{ $t('custom_schedule.success_title_line1') }}<br>
+          {{ $t('custom_schedule.success_title_line2') }}
+        </div>
+
+        <!-- subtitulo -->
+        <div class="success-subtitle q-mt-md">
+          {{ $t('custom_schedule.success_subtitle_before') }}
+          <strong>{{ $t('custom_schedule.success_subtitle_highlight') }}</strong>
+          {{ $t('custom_schedule.success_subtitle_after') }}
+        </div>
+      </q-card-section>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { useDialogPluginComponent } from 'quasar'
+
+defineEmits([...useDialogPluginComponent.emits])
+
+const {
+  dialogRef,
+  onDialogOK
+} = useDialogPluginComponent()
+
+function handleClose () {
+  onDialogOK()
+}
+</script>
+
+<style scoped lang="scss">
+.success-card {
+  width: 300px;
+  border-radius: 30px;
+  padding: 14px 12px 18px;
+  background: white !important;
+}
+
+.stars-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: flex-end;
+  gap: 4px;
+  margin-top: 2px;
+  margin-bottom: 10px;
+}
+
+.star-small {
+  width: 16px;
+  height: 16px;
+  object-fit: contain;
+}
+
+.star-big {
+  width: 28px;
+  height: 28px;
+  object-fit: contain;
+}
+
+.success-image {
+  width: 100px;
+  display: block;
+  margin: 0 auto;
+}
+
+.success-title {
+  font-size: 20px;
+  font-weight: 800;
+  line-height: 1.08;
+  text-align: center;
+  margin-top: 8px;
+}
+
+.success-subtitle {
+  font-size: 14px;
+  line-height: 1.35;
+  color: #666;
+  text-align: center;
+  padding: 0 24px;
+  margin-top: 14px;
+}
+</style>

+ 6 - 12
src/pages/search/components/ServiceSelectionSheet.vue

@@ -32,9 +32,7 @@
           </div>
 
           <!-- Preço -->
-          <div class="text-body2 text-weight-bold text-text q-mx-md" style="white-space: nowrap;">
-            {{ type.price != null ? formatPrice(type.price) : $t('scheduling_page.no_price') }}
-          </div>
+          
 
           <!-- Botão agendar -->
           <q-btn
@@ -42,11 +40,10 @@
             rounded
             no-caps
             :label="$t('scheduling_page.book')"
-            :disable="type.price == null"
             color="secondary"
             size="sm"
             style="min-width: 80px;"
-            @click="onDialogOK({ serviceType: type, date: selectedDate, provider })"
+            @click="onDialogOK({ serviceType: type, date: selectedDate, provider: props.provider })"
           />
         </div>
       </q-card-section>
@@ -79,7 +76,7 @@ const serviceTypes = computed(() => [
     label: t('scheduling_page.service_types.integral.label'),
     hours: t('scheduling_page.service_types.integral.hours'),
     description: t('scheduling_page.service_types.integral.description'),
-    price: props.provider?.daily_price_8h ?? null,
+   
   },
   {
     key: 'padrao',
@@ -87,7 +84,7 @@ const serviceTypes = computed(() => [
     label: t('scheduling_page.service_types.padrao.label'),
     hours: t('scheduling_page.service_types.padrao.hours'),
     description: t('scheduling_page.service_types.padrao.description'),
-    price: props.provider?.daily_price_6h ?? null,
+    
   },
   {
     key: 'meio_periodo',
@@ -95,7 +92,7 @@ const serviceTypes = computed(() => [
     label: t('scheduling_page.service_types.meio_periodo.label'),
     hours: t('scheduling_page.service_types.meio_periodo.hours'),
     description: t('scheduling_page.service_types.meio_periodo.description'),
-    price: props.provider?.daily_price_4h ?? null,
+    
   },
   {
     key: 'diaria_rapida',
@@ -103,13 +100,10 @@ const serviceTypes = computed(() => [
     label: t('scheduling_page.service_types.diaria_rapida.label'),
     hours: t('scheduling_page.service_types.diaria_rapida.hours'),
     description: t('scheduling_page.service_types.diaria_rapida.description'),
-    price: props.provider?.daily_price_2h ?? null,
+    
   },
 ]);
 
-const formatPrice = (value) =>
-  Number(value).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
-
 const openInfo = (type) => {
   $q.dialog({
     component: ServiceTypeInfoDialog,

Неке датотеке нису приказане због велике количине промена