Parcourir la source

feat: :sparkles: feat(agendamento-sob-medida) Ajustes no card de propostas e implementação de ações como aceitar e recusar

Foi realizado o ajuste do card para ficar fiel ao Figma, juntamente com a implementação das ações dos botões de aceitar e recusar. O fluxo está funcionando por completo.

fase:dev | origin:escopo

Co-authored-by: Copilot <copilot@github.com>
kayo henrique il y a 1 jour
Parent
commit
4c8b434730

+ 5 - 0
src/api/customSchedules.js

@@ -8,4 +8,9 @@ export const createCustomSchedule = async (payload) => {
 export const acceptProposal = async (proposalId) => {
   const response = await api.post(`/custom-schedule/${proposalId}/accept`)
   return response.data
+}
+
+export const refuseProposal = async (proposalId) => {
+  const response = await api.post(`/custom-schedule/${proposalId}/refuse`)
+  return response.data
 }

+ 1 - 0
src/helpers/utils.js

@@ -328,6 +328,7 @@ const chooseprice = (periodType, daily_price_8h) => {
     default:
       return 0
   }
+  
 };
 
 const formatLabelByPeriodType = (type) => {

+ 22 - 16
src/i18n/locales/en.json

@@ -373,21 +373,21 @@
     "mascot_alt": "Mascote Sob Medida"
   },
   "sob_medida": {
-  "page_title": "Custom Service",
-  "your_order": "Your request",
-  "quantity_service": "Service quantity",
-  "service_type": "Service type",
-  "preferred_specialty": "Preferred specialty?",
-  "description_label": "Describe request details",
-  "optional": "(optional)",
-  "description_placeholder": "Hello, I would like a dedicated professional who will...",
-  "price_range_title": "Price range for 8 hours",
-  "price_range_helper": "Select the full price range to receive housekeeper proposals.",
-  "date_and_time": "Date and time",
-  "success_message": "Custom request saved successfully!",
-  "residential": "Residential",
-  "commercial": "Commercial"
-},
+    "page_title": "Custom Service",
+    "your_order": "Your request",
+    "quantity_service": "Service quantity",
+    "service_type": "Service type",
+    "preferred_specialty": "Preferred specialty?",
+    "description_label": "Describe request details",
+    "optional": "(optional)",
+    "description_placeholder": "Hello, I would like a dedicated professional who will...",
+    "price_range_title": "Price range for 8 hours",
+    "price_range_helper": "Select the full price range to receive housekeeper proposals.",
+    "date_and_time": "Date and time",
+    "success_message": "Custom request saved successfully!",
+    "residential": "Residential",
+    "commercial": "Commercial"
+  },
   "dashboard_client": {
     "header": {
       "rating": "Rating",
@@ -426,6 +426,12 @@
       "btn_close": "close",
       "btn_help": "Help"
     },
+    "client_proposals": {
+      "candidate": "Candidate",
+      "custom": "custom",
+      "age": "({age} years)",
+      "distance": "Distance"
+    },
     "last_schedules": {
       "title": "Last services",
       "reschedule": "reschedule",
@@ -753,4 +759,4 @@
       }
     }
   }
-}
+}

+ 7 - 1
src/i18n/locales/es.json

@@ -422,6 +422,12 @@
       "place_unknown": "N/A",
       "details": "ver detalles"
     },
+    "client_proposals": {
+      "candidate": "Candidato",
+      "custom": "a medida",
+      "age": "({edad} años)",
+      "distance": "Distancia"
+    },
     "last_schedules": {
       "title": "Últimos servicios",
       "reschedule": "reagendar",
@@ -749,4 +755,4 @@
       }
     }
   }
-}
+}

+ 3 - 1
src/i18n/locales/pt.json

@@ -433,7 +433,9 @@
     },
       "client_proposals": {
         "candidate": "Candidato",
-        "custom": "sob medida"
+        "custom": "sob medida",
+        "age": "({idade} anos)",
+        "distance": "Distância"
       },
     "last_schedules": {
       "title": "Últimos serviços",

+ 155 - 81
src/pages/dashboard/components/DashboardClientProposals.vue

@@ -1,73 +1,81 @@
 <template>
   <div class="scroll-wrapper">
-  <div class="scroll-track q-pb-md q-px-md">
+    <div class="scroll-track q-pb-md q-px-md">
 
-    <q-card
-      v-for="item in data"
-      :key="item.id"
-      class="proposal-card shadow-card"
-      :flat ="false"
+      <q-card v-for="item in data" :key="item.id" class="proposal-card shadow-card" :flat="false">
+        <div class="row no-wrap items-center">
 
-    >
-      <div class="row no-wrap items-center">
+          <q-avatar :style="avatarColors[item.id % avatarColors.length]" class="text-weight-bold q-mx-auto">
+            {{ item.provider_name?.slice(0, 1) ?? '—' }}
+          </q-avatar>
 
-        <q-avatar :style="avatarColors[item.id % avatarColors.length]" class="text-weight-bold q-mx-auto">
-                  {{ item.provider_name?.slice(0,1) ?? '—' }}
-                </q-avatar>
+          <!-- LABEL -->
+          <div class="type">
+            {{ $t('dashboard_client.client_proposals.candidate') }} <span>{{
+              $t('dashboard_client.client_proposals.custom') }}</span>
+          </div>
 
-        <div class="content">
+          <div class="content">
 
-          <!-- HEADER -->
-          <div class="row justify-between items-center text-text ">
-            <div>
-              <div class="name">
-                {{ item.provider_name }}
-              </div>
+            <!-- HEADER -->
+            <div class="row justify-between items-center text-text ">
+              <div>
+                <div class="name">
+                  {{ item.provider_name }}
+                  <span class="age">{{ $t('dashboard_client.client_proposals.age', { idade: item.idade }) }}</span>
+                </div>
 
-              <div class="rating">
-                 {{ item.avarage_rating }}
+                <span class="rating">
+                  <q-icon name="star" size="11px" />
+                  {{ item.average_rating }}
+                </span>
               </div>
+
+
             </div>
 
-            <div class="distance">
-              {{ item.distance }}
+            <!-- DATA -->
+            <div class="datetime">
+              {{ formatDate(item.date) }} <br />
+              {{ $t('dashboard_client.next_schedules.from') }} {{ formatTime(item.start_time) }} {{
+                $t('dashboard_client.next_schedules.to') }} {{ formatTime(item.end_time) }}
             </div>
-          </div>
 
-          <!-- DATA -->
-          <div class="datetime">
-            {{ formatDate(item.date) }} <br />
-            {{ $t('dashboard_client.next_schedules.from') }} {{ formatTime(item.start_time) }} {{ $t('dashboard_client.next_schedules.to') }}  {{ formatTime(item.end_time) }}
-          </div>
+            <!-- PREÇO -->
+            <div class="price">
+              {{ filterCurrency(chooseprice(item.period_type, item.daily_price_8h)) }}
 
-          <!-- PREÇO -->
-          <div class="price">
-            {{ 'R$' + chooseprice(item.period_type, item.daily_price_8h) }}
-          </div>
+              <span class="text-price-label col-6">
+                {{ formatLabelByPeriodType(item.period_type) }}
+              </span>
+
+              <span class="distance">
+                {{ $t('dashboard_client.client_proposals.distance') }} {{ distancia }}
+              </span>
+            </div>
+
+
+
+
+
+            <!-- BOTÕES -->
+            <div class="actions">
+              <q-btn label="recusar" flat class="btn-reject" @click="() => handleRefuseProposal(item.id)" />
+              <q-btn label="aceitar" class="btn-accept" @click="() => handleAcceptProposal(item.id)" />
+            </div>
 
-          <!-- LABEL -->
-           <div class="type">
-            {{ $t('dashboard_client.client_proposals.candidate') }} <span>{{ $t('dashboard_client.client_proposals.custom') }}</span>
-          </div> 
-
-          <!-- BOTÕES -->
-          <div class="actions">
-            <q-btn label="recusar" flat class="btn-reject" />
-            <q-btn label="aceitar" class="btn-accept"  @click="() => handleAcceptProposal(item.id)" />
           </div>
 
         </div>
+      </q-card>
 
-      </div>
-    </q-card>
-
+    </div>
   </div>
-</div>
 </template>
 
 <script setup>
-import { acceptProposal } from 'src/api/customSchedules'
-import { chooseprice } from 'src/helpers/utils'
+import { acceptProposal, refuseProposal } from 'src/api/customSchedules'
+import { chooseprice, formatLabelByPeriodType, filterCurrency } from 'src/helpers/utils'
 
 defineProps({
   data: {
@@ -80,14 +88,14 @@ defineProps({
 const avatarColors = [
   { background: '#ffd5df', color: '#932e57' },
   { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
+  { background: '#dfd', color: '#2a7a3b' },
   { background: '#ffe5cc', color: '#8a4500' },
 ];
 
 const formatTime = (time) => {
   if (!time) return '';
 
-  
+
   const [hour, minute] = time.split(':');
   return `${hour}:${minute}`;
 };
@@ -107,11 +115,30 @@ const formatDate = (date) => {
   return `${weekday}, ${day}-${month}`;
 };
 
+const distancia = '1,5km'
+
+const handleRefuseProposal = async (proposalId) => {
+  // isLoading.value = true
+  try {
+     await refuseProposal(proposalId)
+    
+    // await loadProposals()
+    // emit('refreshData')
+  } catch (error) {
+    console.log(error);
+  } finally {
+    // isLoading.value = false
+  }
+}
+
+
+
+
 const handleAcceptProposal = async (proposalId) => {
   // isLoading.value = true
   try {
     await acceptProposal(proposalId)
-    
+
     // emit('refreshData')
     // onDialogOK()
   } catch (error) {
@@ -125,6 +152,7 @@ const handleAcceptProposal = async (proposalId) => {
 <style scoped lang="scss">
 .scroll-wrapper {
   overflow-x: auto;
+  scrollbar-width: none;
 }
 
 .scroll-track {
@@ -132,101 +160,147 @@ const handleAcceptProposal = async (proposalId) => {
   gap: 12px;
 }
 
-.scroll-track::-webkit-scrollbar {
+.scroll-wrapper::-webkit-scrollbar {
   display: none;
 }
 
 .proposal-card {
-  min-width: 300px;
-  max-width: 300px;
-  min-height: 140px; // garante altura pra distribuir conteúdo
-  border-radius: 20px;
-  padding: 16px;
+  position: relative;
+  min-width: 330px;
+  max-width: 330px;
+  min-height: 115px;
+  border-radius: 18px;
+  padding: 12px;
   background: white;
   flex-shrink: 0;
 }
 
-.proposal-card .row {
-  height: 100%;
-  align-items: stretch !important;
+/* alinhar topo */
+.proposal-card .row:first-child {
+  align-items: flex-start !important;
+}
+
+.row.justify-between {
+  align-items: flex-start !important;
+  padding-right: 70px; // espaço pro preço
 }
 
 .content {
   width: 100%;
-  height: 100%;
   display: flex;
   flex-direction: column;
-  justify-content: space-between; // distribui topo e base
+  justify-content: space-between;
+  gap: 2px;
 }
 
+/* nome */
 .name {
-  font-size: 15px;
+  font-size: 14px;
   font-weight: 600;
-  color: #6b4eff;
+  color: #5a3eff;
 }
 
 .age {
   font-size: 11px;
   color: #999;
+  font-weight: 400;
 }
 
+/* rating */
 .rating {
-  font-size: 12px;
-  color: #0f0000;
+  font-size: 11px;
+  color: #444;
 }
 
+/* distância */
 .distance {
   font-size: 11px;
-  font-weight: 600;
   color: #777;
+  font-weight: 500;
 }
 
+.price .distance {
+  font-size: 9px; // 👈 menor
+  color: #777;
+  margin-top: 4px; // 👈 desce um pouco
+  font-weight: 400;
+}
+
+/* data */
 .datetime {
-  font-size: 11px;
-  color: #999;
-  margin-top: 10px;
-  line-height: 1.5;
-  margin-left: 6px;
+  font-size: 10px;
+  color: #8f8f8f;
+  margin-top: 2px;
+  line-height: 1.2;
 }
 
+/* preço topo direito */
 .price {
   position: absolute;
-  top: 16px;
-  right: 16px;
-  font-size: 14px;
+  top: 12px;
+  right: 12px;
+  font-size: 15px;
   font-weight: 700;
   color: #6b4eff;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
 }
+
+.text-price-label {
+  font-size: 10px;
+  color: #999;
+  font-weight: 400;
+}
+
+
 .type {
-  font-size: 11px;
-  margin-top: 4px;
+  position: absolute;
+  left: 12px;
+  top: 72px;
+  font-size: 10px;
   color: #6b4eff;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  line-height: 1.1;
 }
 
 .type span {
   font-weight: 600;
 }
 
+.type span {
+  font-weight: 600;
+}
+
+/* avatar alinhado */
+.q-avatar {
+  margin-left: 0 !important;
+  margin-right: 10px !important;
+}
+
+/* botões */
 .actions {
   display: flex;
   justify-content: flex-end;
   gap: 8px;
-  margin-top: auto; 
+  margin-top: auto;
 }
 
 .btn-reject {
   background: #eee6ff;
   color: #6b4eff;
   border-radius: 20px;
-  padding: 4px 14px;
-  font-size: 12px;
+  padding: 3px 12px;
+  font-size: 11px;
 }
 
 .btn-accept {
   background: linear-gradient(90deg, #6b4eff, #9f6bff);
   color: white;
   border-radius: 20px;
-  padding: 4px 14px;
-  font-size: 12px;
+  padding: 3px 12px;
+  font-size: 11px;
 }
 </style>