Răsfoiți Sursa

feat: :sparkles: feat(adicionar novo card) Foi adicionado novo card na parte do dashboard do prestador

Foi adicionado novo card na dashboard do presatdor (aguardando confirmação do cliente) Visando deixar mais claro para o presatdor quando o cliente realmenten fechou a diaria ou não, tambem foi ajustado as traduções

fase:dev | origin:escopo
kayo henrique 1 zi în urmă
părinte
comite
08e6def5a5

+ 302 - 0
src/components/dashboard/DashboardPendingConfirmation.vue

@@ -0,0 +1,302 @@
+<template>
+  <div class="q-mx-md q-mb-md">
+    <div class="font16 fontbold gradient-diarista q-mb-sm section-title">
+      {{ $t("provider.dashboard.pending_confirmation.title") }}
+    </div>
+
+    <div v-if="!data.length" class="empty-alert row no-wrap items-start">
+      <q-icon
+        class="empty-alert__icon"
+        name="mdi-alert-outline"
+        size="28px"
+      />
+
+      <div class="empty-alert__text font10">
+        <span class="fontbold">
+          {{ $t("provider.dashboard.pending_confirmation.empty_alert_bold") }}
+        </span>
+
+        {{ $t("provider.dashboard.pending_confirmation.empty_alert_text") }}
+      </div>
+    </div>
+
+    <div
+      v-else
+      class="scroll-wrapper"
+    >
+      <div
+        class="scroll-track"
+        :class="{ 'scroll-track--single': data.length === 1 }"
+      >
+        <q-card
+          v-for="item in data"
+          :key="item.id"
+          :class="{ 'schedule-card--single': data.length === 1 }"
+          :flat="false"
+          class="schedule-card card-border shadow-card bg-surface"
+        >
+          <q-card-section class="q-pa-sm">
+            <div class="row no-wrap items-center q-gutter-x-sm">
+              <q-avatar size="48px">
+                <img
+                  v-if="item.customer_photo"
+                  :src="item.customer_photo"
+                  style="object-fit: cover"
+                />
+
+                <span
+                  v-else
+                  class="flex flex-center full-height full-width"
+                  style="border-radius: 50%"
+                  :style="avatarColors[item.id % avatarColors.length]"
+                >
+                  {{
+                    getFirstName(item.customer_name ?? item.client_name)
+                      ?.slice(0, 2)
+                      .toUpperCase() ?? "??"
+                  }}
+                </span>
+              </q-avatar>
+
+              <div class="column flex-1">
+                <div class="row items-center q-gutter-x-xs">
+                  <span class="ellipsis font12 fontmedium text-name">
+                    {{ getFirstName(item.customer_name ?? item.client_name) || "—" }}
+                  </span>
+
+                  <div
+                    v-if="item.customer_rating"
+                    class="row items-center"
+                  >
+                    <q-icon
+                      color="warning"
+                      name="mdi-star"
+                      size="14px"
+                    />
+
+                    <span class="text-text font11">
+                      {{ item.customer_rating }}
+                    </span>
+                  </div>
+                </div>
+
+                <div class="row items-center no-wrap">
+                  <span class="font9 fontbold text-schedule-date-bold">
+                    {{ formatWeekday(item.date) }}
+                  </span>
+
+                  <span class="font9 text-schedule-date-regular">
+                    {{ ", " + formatDayMonth(item.date) }}
+                  </span>
+                </div>
+
+                <div class="text-schedule-date-regular font9">
+                  {{ $t("common.from") }}
+
+                  <span class="text-schedule-date-bold fontbold">
+                    {{ item.start_time?.slice(0, 5) }}
+                  </span>
+
+                  {{ $t("common.to") }}
+
+                  <span class="text-schedule-date-bold fontbold">
+                    {{ item.end_time?.slice(0, 5) }}
+                  </span>
+                </div>
+              </div>
+
+              <div class="column items-end text-text">
+                <div class="font14 fontbold text-price">
+                  {{ formatCurrency(item.total_amount) }}
+                </div>
+
+                <div class="font10 fontmedium text-type">
+                  {{ t(labelsPeriodTypes.find((label) => label.value == item.period_type)?.label) }}
+                </div>
+
+                <div class="font10 text-distance">
+                  {{ item.distance_km ?? 0 }}{{ $t("common.km") }}
+                </div>
+              </div>
+            </div>
+
+            <div
+              class="font9 fontmedium items-center q-mt-md q-px-xs row text-text"
+            >
+              <div class="col ellipsis text-grey-7">
+                {{ formatAddress(item.address) || "N/A" }}
+              </div>
+
+              <q-icon
+                class="q-ml-xs"
+                color="primary"
+                name="mdi-content-copy"
+                size="12px"
+                @click="copyAddress(item.address)"
+              />
+            </div>
+
+            <div class="row q-mt-sm items-center">
+              <q-btn
+                color="primary"
+                flat
+                no-caps
+                padding="4px 8px"
+                size="xs"
+                unelevated
+                :label="$t('provider.dashboard.pending_confirmation.details')"
+                @click="emit('view-details', item)"
+              />
+
+              <q-space />
+
+              <div class="font9 items-center q-gutter-x-xs row text-grey-7">
+                <q-icon
+                  size="14px"
+                  :color="item.offers_meal ? 'secondary' : 'grey-5'"
+                  :name="item.offers_meal ? 'mdi-silverware' : 'mdi-close-outline'"
+                />
+
+                <span :class="item.offers_meal ? 'fontmedium' : ''">
+                  {{
+                    item.offers_meal
+                      ? $t("provider.dashboard.pending_confirmation.offers_meal")
+                      : $t("provider.dashboard.pending_confirmation.no_meal")
+                  }}
+                </span>
+              </div>
+            </div>
+          </q-card-section>
+        </q-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { avatarColors, formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
+import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
+import { useI18n } from "vue-i18n";
+import { useQuasar } from "quasar";
+
+defineProps({
+  data: {
+    default: () => [],
+    type: Array,
+  },
+});
+
+const emit = defineEmits(["view-details"]);
+
+const $q = useQuasar();
+
+const { t } = useI18n();
+
+
+
+const copyAddress = (address) => {
+  const formatted = formatAddress(address);
+
+  if (formatted) {
+    navigator.clipboard.writeText(formatted);
+  }
+
+  $q.notify({
+    color: "positive",
+    message: t("provider.dashboard.pending_confirmation.address_copied"),
+  });
+};
+
+const formatDayMonth = (iso) => {
+  if (!iso) {
+    return "";
+  }
+
+  return new Date(iso).toLocaleDateString("pt-BR", {
+    day: "2-digit",
+    month: "2-digit",
+  });
+};
+
+const formatWeekday = (iso) => {
+  if (!iso) {
+    return "";
+  }
+
+  const date = new Date(iso);
+
+  const weekday = date.toLocaleDateString("pt-BR", { weekday: "long" });
+
+  return weekday.charAt(0).toUpperCase() + weekday.slice(1);
+};
+</script>
+
+<style scoped lang="scss">
+.scroll-wrapper {
+  overflow: hidden;
+}
+.empty-alert {
+  background: #e3efff;
+  border-radius: 8px;
+  gap: 12px;
+  padding: 13px 14px;
+}
+.empty-alert__icon {
+  color: #6554d9;
+  flex: 0 0 auto;
+}
+.empty-alert__text {
+  color: #6554d9;
+  line-height: 1.2;
+  padding-top: 1px;
+}
+.scroll-track {
+  display: flex;
+  flex-direction: row;
+  gap: 12px;
+  overscroll-behavior-x: contain;
+  overflow-x: auto;
+  padding-bottom: 8px;
+  scroll-snap-type: x proximity;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+
+  &::after {
+    content: "";
+    flex: 0 0 1px;
+  }
+}
+.schedule-card {
+  border-radius: 12px;
+  min-width: 82vw;
+}
+.schedule-card--single {
+  min-width: 100%;
+  width: 100%;
+}
+.text-name {
+  color: #3a3a4a;
+}
+.text-schedule-date-bold {
+  color: #3a3a4a;
+}
+.text-schedule-date-regular {
+  color: #666;
+}
+.text-date,
+.text-time,
+.text-type,
+.text-region,
+.text-distance {
+  color: #666;
+}
+.text-price {
+  color: #3a3a4a;
+}
+.flex-1 {
+  flex: 1;
+  min-width: 0;
+}
+</style> 

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

@@ -431,6 +431,15 @@
         "btn_reschedule": "reschedule",
         "empty_upcoming": "No scheduled services",
         "empty_completed": "No completed services"
+      },
+      "pending_confirmation": {
+        "title": "Waiting for customer confirmation",
+        "empty_alert_bold": "At the moment, you have no services waiting for customer confirmation.",
+        "empty_alert_text": "As soon as a customer completes the payment, the service will appear here.",
+        "details": "View details",
+        "offers_meal": "Offers meal",
+        "no_meal": "Does not offer meal",
+        "address_copied": "Address copied!"
       }
     },
     "payments": {
@@ -886,4 +895,4 @@
       "user_message_header": "--- User message ---"
     }
   }
-}
+}

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

@@ -431,6 +431,15 @@
         "btn_reschedule": "reprogramar",
         "empty_upcoming": "Sin servicios programados",
         "empty_completed": "Sin servicios completados"
+      },
+      "pending_confirmation": {
+        "title": "Esperando la confirmación del cliente",
+        "empty_alert_bold": "Por el momento, no tienes servicios pendientes de confirmación del cliente.",
+        "empty_alert_text": "En cuanto un cliente realice el pago, el servicio aparecerá aquí.",
+        "details": "Ver detalles",
+        "offers_meal": "Ofrece comida",
+        "no_meal": "No ofrece comida",
+        "address_copied": "¡Dirección copiada!"
       }
     },
     "payments": {
@@ -886,4 +895,4 @@
       "user_message_header": "--- Mensaje del usuario ---"
     }
   }
-}
+}

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

@@ -431,6 +431,15 @@
         "btn_reschedule": "reagendar",
         "empty_upcoming": "Nenhum serviço agendado",
         "empty_completed": "Nenhum serviço concluído"
+      },
+      "pending_confirmation": {
+        "title": "Aguardando confirmação do cliente",
+        "empty_alert_bold": "No momento, você não possui serviços aguardando confirmação.",
+        "empty_alert_text": "Assim que um cliente realizar o pagamento, o serviço aparecerá aqui.",
+        "details": "Ver detalhes",
+        "offers_meal": "Oferece refeição",
+        "no_meal": "Não oferece refeição",
+        "address_copied": "Endereço copiado!"
       }
     },
     "payments": {
@@ -889,4 +898,4 @@
       "user_message_header": "--- Mensagem do usuário ---"
     }
   }
-}
+}

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

@@ -34,8 +34,14 @@
           @view-details="(item) => openDetailsDialog(item)"
         />
 
+        <DashboardPendingConfirmation
+         v-if="pendingConfirmation.length > 0"
+        :data="pendingConfirmation"
+        @view-details="(item) => openNextScheduleDialog(item)" 
+        />
+
         <DashboardNextSchedules
-          :data="nextSchedules"
+           :data="nextSchedules"
           @view-details="(item) => openNextScheduleDialog(item)"
         />
 
@@ -64,6 +70,7 @@ import NextSchedulesDetailsDialog from "src/components/dashboard/NextSchedulesDe
 import ScheduleRatingDialog from "src/components/dashboard/ScheduleRatingDialog.vue";
 import SolicitationDetailsDialog from "src/components/dashboard/SolicitationDetailsDialog.vue";
 import OpportunityDialog from "src/pages/opportunities/components/OpportunityDialog.vue";
+import DashboardPendingConfirmation from "src/components/dashboard/DashboardPendingConfirmation.vue";
 
 const $q = useQuasar();
 const router = useRouter();
@@ -78,6 +85,7 @@ const priceSuggestion = ref({});
 const solicitations = ref([]);
 const summaryInfos = ref({});
 const todayServices = ref([]);
+const pendingConfirmation = ref([]);
 
 const handleScheduleAction = async (id, status, ids = [], servicePackageId = null) => {
   try {
@@ -101,6 +109,8 @@ const handleScheduleAction = async (id, status, ids = [], servicePackageId = nul
 
 const loadDashboard = async () => {
   const response = await dadosDashboard();
+  console.log(response);
+console.log(response.solicitations);
 
   if (response) {
     headerBar.value = response.headerBar;
@@ -110,6 +120,7 @@ const loadDashboard = async () => {
     priceSuggestion.value = response.priceSuggested;
     solicitations.value = response.solicitations ?? [];
     summaryInfos.value = response.summaryInfos;
+    pendingConfirmation.value = response.pendingConfirmation ?? [];
     todayServices.value = response.todayServices ?? [];
   }
 };