|
@@ -0,0 +1,328 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <q-page class="opportunities-page">
|
|
|
|
|
+ <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.opportunities.title') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <q-card flat class="info-banner">
|
|
|
|
|
+ <q-icon name="mdi-auto-fix" size="22px" class="banner-icon" />
|
|
|
|
|
+ <div class="banner-text">
|
|
|
|
|
+ {{ $t('provider.dashboard.opportunities.banner_text') }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </q-card>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="loading" class="flex flex-center q-pa-lg">
|
|
|
|
|
+ <q-spinner-dots color="secondary" size="32px" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-else-if="!opportunities.length"
|
|
|
|
|
+ class="text-center q-pa-md text-grey"
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+<div v-else class="opportunity-list">
|
|
|
|
|
+ <q-card
|
|
|
|
|
+ v-for="item in opportunities"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ flat
|
|
|
|
|
+ class="opportunity-card"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- coluna avatar -->
|
|
|
|
|
+ <div class="avatar-column">
|
|
|
|
|
+ <img :src="item.avatar" class="client-avatar" />
|
|
|
|
|
+
|
|
|
|
|
+ <div class="service-type">
|
|
|
|
|
+ {{ item.serviceType }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- conteúdo central -->
|
|
|
|
|
+ <div class="center-content">
|
|
|
|
|
+ <div class="client-name-row">
|
|
|
|
|
+ <span class="client-name">{{ item.clientName }}</span>
|
|
|
|
|
+
|
|
|
|
|
+ <span class="rating">
|
|
|
|
|
+ <q-icon name="star" size="11px" />
|
|
|
|
|
+ {{ item.rating }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="service-date">
|
|
|
|
|
+ {{ item.date }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="service-hour">
|
|
|
|
|
+ {{ item.hour }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- lado direito -->
|
|
|
|
|
+ <div class="right-content">
|
|
|
|
|
+ <div class="price">
|
|
|
|
|
+ {{ `R$${item.price}` }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="service-address">
|
|
|
|
|
+ {{ item.address }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="distance">
|
|
|
|
|
+ {{ item.distance }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <q-btn
|
|
|
|
|
+ unelevated
|
|
|
|
|
+ rounded
|
|
|
|
|
+ no-caps
|
|
|
|
|
+ color="secondary"
|
|
|
|
|
+ label="ver detalhes"
|
|
|
|
|
+ class="details-btn"
|
|
|
|
|
+ @click="goToOpportunityDetails(item)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </q-card>
|
|
|
|
|
+</div>
|
|
|
|
|
+ </q-page>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+import { getProviderOpportunities } from 'src/api/opportunities'
|
|
|
|
|
+import { userStore } from 'src/stores/user'
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+const user = userStore()
|
|
|
|
|
+
|
|
|
|
|
+const opportunities = ref([])
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const formatHour = (time) =>
|
|
|
|
|
+ time ? time.slice(0, 5).replace(':', 'h') : ''
|
|
|
|
|
+
|
|
|
|
|
+const normalizeOpportunity = (item) => ({
|
|
|
|
|
+ id: item.id,
|
|
|
|
|
+
|
|
|
|
|
+ avatar: item.client?.user?.photo || '/icons/avatar.svg',
|
|
|
|
|
+
|
|
|
|
|
+ clientName:
|
|
|
|
|
+ item.client?.user?.name || 'Cliente',
|
|
|
|
|
+
|
|
|
|
|
+ rating:
|
|
|
|
|
+ item.client?.average_rating || 5.0,
|
|
|
|
|
+
|
|
|
|
|
+ date: new Date(
|
|
|
|
|
+ item.custom_schedule?.created_at ||
|
|
|
|
|
+ item.created_at
|
|
|
|
|
+).toLocaleDateString('pt-BR'),
|
|
|
|
|
+
|
|
|
|
|
+hour: `Das ${formatHour(
|
|
|
|
|
+ item.start_time
|
|
|
|
|
+)} às ${formatHour(
|
|
|
|
|
+ item.end_time
|
|
|
|
|
+)}`,
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ address:
|
|
|
|
|
+ item.address?.address || 'Endereço não informado',
|
|
|
|
|
+
|
|
|
|
|
+ serviceType:
|
|
|
|
|
+ item.custom_schedule?.service_type?.name || 'Serviço',
|
|
|
|
|
+
|
|
|
|
|
+ price: Number(
|
|
|
|
|
+ item.custom_schedule?.max_price || 0
|
|
|
|
|
+ ).toFixed(2),
|
|
|
|
|
+
|
|
|
|
|
+ distance: '0 km'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const goToOpportunityDetails = (item) => {
|
|
|
|
|
+ router.push({
|
|
|
|
|
+ name: 'OpportunityDetailsPage',
|
|
|
|
|
+ params: {
|
|
|
|
|
+ id: item.id
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loadOpportunities = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await getProviderOpportunities(
|
|
|
|
|
+ user.user.provider.id
|
|
|
|
|
+ )
|
|
|
|
|
+ console.log('DETALHE DA OPORTUNIDADE', response)
|
|
|
|
|
+
|
|
|
|
|
+ console.log('Oportunidades recebidas:', response)
|
|
|
|
|
+ opportunities.value = (response || []).map(normalizeOpportunity)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Erro ao buscar oportunidades:', error)
|
|
|
|
|
+ opportunities.value = []
|
|
|
|
|
+
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(loadOpportunities)
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.opportunities-page {
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ background: #f7f7fb;
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.back-btn {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #7c5cff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-banner {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ border-radius: 14px;
|
|
|
|
|
+ background: #a78bfa;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.banner-text {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.opportunity-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.opportunity-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ border-radius: 18px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.avatar-column {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ width: 58px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.client-avatar {
|
|
|
|
|
+ width: 54px;
|
|
|
|
|
+ height: 54px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.service-type {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #7c5cff;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.center-content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.client-name-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.client-name {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #2d2d2d;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rating {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 2px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #ffb800;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.service-date,
|
|
|
|
|
+.service-hour {
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ line-height: 1.35;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.right-content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+ min-width: 110px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.price {
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #2d2d2d;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.service-address {
|
|
|
|
|
+ margin-top: 6px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ line-height: 1.3;
|
|
|
|
|
+ max-width: 120px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.distance {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.details-btn {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ min-height: 30px;
|
|
|
|
|
+ padding: 0 18px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|