Переглянути джерело

Merge branch 'feature/diariaapp-kay-agendamentos-sob-medida-apps' of Softpar/sfp_front_vue_diarista_prestador into development

zntt 1 місяць тому
батько
коміт
06e0c3601b

+ 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"

+ 23 - 1
src/pages/dashboard/DashboardPage.vue

@@ -36,10 +36,18 @@ import DashboardOpportunities from 'src/components/dashboard/DashboardOpportunit
 import SolicitationDetailsDialog from 'src/components/dashboard/SolicitationDetailsDialog.vue';
 import NextSchedulesDetailsDialog from 'src/components/dashboard/NextSchedulesDetailsDialog.vue';
 import ScheduleRatingDialog from 'src/components/dashboard/ScheduleRatingDialog.vue';
+import OpportunityDialog from 'src/pages/opportunities/components/OpportunityDialog.vue';
 import { onMounted, ref } from 'vue';
 import { useQuasar } from 'quasar';
 import { dadosDashboard } from 'src/api/dashboard';
 import { updateScheduleStatus } from 'src/api/schedule';
+import { useRouter } from 'vue-router';
+
+
+
+
+
+const router = useRouter();
 
 const headerBar = ref({});
 const summaryInfos = ref({});
@@ -49,6 +57,8 @@ const todayServices = ref([]);
 const nextSchedules = ref([]);
 const opportunities = ref([]);
 
+const showSuccessModal = ref(router.currentRoute.value.fullPath.includes('showSuccessModal') || 'true');
+
 const $q = useQuasar();
 
 const loading = ref(true);
@@ -64,7 +74,19 @@ const loadDashboard = async () => {
     nextSchedules.value = response.nextSchedules ?? [];
     opportunities.value = response.opportunities ?? [];
   }
-};
+
+  if( showSuccessModal.value ) {
+    $q.dialog({
+       component: OpportunityDialog   
+       })
+
+    showSuccessModal.value = false;
+    router.replace({ path: router.currentRoute.value.path, query: {} });
+  }
+
+  
+}
+
 
 const handleScheduleAction = async (id, status) => {
   try {

+ 32 - 22
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,15 +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 }}
-          </div>
-
-          <div class="district">
-             {{ item.address?.district }}
+            {{ item.address?.district }}
           </div>
 
 
@@ -79,31 +75,31 @@
 </template>
 <script setup>
 import { ref, onMounted } from 'vue'
-import { useRouter } from 'vue-router'
-import { chooseprice } from 'src/helpers/utils'
+import { useQuasar } from 'quasar'
+import { chooseprice, filterCurrency } from 'src/helpers/utils'
 import { getProviderOpportunities } from 'src/api/opportunities'
 import { userStore } from 'src/stores/user'
+import OpportunityDetailsDialog from './components/OpportunityDetailsDialog.vue'
+
+const $q = useQuasar()
 
-const router = useRouter()
 const user = userStore()
 
 const opportunities = ref([])
 const loading = ref(false)
 
-
 const goToOpportunityDetails = (item) => {
 
   const id = item.custom_schedule?.id || item.id
 
-  router.push({
-    name: 'OpportunityDetailsPage',
-    params: { id },
-    state: { opportunity: item }
+  $q.dialog({
+    component: OpportunityDetailsDialog,
+    componentProps: {
+      opportunityId: id
+    }
   })
 }
 
-
-
 // formatando a data
 const formatDate = (date) => {
   if (!date) return ''
@@ -118,9 +114,10 @@ const formatDate = (date) => {
 // formatando hora para exibir só HH:mm
 const formatHour = (time) => {
   if (!time) return ''
-  return time.slice(0, 5) 
+  return time.slice(0, 5)
 }
 
+
 const loadOpportunities = async () => {
   loading.value = true
 
@@ -130,9 +127,12 @@ const loadOpportunities = async () => {
     )
 
     opportunities.value = (response || [])
+
   } catch (error) {
     console.error('Erro ao buscar oportunidades:', error)
+
     opportunities.value = []
+
   } finally {
     loading.value = false
   }
@@ -143,7 +143,7 @@ onMounted(loadOpportunities)
 
 <style scoped lang="scss">
 .opportunities-page {
-  padding: 16px;
+  padding: 0;
   background: #f7f7fb;
   min-height: 100vh;
 }
@@ -153,17 +153,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 +183,7 @@ onMounted(loadOpportunities)
   border-radius: 14px;
   background: #a78bfa;
   color: white;
-  margin-bottom: 16px;
+  margin: 16px;
 }
 
 .banner-text {
@@ -186,6 +195,7 @@ onMounted(loadOpportunities)
   display: flex;
   flex-direction: column;
   gap: 14px;
+  padding: 0 16px 16px;
 }
 
 .opportunity-card {

+ 455 - 0
src/pages/opportunities/components/OpportunityDetailsDialog.vue

@@ -0,0 +1,455 @@
+<template>
+  <q-dialog
+    ref="dialogRef"
+    maximized
+    transition-show="slide-up"
+    transition-hide="slide-down"
+  >
+    <div v-if="details" class="details-page">
+
+      <!-- HEADER -->
+      <div class="page-header">
+        <q-btn
+          flat
+          round
+          dense
+          icon="chevron_left"
+          class="back-btn"
+          @click="goBack"
+        />
+
+        <div class="page-title">
+          {{ $t('provider.dashboard.opportunity_details.title') }}
+        </div>
+      </div>
+
+      <!-- CLIENTE -->
+      <div class="client-section">
+        <img :src="details.avatar" class="client-avatar" />
+
+        <div class="client-name">
+          {{ details.schedule?.client_name }}
+          <span class="rating">
+            {{ details.schedule?.rating }}
+          </span>
+        </div>
+
+        <div class="client-price">
+          {{
+            $t('provider.dashboard.opportunities.currency', {
+              value: chooseprice(details.schedule?.period_type)
+            })
+          }}
+        </div>
+
+        <div class="date">
+          {{ formatDate(details.schedule?.date) }}
+        </div>
+
+        <div class="hour">
+          {{ formatHour(details.schedule?.start_time) }}
+          {{ formatHour(details.schedule?.end_time) }}
+        </div>
+      </div>
+
+      <!-- ENDEREÇO -->
+      <div class="address">
+        <q-icon name="place" size="16px" />
+
+        {{ details.schedule?.address?.district }}
+      </div>
+
+      <div class="distance">
+        {{
+          $t(
+            'provider.dashboard.opportunity_details.distance_text',
+            { distance: details.schedule?.distance }
+          )
+        }}
+      </div>
+
+      <!-- TAGS -->
+      <div v-if="details.tags?.length" class="tags-row">
+        <q-chip
+          v-for="(tag, index) in details.tags"
+          :key="index"
+          outline
+          class="chip"
+        >
+          {{ tag }}
+        </q-chip>
+      </div>
+
+      <!-- INFO -->
+      <div class="service-type">
+        {{ $t('provider.dashboard.opportunity_details.sob_medida') }}
+
+        <span>
+          {{
+            $t(
+              'provider.dashboard.opportunity_details.sob_medida_highlight'
+            )
+          }}
+        </span>
+
+        <br />
+
+        {{ $t('provider.dashboard.opportunity_details.para') }}
+
+        <strong class="highlight-service">
+          {{ details.service_type_name }}
+        </strong>
+      </div>
+
+      <div class="address-type">
+        <span class="chip-type">
+          {{ details.address_type }}
+        </span>
+
+        <span
+          v-if="details.offers_meal"
+          class="chip-type"
+        >
+          {{ $t('provider.dashboard.opportunity_details.offers_meal') }}
+        </span>
+      </div>
+
+      <!-- INFO TITLE -->
+      <div
+        v-if="details.description"
+        class="service-type gradient-diarista"
+      >
+        {{ $t('provider.dashboard.opportunity_details.info_title') }}
+      </div>
+
+      <!-- DESCRIÇÃO -->
+      <div class="description-box">
+        {{ details.description }}
+      </div>
+
+      <!-- BOTÃO -->
+      <q-btn
+        unelevated
+        rounded
+        no-caps
+        class="accept-btn"
+        :label="$t('provider.dashboard.opportunity_details.button_accept')"
+        @click="goToProposalFlow"
+      />
+
+      <!-- ALERTA -->
+      <div class="alert-box">
+        <q-icon
+          name="warning"
+          size="18px"
+          class="alert-icon"
+        />
+
+        <span class="alert-text">
+          {{ $t('provider.dashboard.opportunity_details.alert_text') }}
+        </span>
+      </div>
+
+    </div>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { useDialogPluginComponent } from 'quasar'
+import { userStore } from 'src/stores/user'
+import {
+  getOpportunityById,
+  proposalOpportunity
+} from 'src/api/opportunities'
+
+const props = defineProps({
+  opportunityId: {
+    type: Number,
+    required: true
+  }
+})
+
+defineEmits([
+  ...useDialogPluginComponent.emits
+])
+
+const {
+  dialogRef,
+  onDialogOK
+} = useDialogPluginComponent()
+
+const router = useRouter()
+
+const user = userStore()
+const details = ref(null)
+const loading = ref(true)
+
+const chooseprice = (periodType) => {
+  switch (periodType) {
+    case "8":
+      return user.user.provider_daily_price_8h
+    case "6":
+      return user.user.provider_daily_price_6h
+    case "4":
+      return user.user.provider_daily_price_4h
+    case "2":
+      return user.user.provider_daily_price_2h
+    default:
+      return 0
+  }
+}
+
+// formatando a data
+const formatDate = (date) => {
+  if (!date) return ''
+
+  const [day, month, year] = date.split('/')
+
+  const parsedDate = new Date(`${year}-${month}-${day}`)
+
+  const formatted = parsedDate.toLocaleDateString('pt-BR', {
+    weekday: 'long',
+    day: '2-digit',
+    month: '2-digit'
+  })
+
+  return formatted.charAt(0).toUpperCase() + formatted.slice(1)
+}
+
+// formatando hora para exibir só HH:mm
+const formatHour = (time) => {
+  if (!time) return ''
+  return time.slice(0, 5)
+}
+
+const goBack = () => {
+  onDialogOK()
+}
+
+const goToProposalFlow = async () => {
+
+  await proposalOpportunity(
+    details.value.schedule_id,
+    user.user.provider.id
+  )
+
+  await router.push({
+    name: 'DashboardPage',
+    query: { showSuccessModal: 'true' }
+  })
+
+  onDialogOK()
+}
+
+onMounted(async () => {
+  try {
+    const response = await getOpportunityById(
+      props.opportunityId
+    )
+
+    if (response) {
+      details.value = response
+    } else {
+      console.warn('Nenhum dado retornado')
+    }
+
+  } catch (error) {
+    console.error('Erro ao carregar detalhes:', error)
+
+  } finally {
+    loading.value = false
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.details-page {
+  padding: 24px 16px;
+  background: #f4f5f7;
+  min-height: 100vh;
+}
+
+/* HEADER */
+.page-header {
+  display: flex;
+  align-items: center;      
+  justify-content: center;
+  position: relative;
+
+  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: 8px;
+  top: 50%;
+  transform: translateY(-50%); 
+  color: #7c5cff;
+}
+
+.page-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #7c5cff;
+}
+
+/* CLIENTE */
+.client-section {
+  text-align: center;
+  margin-top: 10px;
+}
+
+.client-avatar {
+  width: 90px;
+  height: 90px;
+  border-radius: 50%;
+  object-fit: cover;
+  margin-bottom: 10px;
+}
+
+.client-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #444;
+}
+
+.rating {
+  color: #ffb800;
+  font-size: 12px;
+  margin-left: 4px;
+}
+
+.client-price {
+  margin-top: 10px;
+  font-size: 26px;
+  font-weight: 700;
+  color: #7c5cff;
+}
+
+/* DATA */
+.date {
+  margin-top: 10px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #555;
+}
+
+.hour {
+  font-size: 13px;
+  color: #777;
+  margin-bottom: 10px;
+}
+
+/* ENDEREÇO */
+.address {
+  margin-top: 12px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 6px;
+  color: #7c5cff;
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.distance {
+  text-align: center;
+  font-size: 12px;
+  color: #888;
+  margin-top: 4px;
+  margin-bottom: 14px;
+}
+
+/* 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: 14px;
+  margin-bottom: 14px;
+}
+
+.chip-type {
+  border: 1.5px solid #7c5cff;
+  color: #7c5cff;
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 12px;
+  font-weight: 600;
+  background: white;
+}
+
+/* INFO TITLE */
+.gradient-diarista {
+  text-align: center;
+  font-weight: 700;
+  color: #7c5cff;
+  margin-top: 18px;
+  font-size: 14px;
+}
+
+/* DESCRIÇÃO */
+.description-box {
+  text-align: center;
+  font-size: 13px;
+  color: #666;
+  margin: 12px 0 24px;
+  line-height: 1.5;
+  padding: 0 10px;
+}
+
+/* BOTÃO */
+.accept-btn {
+  width: 100%;
+  height: 52px;
+  border-radius: 28px;
+  background: linear-gradient(90deg, #7c5cff, #9f7aea);
+  color: white;
+  font-weight: 600;
+  font-size: 15px;
+}
+
+/* ALERTA */
+.alert-box {
+  margin-top: 14px;
+  background: #e9f0ff;
+  padding: 12px;
+  border-radius: 14px;
+  font-size: 12px;
+  text-align: center;
+  color: #5c6b8a;
+
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 0 - 338
src/pages/opportunities/components/OpportunityDetailsPage.vue

@@ -1,338 +0,0 @@
-<template>
-  <q-page v-if="details" class="details-page">
-
-    <!-- HEADER -->
-    <div class="page-header">
-      <q-btn flat round dense icon="chevron_left" class="back-btn" @click="router.back()" />
-      <div class="page-title">
-        {{ $t('provider.dashboard.opportunity_details.title') }}
-      </div>
-    </div>
-
-    <!-- CLIENTE -->
-    <div class="client-section">
-      <img :src="details.avatar" class="client-avatar" />
-
-      <div class="client-name">
-        {{ details.schedule?.client_name }}
-        <span class="rating"> {{ details.schedule?.rating }}</span>
-      </div>
-
-      <div class="client-price">{{ $t('provider.dashboard.opportunities.currency', { value: chooseprice(details.schedule?.period_type) }) }}</div>
-
-      <div class="date">  {{ formatDate(details.schedule?.date) }}</div>
-      <div class="hour">{{ formatHour(details.schedule?.start_time ) }} {{ formatHour(details.schedule?.end_time ) }}</div>
-    </div>
-
-    <!-- ENDEREÇO -->
-    <div class="address">
-      <q-icon name="place" size="16px" />
-      {{ details.schedule?.address?.district }}
-    </div>
-
-    <div class="distance">
-      {{ $t('provider.dashboard.opportunity_details.distance_text', { distance: details.schedule?.distance }) }}
-    </div>
-
-    <!-- TAGS -->
-    <div v-if="details.tags?.length" class="tags-row">
-      <q-chip v-for="(tag, index) in details.tags" :key="index" outline class="chip">
-        {{ tag }}
-      </q-chip>
-    </div>
-
-    <!-- INFO -->
-    <div class="service-type">
-  {{ $t('provider.dashboard.opportunity_details.sob_medida') }}
-  <span>
-    {{ $t('provider.dashboard.opportunity_details.sob_medida_highlight') }}
-  </span>
-  <br />
-  {{ $t('provider.dashboard.opportunity_details.para') }}
-  <strong class="highlight-service">
-    {{ details.service_type_name }}
-  </strong>
-</div>
-    
-
-
-    <div class="address-type">
-  <span class="chip-type">
-    {{ (details.address_type) }}
-  </span> 
-  <span v-if="details.offers_meal" class="chip-type">
-    {{ $t('provider.dashboard.opportunity_details.offers_meal') }}
-  </span>    
-</div>
-    
-
-
-     <div v-if="details.description" class="service-type gradient-diarista">
-  {{ $t('provider.dashboard.opportunity_details.info_title') }}
-  </div>
-    <!-- DESCRIÇÃO -->
-    <div class="description-box">
-      {{ details.description }}
-    </div>
-
-    <!-- BOTÃO -->
-    <q-btn unelevated rounded no-caps class="accept-btn" :label="$t('provider.dashboard.opportunity_details.button_accept')" @click="goToProposalFlow" />
-
-    <!-- ALERTA -->
-    <div class="alert-box">
-      <q-icon name="warning" size="18px" class="alert-icon" />
-      <span class="alert-text">
-        {{ $t('provider.dashboard.opportunity_details.alert_text') }}
-      </span>
-    </div>
-
-  </q-page>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-import { useRouter, useRoute } from 'vue-router'
-import { userStore } from 'src/stores/user'
-import { getOpportunityById, proposalOpportunity } from 'src/api/opportunities'
-
-
-const router = useRouter()
-const route = useRoute()
-
-const user = userStore()
-const details = ref(null)
-const loading = ref(true)
-
-const chooseprice = (periodType) => {
-  switch (periodType) {
-    case "8":
-      return user.user.provider_daily_price_8h
-    case "6":
-      return user.user.provider_daily_price_6h
-    case "4":
-      return user.user.provider_daily_price_4h
-    case "2":
-      return user.user.provider_daily_price_2h
-    default:
-      return 0
-  }
-}
-
-// formatando a data
-const formatDate = (date) => {
-  if (!date) return ''
-
-  const [day, month, year] = date.split('/')
-
-  const parsedDate = new Date(`${year}-${month}-${day}`)
-
-  const formatted = parsedDate.toLocaleDateString('pt-BR', {
-    weekday: 'long',
-    day: '2-digit',
-    month: '2-digit'
-  })
-
-  return formatted.charAt(0).toUpperCase() + formatted.slice(1)
-}
-
-// formatando hora para exibir só HH:mm
-const formatHour = (time) => {
-  if (!time) return ''
-  return time.slice(0, 5) 
-}
-
-onMounted(async () => {
-  try {
-    const id = route.params.id
-    const response = await getOpportunityById(id)
-
-    if (response) {
-      details.value = response
-    } else {
-      console.warn('Nenhum dado retornado')
-    }
-  } catch (error) {
-    console.error('Erro ao carregar detalhes:', error)
-  } finally {
-    loading.value = false
-  }
-})
-
-const goToProposalFlow = async () => {
-
-  await proposalOpportunity(details.value.schedule_id, user.user.provider.id)
-  router.push({ name: 'DashboardPage' })
-}
-</script>
-
-<style scoped lang="scss">
-.details-page {
-  padding: 16px;
-  background: #f4f5f7;
-  min-height: 100vh;
-}
-
-/* HEADER */
-.page-header {
-  display: flex;
-  justify-content: center;
-  position: relative;
-  margin-bottom: 16px;
-}
-
-.back-btn {
-  position: absolute;
-  left: 0;
-  top: -4px;
-}
-
-.page-title {
-  font-size: 15px;
-  font-weight: 600;
-  color: #7c5cff;
-}
-
-/* CLIENTE */
-.client-section {
-  text-align: center;
-}
-
-.client-avatar {
-  width: 88px;
-  height: 88px;
-  border-radius: 50%;
-  object-fit: cover;
-}
-
-.client-name {
-  margin-top: 6px;
-  font-size: 14px;
-  color: #666;
-}
-
-.rating {
-  color: #ffb800;
-  font-size: 12px;
-  margin-left: 4px;
-}
-
-.client-price {
-  margin-top: 8px;
-  font-size: 24px;
-  font-weight: 700;
-  color: #7c5cff;
-}
-
-/* DATA */
-.date {
-  margin-top: 6px;
-  font-size: 12px;
-  font-weight: 600;
-  color: #555;
-}
-
-.hour {
-  font-size: 12px;
-  color: #777;
-}
-
-.highlight-service{
-  color: #7c5cff;
-  font-weight: 600;
-}
-
-/* ENDEREÇO */
-.address {
-  margin-top: 12px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  gap: 4px;
-  color: #7c5cff;
-  font-size: 13px;
-  font-weight: 600;
-}
-
-.distance {
-  text-align: center;
-  font-size: 11px;
-  color: #999;
-  margin-top: 4px;
-}
-
-/* ADDRESS TYPE (CHIPS) */
-.address-type {
-  display: flex;
-  justify-content: center;
-  gap: 10px;
-  margin-top: 10px;
-}
-
-.chip-type {
-  border: 1.5px solid #7c5cff;
-  color: #7c5cff;
-  padding: 6px 14px;
-  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 {
-  text-align: center;
-  font-weight: 700;
-  color: #7c5cff;
-  margin-top: 14px;
-  font-size: 13px;
-}
-
-/* DESCRIÇÃO */
-.description-box {
-  text-align: center;
-  font-size: 12px;
-  color: #666;
-  margin: 10px 0 20px;
-  line-height: 1.4;
-}
-
-/* BOTÃO */
-.accept-btn {
-  width: 100%;
-  height: 48px;
-  border-radius: 25px;
-  background: linear-gradient(90deg, #7c5cff, #9f7aea);
-  color: white;
-  font-weight: 600;
-  font-size: 14px;
-}
-
-/* ALERTA */
-.alert-box {
-  margin-top: 12px;
-  background: #e9f0ff;
-  padding: 10px;
-  border-radius: 12px;
-  font-size: 11px;
-  text-align: center;
-  color: #5c6b8a;
-  display: flex;
-  gap: 6px;
-  align-items: center;
-  justify-content: center;
-}
-</style>

+ 93 - 0
src/pages/opportunities/components/OpportunityDialog.vue

@@ -0,0 +1,93 @@
+<template>
+ <q-dialog ref="dialogRef" @hide="goBack">
+
+  <q-card 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"
+    />
+
+  </q-card>
+
+</q-dialog>
+</template>
+<script setup>
+import { useDialogPluginComponent } from 'quasar'
+
+defineEmits([...useDialogPluginComponent.emits])
+
+const {
+  dialogRef,
+  onDialogOK
+} = useDialogPluginComponent()
+
+function goBack () {
+  onDialogOK()
+}
+</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>

+ 3 - 2
src/router/routes.js

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

+ 0 - 20
src/router/routes/appointments.route.js

@@ -18,24 +18,4 @@ export default [
       ]
     }
   },
-  {
-    path: 'opportunities/:id',
-    name: 'OpportunityDetailsPage',
-    component: () =>
-      import('src/pages/opportunities/components/OpportunityDetailsPage.vue'),
-    meta: {
-      title: 'Detalhes da oportunidade',
-      requireAuth: true,
-      breadcrumbs: [
-        {
-          name: 'DashboardPage',
-          title: 'ui.navigation.dashboard'
-        },
-        {
-          name: 'OpportunitiesPage',
-          title: 'provider.dashboard.opportunities.title'
-        }
-      ]
-    }
-  }
 ]