Преглед на файлове

feat: :sparkles: feat(agendamento-sob-medida) Foi ajustado para ficar fiel ao figma e criado um modal

Foi criado um modal que exibe a mensagem quando o prestador clica em atender, tambem foi ajustado as traduções de algumas frases, tambem feito o ajuste da rota para exibição do modal e ajuste da dashboard para a exibição

fase:dev | origin:escopo
kayo henrique преди 11 часа
родител
ревизия
dfeef087ab

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

@@ -299,6 +299,10 @@
         "alert_text": "If your request is accepted by the client, you will receive a notification confirming the booking.",
         "offers_meal": "On-site meal"
       },
+      "opportunities_dialog": {
+        "message": "If your request is accepted by the client, you will receive a confirmation notification and it will appear in your upcoming services.",
+        "close": "close"
+      },
       "favorites": {
         "title": "Your favorites",
         "view_schedule": "View schedule"

+ 4 - 0
src/i18n/locales/es.json

@@ -297,6 +297,10 @@
         "alert_text": "Si el cliente acepta tu solicitud, recibirás una notificación confirmando el servicio.",
         "offers_meal": "Comida en el lugar"
       },
+      "opportunities_dialog": {
+        "message": "Si su solicitud es aceptada por el cliente, recibirá una notificación de confirmación y aparecerá en sus próximos servicios.",
+        "close": "cerrar"
+      },
       "favorites": {
         "title": "Tus favoritos",
         "view_schedule": "Ver agenda"

+ 4 - 0
src/i18n/locales/pt.json

@@ -311,6 +311,10 @@
         "alert_text": "Se seu pedido for aceito pelo cliente você receberá um aviso confirmando o agendamento.",
         "offers_meal": "Refeição no local"
       },
+      "opportunities_dialog": {
+        "message": "Se seu pedido for aceito pelo cliente você receberá um aviso confirmando o agendamento e aparecerá nos seus próximos serviços.",
+        "close": "fechar"
+      },
       "favorites": {
         "title": "Seus favoritos",
         "view_schedule": "Ver agenda"

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

@@ -20,6 +20,7 @@
       />
       <DashboardNextSchedules v-if="nextSchedules?.length > 0" :data="nextSchedules" @view-details="(item) => openNextScheduleDialog(item)" />
       <DashboardOpportunities v-if="opportunities?.length > 0" :data="opportunities"/>
+      <router-view />
     </template>
   </div>
 </template>
@@ -40,6 +41,15 @@ import { onMounted, ref } from 'vue';
 import { useQuasar } from 'quasar';
 import { dadosDashboard } from 'src/api/dashboard';
 import { updateScheduleStatus } from 'src/api/schedule';
+import { useRoute, useRouter } from 'vue-router';
+
+
+
+
+
+const router = useRouter();
+const route = useRoute();
+const showDialog = ref(false);
 
 const headerBar = ref({});
 const summaryInfos = ref({});
@@ -106,6 +116,13 @@ const openRatingDialog = (schedule) => {
 
 onMounted(async () => {
   await loadDashboard();
+
+   if (route.query.dialog === 'success') {
+    showDialog.value = true;
+
+    
+    router.replace({ query: {} });
+  }
   loading.value = false;
 });
 </script>

+ 28 - 8
src/pages/opportunities/OpportunitiesPage.vue

@@ -25,7 +25,7 @@
     <div v-else class="opportunity-list">
       <q-card v-for="item in opportunities" :key="item.id" flat class="opportunity-card">
         <div class="avatar-column">
-          <img :src="item.avatar" class="client-avatar" />
+          <img :src="item.customer_photo || 'https://cdn.quasar.dev/img/avatar.png'" alt="Avatar" class="client-avatar" />
           <div class="service-type">
             {{ item.custom_schedule?.service_type.description }}
           </div>
@@ -53,11 +53,11 @@
 
         <div class="right-content">
           <div class="price">
-            {{ $t('provider.dashboard.opportunities.currency', { value: chooseprice(item.period_type, user.user.provider.daily_price_8h) }) }}
+             {{ filterCurrency(chooseprice(item.period_type, user.user.provider.daily_price_8h)) }}
           </div>
 
           <div class="service-address">
-            {{ item.custom_schedule?.address_type }}
+            {{ neighborhood(item.address?.address) }}
           </div>
 
           <div class="district">
@@ -80,7 +80,7 @@
 <script setup>
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
-import { chooseprice } from 'src/helpers/utils'
+import { chooseprice,filterCurrency } from 'src/helpers/utils'
 import { getProviderOpportunities } from 'src/api/opportunities'
 import { userStore } from 'src/stores/user'
 
@@ -121,6 +121,16 @@ const formatHour = (time) => {
   return time.slice(0, 5) 
 }
 
+
+const neighborhood = (address) => {
+  if(!address) return '';
+
+  return address
+  .split(',')[1]
+  ?.split('-')[0]
+  ?.trim();
+};
+
 const loadOpportunities = async () => {
   loading.value = true
 
@@ -143,7 +153,7 @@ onMounted(loadOpportunities)
 
 <style scoped lang="scss">
 .opportunities-page {
-  padding: 16px;
+  padding: 0;
   background: #f7f7fb;
   min-height: 100vh;
 }
@@ -153,17 +163,26 @@ onMounted(loadOpportunities)
   align-items: center;
   justify-content: center;
   position: relative;
+
+  padding: 14px 16px;
+  background: #fff;
+
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  border-radius: 0 0 16px 16px;
+
   margin-bottom: 16px;
+  z-index: 10;
 }
 
 .back-btn {
   position: absolute;
-  left: 0;
+  left: 8px;
+  color: #7c5cff;
 }
 
 .page-title {
   font-size: 16px;
-  font-weight: 700;
+  font-weight: 600;
   color: #7c5cff;
 }
 
@@ -174,7 +193,7 @@ onMounted(loadOpportunities)
   border-radius: 14px;
   background: #a78bfa;
   color: white;
-  margin-bottom: 16px;
+  margin: 16px;
 }
 
 .banner-text {
@@ -186,6 +205,7 @@ onMounted(loadOpportunities)
   display: flex;
   flex-direction: column;
   gap: 14px;
+  padding: 0 16px 16px;
 }
 
 .opportunity-card {

+ 78 - 57
src/pages/opportunities/components/OpportunityDetailsPage.vue

@@ -161,13 +161,16 @@ onMounted(async () => {
 const goToProposalFlow = async () => {
 
   await proposalOpportunity(details.value.schedule_id, user.user.provider.id)
-  router.push({ name: 'DashboardPage' })
+
+  await router.push({ name: 'DashboardPage'})
+
+   router.push({ name: 'OpportunityDialogPage' })
 }
 </script>
 
 <style scoped lang="scss">
 .details-page {
-  padding: 16px;
+  padding: 24px 16px;
   background: #f4f5f7;
   min-height: 100vh;
 }
@@ -175,19 +178,30 @@ const goToProposalFlow = async () => {
 /* HEADER */
 .page-header {
   display: flex;
+  align-items: center;      
   justify-content: center;
   position: relative;
-  margin-bottom: 16px;
+
+  height: 56px;            
+  padding: 0 16px;
+
+  background: #fff;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  border-radius: 0 0 16px 16px;
+
+  margin-bottom: 20px;
 }
 
 .back-btn {
   position: absolute;
-  left: 0;
-  top: -4px;
+  left: 8px;
+  top: 50%;
+  transform: translateY(-50%); 
+  color: #7c5cff;
 }
 
 .page-title {
-  font-size: 15px;
+  font-size: 16px;
   font-weight: 600;
   color: #7c5cff;
 }
@@ -195,19 +209,21 @@ const goToProposalFlow = async () => {
 /* CLIENTE */
 .client-section {
   text-align: center;
+  margin-top: 10px;
 }
 
 .client-avatar {
-  width: 88px;
-  height: 88px;
+  width: 90px;
+  height: 90px;
   border-radius: 50%;
   object-fit: cover;
+  margin-bottom: 10px;
 }
 
 .client-name {
-  margin-top: 6px;
-  font-size: 14px;
-  color: #666;
+  font-size: 16px;
+  font-weight: 600;
+  color: #444;
 }
 
 .rating {
@@ -217,28 +233,24 @@ const goToProposalFlow = async () => {
 }
 
 .client-price {
-  margin-top: 8px;
-  font-size: 24px;
+  margin-top: 10px;
+  font-size: 26px;
   font-weight: 700;
   color: #7c5cff;
 }
 
 /* DATA */
 .date {
-  margin-top: 6px;
-  font-size: 12px;
+  margin-top: 10px;
+  font-size: 13px;
   font-weight: 600;
   color: #555;
 }
 
 .hour {
-  font-size: 12px;
+  font-size: 13px;
   color: #777;
-}
-
-.highlight-service{
-  color: #7c5cff;
-  font-weight: 600;
+  margin-bottom: 10px;
 }
 
 /* ENDEREÇO */
@@ -247,91 +259,100 @@ const goToProposalFlow = async () => {
   display: flex;
   justify-content: center;
   align-items: center;
-  gap: 4px;
+  gap: 6px;
   color: #7c5cff;
-  font-size: 13px;
+  font-size: 14px;
   font-weight: 600;
 }
 
 .distance {
   text-align: center;
-  font-size: 11px;
-  color: #999;
+  font-size: 12px;
+  color: #888;
   margin-top: 4px;
+  margin-bottom: 14px;
 }
 
-/* ADDRESS TYPE (CHIPS) */
+/* TEXTO SOB MEDIDA */
+.service-type {
+  text-align: center;
+  font-size: 13px;
+  margin-top: 14px;
+  color: #666;
+  line-height: 1.5;
+}
+
+.service-type span {
+  color: #7c5cff;
+  font-weight: 600;
+}
+
+.highlight-service {
+  color: #7c5cff;
+  font-weight: 600;
+}
+
+/* CHIPS */
 .address-type {
   display: flex;
   justify-content: center;
   gap: 10px;
-  margin-top: 10px;
+  margin-top: 14px;
+  margin-bottom: 14px;
 }
 
 .chip-type {
   border: 1.5px solid #7c5cff;
   color: #7c5cff;
-  padding: 6px 14px;
+  padding: 6px 16px;
   border-radius: 999px;
   font-size: 12px;
   font-weight: 600;
   background: white;
-  text-transform: lowercase;
-}
-
-/* TEXTO SOB MEDIDA */
-.service-type {
-  text-align: center;
-  font-size: 12px;
-  margin-top: 12px;
-  color: #666;
 }
 
-.service-type span {
-  color: #7c5cff;
-  font-weight: 600;
-}
-
-/* INFO */
-.info-title {
+/* INFO TITLE */
+.gradient-diarista {
   text-align: center;
   font-weight: 700;
   color: #7c5cff;
-  margin-top: 14px;
-  font-size: 13px;
+  margin-top: 18px;
+  font-size: 14px;
 }
 
 /* DESCRIÇÃO */
 .description-box {
   text-align: center;
-  font-size: 12px;
+  font-size: 13px;
   color: #666;
-  margin: 10px 0 20px;
-  line-height: 1.4;
+  margin: 12px 0 24px;
+  line-height: 1.5;
+  padding: 0 10px;
 }
 
 /* BOTÃO */
 .accept-btn {
   width: 100%;
-  height: 48px;
-  border-radius: 25px;
+  height: 52px;
+  border-radius: 28px;
   background: linear-gradient(90deg, #7c5cff, #9f7aea);
   color: white;
   font-weight: 600;
-  font-size: 14px;
+  font-size: 15px;
 }
 
 /* ALERTA */
 .alert-box {
-  margin-top: 12px;
+  margin-top: 14px;
   background: #e9f0ff;
-  padding: 10px;
-  border-radius: 12px;
-  font-size: 11px;
+  padding: 12px;
+  border-radius: 14px;
+  font-size: 12px;
   text-align: center;
   color: #5c6b8a;
+
   display: flex;
-  gap: 6px;
+  gap: 8px;
   align-items: center;
   justify-content: center;
 }

+ 92 - 0
src/pages/opportunities/components/OpportunityDialogPage.vue

@@ -0,0 +1,92 @@
+<template>
+  <q-page class="dialog-page">
+
+    <div class="overlay">
+
+      <div class="alert-card">
+
+        <!-- ÍCONE -->
+        <q-icon name="warning" size="36px" class="icon" />
+
+        <!-- TEXTO -->
+        <div class="text">
+            {{ $t('provider.dashboard.opportunities_dialog.message') }}
+        </div>
+
+        <!-- BOTÃO -->
+        <q-btn
+          :label="$t('provider.dashboard.opportunities_dialog.close')"
+          no-caps
+          class="btn"
+          @click="goBack"
+        />
+
+      </div>
+
+    </div>
+
+  </q-page>
+</template>
+<script setup>
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+
+const goBack = () => {
+  router.back()
+}
+</script>
+<style scoped>
+.dialog-page {
+  position: relative;
+}
+
+/* fundo escuro */
+.overlay {
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.4);
+
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* card */
+.alert-card {
+  width: 85%;
+  max-width: 320px;
+
+  background: #fff;
+  border-radius: 22px;
+
+  padding: 24px 18px;
+  text-align: center;
+
+  box-shadow: 0 10px 30px rgba(0,0,0,0.2);
+}
+
+/* ícone */
+.icon {
+  color: #7c5cff;
+  margin-bottom: 16px;
+}
+
+/* texto */
+.text {
+  font-size: 13px;
+  color: #666;
+  line-height: 1.5;
+  margin-bottom: 20px;
+}
+
+/* botão */
+.btn {
+  width: 100%;
+  height: 44px;
+  border-radius: 24px;
+  background: linear-gradient(90deg, #7c5cff, #9f7aea);
+  color: white;
+  font-weight: 600;
+}
+</style>

+ 10 - 2
src/router/routes.js

@@ -28,6 +28,14 @@ const routes = [
           ],
         },
       },
+
+      {
+        path: "opportunity-dialog",
+        name: "OpportunityDialogPage",
+        component: () =>
+          import("src/pages/opportunities/components/OpportunityDialogPage.vue"),
+      },
+
       {
         path: "pagamentos",
         name: "PagamentosPage",
@@ -43,8 +51,6 @@ const routes = [
         name: "ProfilePage",
         component: () => import("src/pages/profile/ProfilePage.vue"),
       },
-
-      
       {
         path: "opportunities",
         name: "OpportunitiesPage",
@@ -69,6 +75,7 @@ const routes = [
       ...sub_routes,
     ],
   },
+
   {
     path: "/login",
     component: () => import("layouts/LoginLayout.vue"),
@@ -80,6 +87,7 @@ const routes = [
       },
     ],
   },
+
   {
     path: "/:catchAll(.*)*",
     component: () => import("pages/ErrorNotFound.vue"),