Эх сурвалжийг харах

feat: :sparkles: feat(agendamento-sob-medida) adicionado tradução e correções gerais

tradução  e padronização do codigo e alguns ajustes gerais

fase:dev | origin:escopo
kayo henrique 2 долоо хоног өмнө
parent
commit
cca94d3b2c

+ 10 - 2
src/i18n/locales/en.json

@@ -241,7 +241,15 @@
         "until_2h": "Quick (up to 2h)"
       },
       "opportunities": {
-        "title": "Opportunities"
+        "title": "Opportunities",
+        "banner_text": "Client requests for Diária professionals. Check the request details, apply for the service and if the client accepts, you will receive a new job.",
+        "full_day": "Full day (up to 8h)",
+        "details": "View details",
+        "empty": "No opportunities found",
+        "distance_km": "{distance} km",
+        "client_default": "Client",
+        "address_not_found": "Address not available",
+        "currency": "$ {value}"
       },
       "favorites": {
         "title": "Your favorites",
@@ -631,4 +639,4 @@
       "commercial": "Commercial"
     }
   }
-}
+}

+ 10 - 2
src/i18n/locales/es.json

@@ -241,7 +241,15 @@
         "until_2h": "Jornada Rápida (Hasta 2h)"
       },
       "opportunities": {
-        "title": "Oportunidades"
+        "title": "Oportunidades",
+        "banner_text": "Solicitudes de clientes para los profesionales de Diária. Consulta los detalles del pedido, postúlate al servicio y si el cliente te acepta recibirás una nueva jornada.",
+        "full_day": "Jornada completa (hasta 8h)",
+        "details": "Ver detalles",
+        "empty": "No se encontraron oportunidades",
+        "distance_km": "{distance} km",
+        "client_default": "Cliente",
+        "address_not_found": "Dirección no informada",
+        "currency": "R$ {value}"
       },
       "favorites": {
         "title": "Tus favoritos",
@@ -631,4 +639,4 @@
       "commercial": "Comercial"
     }
   }
-}
+}

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

@@ -243,8 +243,13 @@
       "opportunities": {
         "title": "Oportunidades",
         "banner_text": "Pedidos de clientes para os profissionais do Diário. Veja os detalhes do pedido, se candidate ao serviço e se o cliente aceitar você receberá uma nova diária.",
-        "full_day": "Integral (8h)",
-        "details": "ver detalhes"
+        "full_day": "Integral (até 8h)",
+        "details": "Ver detalhes",
+        "empty": "Nenhuma oportunidade encontrada",
+        "distance_km": "{distance} km",
+        "client_default": "Cliente",
+        "address_not_found": "Endereço não informado",
+        "currency": "R$ {value}"
       },
       "favorites": {
         "title": "Seus favoritos",

+ 77 - 85
src/pages/opportunities/OpportunitiesPage.vue

@@ -15,7 +15,7 @@
     </div>
 
     <q-card flat class="info-banner">
-      <q-icon name="mdi-auto-fix" size="22px" class="banner-icon" />
+      <q-icon name="mdi-auto-fix" size="22px" />
       <div class="banner-text">
         {{ $t('provider.dashboard.opportunities.banner_text') }}
       </div>
@@ -29,80 +29,79 @@
       v-else-if="!opportunities.length"
       class="text-center q-pa-md text-grey"
     >
+      {{ $t('provider.dashboard.opportunities.empty') }}
     </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 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" />
+          <div class="service-type">
+            {{ item.serviceType }}
+          </div>
+        </div>
+
+        <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>
+
+        <div class="right-content">
+          <div class="price">
+            {{ $t('provider.dashboard.opportunities.currency', { value: item.price }) }}
+          </div>
+
+          <div class="service-address">
+            {{ item.address }}
+          </div>
+
+          <div class="distance">
+            {{ $t('provider.dashboard.opportunities.distance_km', { distance: item.distance }) }}
+          </div>
+
+          <q-btn
+            unelevated
+            rounded
+            no-caps
+            color="secondary"
+            :label="$t('provider.dashboard.opportunities.details')"
+            class="details-btn"
+            @click="goToOpportunityDetails(item)"
+          />
+        </div>
+      </q-card>
     </div>
-  </q-card>
-</div>
   </q-page>
 </template>
-
 <script setup>
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+
 import { getProviderOpportunities } from 'src/api/opportunities'
 import { userStore } from 'src/stores/user'
 
 const router = useRouter()
+const { t } = useI18n()
 const user = userStore()
 
 const opportunities = ref([])
@@ -111,65 +110,58 @@ 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,
+    item.client?.user?.name ||
+    t('provider.dashboard.opportunities.client_default'),
 
- date: new Date(
-  item.custom_schedule?.created_at ||
-  item.created_at
-).toLocaleDateString('pt-BR'),
+  rating: item.client?.average_rating || 5.0,
 
-hour: `Das ${formatHour(
-  item.start_time
-)} às ${formatHour(
-  item.end_time
-)}`,
+  date: new Date(
+    item.custom_schedule?.created_at || item.created_at
+  ).toLocaleDateString(),
 
+  hour: `${t('common.from')} ${formatHour(item.start_time)} ${t('common.to')} ${formatHour(item.end_time)}`,
 
   address:
-    item.address?.address || 'Endereço não informado',
+    item.address?.address ||
+    t('provider.dashboard.opportunities.address_not_found'),
 
   serviceType:
-    item.custom_schedule?.service_type?.name || 'Serviço',
+    item.custom_schedule?.service_type?.descritpion ||
+    t('provider.dashboard.opportunities.client_default'),
 
   price: Number(
     item.custom_schedule?.max_price || 0
   ).toFixed(2),
 
-  distance: '0 km'
+  distance: 0
 })
 
 const goToOpportunityDetails = (item) => {
   router.push({
     name: 'OpportunityDetailsPage',
-    params: {
-      id: item.id
-    }
+    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
   }

+ 87 - 23
src/pages/opportunities/components/OpportunityDetailsPage.vue

@@ -10,38 +10,38 @@
         class="back-btn"
         @click="router.back()"
       />
-      <div class="page-title">{{ mockDetails.title }}</div>
+      <div class="page-title">{{ details.title }}</div>
     </div>
 
     <!-- CLIENTE -->
     <div class="client-section">
       <img :src="AvatarMock" class="client-avatar" />
-      <div class="client-name">{{ mockDetails.clientName }}</div>
-      <div class="client-price">{{ mockDetails.price }}</div>
+      <div class="client-name">{{ details.clientName }}</div>
+      <div class="client-price">{{ details.price }}</div>
     </div>
 
     <!-- INFOS -->
     <div class="details-info">
-      <div>{{ mockDetails.date }}</div>
-      <div>{{ mockDetails.hour }}</div>
-      <div>{{ mockDetails.address }}</div>
-      <div>{{ mockDetails.distance }}</div>
+      <div>{{ details.date }}</div>
+      <div>{{ details.hour }}</div>
+      <div>{{ details.address }}</div>
+      <div>{{ details.distance }}</div>
     </div>
 
     <!-- TAGS -->
     <div class="tags-row">
       <q-chip dense color="grey-3">
-        {{ mockDetails.tags[0] }}
+        {{ details.tags[0] }}
       </q-chip>
 
       <q-chip dense color="grey-3">
-        {{ mockDetails.tags[1] }}
+        {{ details.tags[1] }}
       </q-chip>
     </div>
 
     <!-- DESCRIÇÃO -->
     <div class="description-box">
-      {{ mockDetails.description }}
+      {{ details.description }}
     </div>
 
     <!-- BOTÃO -->
@@ -50,42 +50,106 @@
       rounded
       no-caps
       color="secondary"
-      :label="mockDetails.buttonLabel"
+      :label="details.buttonLabel"
       class="full-width q-mt-md"
       @click="goToProposalFlow"
     />
 
     <!-- ALERTA -->
     <q-card flat class="bottom-alert">
-      {{ mockDetails.alertText }}
+      {{ details.alertText }}
     </q-card>
   </q-page>
 </template>
 
 <script setup>
-import { useRouter } from 'vue-router'
+import { ref, onMounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+
 import AvatarMock from 'src/assets/foto_diarista_login.svg'
+import { getOpportunityById } from 'src/api/opportunities'
 
+// router
 const router = useRouter()
+const route = useRoute()
+
+// state
+const details = ref(null)
+const loading = ref(false)
+
+// params
+const opportunityId = route.params.id
 
-const mockDetails = {
+// helpers
+const formatHour = (time) =>
+  time ? time.slice(0, 5).replace(':', 'h') : ''
+
+// normalize (PADRÃO EMPRESA)
+const normalizeDetails = (item) => ({
   title: 'Detalhes do serviço',
-  clientName: 'Helena',
-  price: 'R$245,00',
-  date: 'Domingo, 04/10',
-  hour: 'Das 08h00 às 17h00',
-  address: 'Rua Teste, 123',
-  distance: '4,2 km da sua localização',
-  tags: ['Comercial', 'Refeição no local'],
-  description: 'Limpeza pós obra detalhada para salão comercial.',
+
+  avatar: item.client?.user?.photo || AvatarMock,
+
+  clientName:
+    item.client?.user?.name || 'Cliente',
+
+  price: `R$${Number(
+    item.custom_schedule?.max_price || 0
+  ).toFixed(2)}`,
+
+  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',
+
+  distance: '0 km',
+
+  tags: [
+    item.custom_schedule?.service_type?.description,
+    item.custom_schedule?.offers_meal
+      ? 'Refeição no local'
+      : null
+  ].filter(Boolean),
+
+  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.'
+})
+
+// load
+const loadDetails = async () => {
+  loading.value = true
+
+  try {
+    const response = await getOpportunityById(opportunityId)
+
+    console.log('DETAILS RESPONSE:', response)
+
+    details.value = normalizeDetails(response)
+  } catch (error) {
+    console.error('Erro ao carregar detalhes:', error)
+    details.value = null
+  } finally {
+    loading.value = false
+  }
 }
 
+// actions
 const goToProposalFlow = () => {
-  console.log('aqui')
+  console.log('Ir para proposta', details.value)
 }
+
+// lifecycle
+onMounted(loadDetails)
+
 </script>
 
 <style scoped lang="scss">