|
@@ -1,5 +1,6 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <q-page class="details-page">
|
|
|
|
|
|
|
+ <q-page v-if="details" class="details-page">
|
|
|
|
|
+
|
|
|
<!-- HEADER -->
|
|
<!-- HEADER -->
|
|
|
<div class="page-header">
|
|
<div class="page-header">
|
|
|
<q-btn
|
|
<q-btn
|
|
@@ -10,33 +11,62 @@
|
|
|
class="back-btn"
|
|
class="back-btn"
|
|
|
@click="router.back()"
|
|
@click="router.back()"
|
|
|
/>
|
|
/>
|
|
|
- <div class="page-title">{{ details.title }}</div>
|
|
|
|
|
|
|
+ <div class="page-title">
|
|
|
|
|
+ {{ $t('provider.dashboard.opportunity_details.title') }}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- CLIENTE -->
|
|
<!-- CLIENTE -->
|
|
|
<div class="client-section">
|
|
<div class="client-section">
|
|
|
- <img :src="AvatarMock" class="client-avatar" />
|
|
|
|
|
- <div class="client-name">{{ details.clientName }}</div>
|
|
|
|
|
|
|
+ <img :src="details.avatar" class="client-avatar" />
|
|
|
|
|
+
|
|
|
|
|
+ <div class="client-name">
|
|
|
|
|
+ {{ details.clientName }}
|
|
|
|
|
+ <span class="rating"> {{ details.rating }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<div class="client-price">{{ details.price }}</div>
|
|
<div class="client-price">{{ details.price }}</div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="date">{{ details.date }}</div>
|
|
|
|
|
+ <div class="hour">{{ details.hour }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- INFOS -->
|
|
|
|
|
- <div class="details-info">
|
|
|
|
|
- <div>{{ details.date }}</div>
|
|
|
|
|
- <div>{{ details.hour }}</div>
|
|
|
|
|
- <div>{{ details.address }}</div>
|
|
|
|
|
- <div>{{ details.distance }}</div>
|
|
|
|
|
|
|
+ <!-- ENDEREÇO -->
|
|
|
|
|
+ <div class="address">
|
|
|
|
|
+ <q-icon name="place" size="16px" />
|
|
|
|
|
+ {{ details.address }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="distance">
|
|
|
|
|
+ {{ $t('provider.dashboard.opportunity_details.distance_text', { distance: details.distance }) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- TIPO -->
|
|
|
|
|
+ <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>{{ details.tags[0] }}</strong>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- TAGS -->
|
|
<!-- TAGS -->
|
|
|
- <div class="tags-row">
|
|
|
|
|
- <q-chip dense color="grey-3">
|
|
|
|
|
- {{ details.tags[0] }}
|
|
|
|
|
|
|
+ <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>
|
|
</q-chip>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <q-chip dense color="grey-3">
|
|
|
|
|
- {{ details.tags[1] }}
|
|
|
|
|
- </q-chip>
|
|
|
|
|
|
|
+ <!-- INFO -->
|
|
|
|
|
+ <div class="info-title">
|
|
|
|
|
+ {{ $t('provider.dashboard.opportunity_details.info_title') }}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- DESCRIÇÃO -->
|
|
<!-- DESCRIÇÃO -->
|
|
@@ -49,107 +79,91 @@
|
|
|
unelevated
|
|
unelevated
|
|
|
rounded
|
|
rounded
|
|
|
no-caps
|
|
no-caps
|
|
|
- color="secondary"
|
|
|
|
|
- :label="details.buttonLabel"
|
|
|
|
|
- class="full-width q-mt-md"
|
|
|
|
|
|
|
+ class="accept-btn"
|
|
|
|
|
+ :label="$t('provider.dashboard.opportunity_details.button_accept')"
|
|
|
@click="goToProposalFlow"
|
|
@click="goToProposalFlow"
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
<!-- ALERTA -->
|
|
<!-- ALERTA -->
|
|
|
- <q-card flat class="bottom-alert">
|
|
|
|
|
- {{ details.alertText }}
|
|
|
|
|
- </q-card>
|
|
|
|
|
|
|
+ <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>
|
|
</q-page>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { ref, onMounted } from 'vue'
|
|
import { ref, onMounted } from 'vue'
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
-
|
|
|
|
|
|
|
+import { useI18n } from 'vue-i18n'
|
|
|
import AvatarMock from 'src/assets/foto_diarista_login.svg'
|
|
import AvatarMock from 'src/assets/foto_diarista_login.svg'
|
|
|
import { getOpportunityById } from 'src/api/opportunities'
|
|
import { getOpportunityById } from 'src/api/opportunities'
|
|
|
|
|
|
|
|
-// router
|
|
|
|
|
const router = useRouter()
|
|
const router = useRouter()
|
|
|
const route = useRoute()
|
|
const route = useRoute()
|
|
|
|
|
+const { t } = useI18n()
|
|
|
|
|
|
|
|
-// state
|
|
|
|
|
const details = ref(null)
|
|
const details = ref(null)
|
|
|
-const loading = ref(false)
|
|
|
|
|
|
|
+const loading = ref(true)
|
|
|
|
|
|
|
|
-// params
|
|
|
|
|
-const opportunityId = route.params.id
|
|
|
|
|
-
|
|
|
|
|
-// helpers
|
|
|
|
|
const formatHour = (time) =>
|
|
const formatHour = (time) =>
|
|
|
time ? time.slice(0, 5).replace(':', 'h') : ''
|
|
time ? time.slice(0, 5).replace(':', 'h') : ''
|
|
|
|
|
|
|
|
-// normalize (PADRÃO EMPRESA)
|
|
|
|
|
const normalizeDetails = (item) => ({
|
|
const normalizeDetails = (item) => ({
|
|
|
- title: 'Detalhes do serviço',
|
|
|
|
|
-
|
|
|
|
|
- avatar: item.client?.user?.photo || AvatarMock,
|
|
|
|
|
|
|
+ avatar: AvatarMock,
|
|
|
|
|
|
|
|
clientName:
|
|
clientName:
|
|
|
- item.client?.user?.name || 'Cliente',
|
|
|
|
|
|
|
+ item?.schedule?.client_name ||
|
|
|
|
|
+ t('provider.dashboard.opportunity_details.client_default'),
|
|
|
|
|
|
|
|
- price: `R$${Number(
|
|
|
|
|
- item.custom_schedule?.max_price || 0
|
|
|
|
|
- ).toFixed(2)}`,
|
|
|
|
|
|
|
+ price: `R$ ${Number(item?.max_price || 0).toFixed(2)}`,
|
|
|
|
|
|
|
|
- date: new Date(
|
|
|
|
|
- item.custom_schedule?.created_at || item.created_at
|
|
|
|
|
- ).toLocaleDateString('pt-BR'),
|
|
|
|
|
|
|
+ date: item?.schedule?.date || '',
|
|
|
|
|
|
|
|
- hour: `Das ${formatHour(item.start_time)} às ${formatHour(item.end_time)}`,
|
|
|
|
|
|
|
+ hour:
|
|
|
|
|
+ item?.schedule?.start_time && item?.schedule?.end_time
|
|
|
|
|
+ ? `Das ${formatHour(item.schedule.start_time)} às ${formatHour(item.schedule.end_time)}`
|
|
|
|
|
+ : t('provider.dashboard.opportunity_details.hour_not_found'),
|
|
|
|
|
|
|
|
address:
|
|
address:
|
|
|
- item.address?.address || 'Endereço não informado',
|
|
|
|
|
|
|
+ item?.schedule?.address?.address ||
|
|
|
|
|
+ t('provider.dashboard.opportunity_details.address_not_found'),
|
|
|
|
|
|
|
|
- distance: '0 km',
|
|
|
|
|
|
|
+ distance:
|
|
|
|
|
+ item?.distance
|
|
|
|
|
+ ? `${item.distance} km`
|
|
|
|
|
+ : t('provider.dashboard.opportunity_details.distance_default'),
|
|
|
|
|
|
|
|
- tags: [
|
|
|
|
|
- item.custom_schedule?.service_type?.description,
|
|
|
|
|
- item.custom_schedule?.offers_meal
|
|
|
|
|
- ? 'Refeição no local'
|
|
|
|
|
- : null
|
|
|
|
|
- ].filter(Boolean),
|
|
|
|
|
|
|
+ tags: [item?.service_type_name].filter(Boolean),
|
|
|
|
|
|
|
|
description:
|
|
description:
|
|
|
- item.custom_schedule?.description || '',
|
|
|
|
|
-
|
|
|
|
|
- buttonLabel: 'Quero atender',
|
|
|
|
|
-
|
|
|
|
|
- alertText:
|
|
|
|
|
- 'Se seu pedido for aceito pelo cliente você receberá um aviso confirmando o agendamento e aparecerá nos seus próximos serviços.'
|
|
|
|
|
|
|
+ item?.description ||
|
|
|
|
|
+ t('provider.dashboard.opportunity_details.description_not_found')
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-// load
|
|
|
|
|
-const loadDetails = async () => {
|
|
|
|
|
- loading.value = true
|
|
|
|
|
-
|
|
|
|
|
|
|
+onMounted(async () => {
|
|
|
try {
|
|
try {
|
|
|
- const response = await getOpportunityById(opportunityId)
|
|
|
|
|
-
|
|
|
|
|
- console.log('DETAILS RESPONSE:', response)
|
|
|
|
|
-
|
|
|
|
|
- details.value = normalizeDetails(response)
|
|
|
|
|
|
|
+ const id = route.params.id
|
|
|
|
|
+ const response = await getOpportunityById(id)
|
|
|
|
|
+
|
|
|
|
|
+ if (response) {
|
|
|
|
|
+ details.value = normalizeDetails(response)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.warn('Nenhum dado retornado')
|
|
|
|
|
+ }
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Erro ao carregar detalhes:', error)
|
|
console.error('Erro ao carregar detalhes:', error)
|
|
|
- details.value = null
|
|
|
|
|
} finally {
|
|
} finally {
|
|
|
loading.value = false
|
|
loading.value = false
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
-// actions
|
|
|
|
|
const goToProposalFlow = () => {
|
|
const goToProposalFlow = () => {
|
|
|
console.log('Ir para proposta', details.value)
|
|
console.log('Ir para proposta', details.value)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-// lifecycle
|
|
|
|
|
-onMounted(loadDetails)
|
|
|
|
|
-
|
|
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|
|
@@ -159,11 +173,17 @@ onMounted(loadDetails)
|
|
|
min-height: 100vh;
|
|
min-height: 100vh;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.details-page {
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ background: #f7f7fb;
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.page-header {
|
|
.page-header {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
position: relative;
|
|
position: relative;
|
|
|
- margin-bottom: 24px;
|
|
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.back-btn {
|
|
.back-btn {
|
|
@@ -178,103 +198,133 @@ onMounted(loadDetails)
|
|
|
color: #7c5cff;
|
|
color: #7c5cff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* CLIENTE */
|
|
|
.client-section {
|
|
.client-section {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
- margin-top: 8px;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.client-avatar {
|
|
.client-avatar {
|
|
|
- width: 84px;
|
|
|
|
|
- height: 84px;
|
|
|
|
|
|
|
+ width: 90px;
|
|
|
|
|
+ height: 90px;
|
|
|
border-radius: 50%;
|
|
border-radius: 50%;
|
|
|
object-fit: cover;
|
|
object-fit: cover;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.client-name {
|
|
.client-name {
|
|
|
margin-top: 8px;
|
|
margin-top: 8px;
|
|
|
- font-size: 18px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- color: #666;
|
|
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ color: #555;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rating {
|
|
|
|
|
+ color: #ffb800;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ margin-left: 4px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.client-price {
|
|
.client-price {
|
|
|
- margin-top: 8px;
|
|
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
color: #7c5cff;
|
|
color: #7c5cff;
|
|
|
- font-size: 32px;
|
|
|
|
|
- font-weight: 700;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.details-info {
|
|
|
|
|
- margin-top: 12px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
|
|
+.price-sub {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #888;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* DATA */
|
|
|
|
|
+.date {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.hour {
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
- line-height: 1.6;
|
|
|
|
|
color: #666;
|
|
color: #666;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.distance-info {
|
|
|
|
|
- margin-top: 12px;
|
|
|
|
|
|
|
+/* ENDEREÇO */
|
|
|
|
|
+.address {
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ color: #7c5cff;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.distance {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
- color: #999;
|
|
|
|
|
|
|
+ color: #888;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.service-highlight {
|
|
|
|
|
- margin-top: 16px;
|
|
|
|
|
|
|
+/* TIPO */
|
|
|
|
|
+.service-type {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
- color: #666;
|
|
|
|
|
|
|
+ margin-top: 12px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.highlight-text {
|
|
|
|
|
|
|
+.service-type span {
|
|
|
color: #7c5cff;
|
|
color: #7c5cff;
|
|
|
- font-weight: 700;
|
|
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* TAGS */
|
|
|
.tags-row {
|
|
.tags-row {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- gap: 8px;
|
|
|
|
|
- margin: 18px 0;
|
|
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin: 16px 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tags-row .q-chip {
|
|
|
|
|
|
|
+.chip {
|
|
|
border: 1px solid #7c5cff;
|
|
border: 1px solid #7c5cff;
|
|
|
color: #7c5cff;
|
|
color: #7c5cff;
|
|
|
background: white;
|
|
background: white;
|
|
|
- font-size: 12px;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* INFO */
|
|
|
.info-title {
|
|
.info-title {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
|
|
+ font-weight: bold;
|
|
|
color: #7c5cff;
|
|
color: #7c5cff;
|
|
|
- font-size: 18px;
|
|
|
|
|
- font-weight: 700;
|
|
|
|
|
- margin-bottom: 12px;
|
|
|
|
|
|
|
+ margin-top: 10px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.description-box {
|
|
.description-box {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
- line-height: 1.6;
|
|
|
|
|
color: #666;
|
|
color: #666;
|
|
|
|
|
+ margin: 10px 0 20px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.full-width {
|
|
|
|
|
- margin-top: 20px;
|
|
|
|
|
- height: 48px;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- background: #8f6dfc !important;
|
|
|
|
|
|
|
+/* BOTÃO */
|
|
|
|
|
+.accept-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 50px;
|
|
|
|
|
+ background: linear-gradient(90deg, #7c5cff, #9f7aea);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.bottom-alert {
|
|
|
|
|
- margin-top: 18px;
|
|
|
|
|
- padding: 14px;
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- background: #dfeeff;
|
|
|
|
|
|
|
+/* ALERTA */
|
|
|
|
|
+.alert-box {
|
|
|
|
|
+ margin-top: 15px;
|
|
|
|
|
+ background: #e8f0ff;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
- line-height: 1.5;
|
|
|
|
|
- color: #5c6b8a;
|
|
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
|
|
+ color: #5c6b8a;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|