Просмотр исходного кода

feat: :sparkles: feat (agendamentos) criado bloco de servicos do dia na dashboard

foi criado o bloco de serviços do dia na dashboard, com indicacao de inicio, codigo preenchido e finalizacao (libera avaliacao ao finalizar)

fase:dev | origin:escopo
Gustavo Zanatta 1 неделя назад
Родитель
Сommit
5c4c14155b

+ 209 - 0
src/components/dashboard/DashboardTodaySchedules.vue

@@ -0,0 +1,209 @@
+<template>
+  <div class="q-mx-md q-mb-md">
+    <div class="scroll-wrapper">
+      <div class="scroll-track">
+        <q-card
+          v-for="item in data"
+          :key="item.id"
+          class="today-card card-border shadow-card bg-surface"
+          :flat="false"
+        >
+          <q-card-section class="q-pa-md">
+            <div class="row no-wrap items-start q-mb-xs">
+              <q-avatar size="40px" class="flex-shrink-0 q-mr-sm">
+                <img v-if="item.provider_photo" :src="item.provider_photo" />
+                <span v-else :style="avatarColors[item.id % avatarColors.length]" class="text-weight-bold full-width full-height flex flex-center" style="font-size:14px; border-radius:50%;">
+                  {{ item.provider_name?.slice(0, 2).toUpperCase() ?? '??' }}
+                </span>
+              </q-avatar>
+              <div class="col column no-wrap overflow-hidden justify-center">
+                <span class="text-body2 text-text leading-tight">
+                  <template v-if="cardState(item) === 'awaiting_code'">{{ $t('dashboard_client.today_schedules.start_with') }}</template>
+                  <template v-else-if="cardState(item) === 'in_progress'">{{ $t('dashboard_client.today_schedules.started_by') }}</template>
+                  <template v-else>{{ $t('dashboard_client.today_schedules.finished_by') }}</template>
+                  <span class="text-weight-bold"> {{ ' ' +  item.provider_name ?? '—' }}</span>
+                </span>
+                <div class="row items-center q-mt-xs">
+                  <q-icon name="mdi-clock-outline" size="13px" class="q-mr-xs gradient-diarista" />
+                  <span class="text-caption text-grey-5">
+                    <template v-if="cardState(item) !== 'finished'">{{ $t('dashboard_client.next_schedules.from') }} </template>
+                    {{ item.start_time?.slice(0, 5) }} {{ $t('dashboard_client.next_schedules.to') }} {{ item.end_time?.slice(0, 5) }}
+                  </span>
+                </div>
+              </div>
+              <div class="flex-shrink-0 q-ml-sm column items-center justify-start">
+                <template v-if="cardState(item) === 'awaiting_code'">
+                  <div class="column items-center">
+                    <span class="text-caption text-primary q-mb-xs">{{ $t('dashboard_client.today_schedules.code_label') }}</span>
+                    <div class="code-pill bg-primary">{{ item.code }}</div>
+                  </div>
+                </template>
+
+                <template v-else-if="cardState(item) === 'in_progress'">
+                  <div class="column items-center">
+                    <div class="clock-badge">
+                      <q-icon name="mdi-clock-outline" size="18px" color="white" />
+                    </div>
+                    <span class="badge-status-text text-primary text-weight-bold q-mt-xs">
+                      {{ $t('dashboard_client.today_schedules.in_progress') }}
+                    </span>
+                  </div>
+                </template>
+
+                <template v-else>
+                  <q-btn
+                    unelevated rounded no-caps
+                    class="rate-btn"
+                    icon="mdi-star-outline"
+                    :label="$t('dashboard_client.today_schedules.rate_btn')"
+                    size="sm"
+                    @click.stop="emit('rate', item)"
+                  />
+                </template>
+              </div>
+            </div>
+
+            <div class="progress-track q-mb-sm">
+              <div class="progress-fill" :class="cardState(item) === 'finished' ? 'progress-fill--finished' : ''" :style="{ width: progressByState(item) + '%' }" />
+            </div>
+
+            <div class="row items-center no-wrap">
+              <template v-if="cardState(item) !== 'finished'">
+                <q-btn
+                  flat no-caps dense
+                  :label="$t('dashboard_client.today_schedules.help_btn')"
+                  color="primary"
+                  size="sm"
+                  class="flex-shrink-0"
+                  @click.stop="openHelp"
+                />
+                <q-space />
+                <template v-if="cardState(item) === 'awaiting_code'">
+                  <q-icon name="mdi-map-marker-outline" size="13px" color="grey-6" class="q-mr-xs flex-shrink-0" />
+                  <span class="text-caption text-grey-6 col ellipsis text-right">
+                    {{ [item.address?.address, item.address?.number, item.address?.district].filter(Boolean).join(', ') || '—' }}
+                  </span>
+                </template>
+                <template v-else>
+                  <q-icon name="mdi-clock-outline" size="13px" class="q-mr-xs flex-shrink-0 gradient-diarista" />
+                  <span class="text-caption text-grey-6 text-right text-no-wrap">
+                    {{ $t('dashboard_client.today_schedules.end_time_label') }} 
+                    <span class="gradient-diarista">{{ item.end_time?.slice(0, 5) }}</span>
+                  </span>
+                </template>
+              </template>
+            </div>
+          </q-card-section>
+        </q-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { useQuasar } from 'quasar'
+import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
+
+defineProps({ data: { type: Array, default: () => [] } })
+const emit = defineEmits(['rate'])
+
+const $q = useQuasar()
+
+const avatarColors = [
+  { background: '#ffd5df', color: '#932e57' },
+  { background: '#d7e8ff', color: '#2158a8' },
+  { background: '#dfd',    color: '#2a7a3b' },
+  { background: '#ffe5cc', color: '#8a4500' },
+]
+
+const cardState = (item) => {
+  if (!item.code_verified) return 'awaiting_code'
+  const [h, m] = (item.end_time || '23:59').slice(0, 5).split(':').map(Number)
+  const endTime = new Date()
+  endTime.setHours(h, m, 0, 0)
+  return new Date() >= endTime ? 'finished' : 'in_progress'
+}
+
+const progressByState = (item) => {
+  const state = cardState(item)
+  if (state === 'awaiting_code') return 60
+  if (state === 'in_progress')   return 80
+  return 100
+}
+
+const openHelp = () => {
+  $q.dialog({ component: ProfileHelpDialog })
+}
+</script>
+
+<style scoped lang="scss">
+.scroll-wrapper { overflow: hidden; }
+.scroll-track {
+  display: flex;
+  flex-direction: row;
+  gap: 12px;
+  overflow-x: auto;
+  overscroll-behavior-x: contain;
+  scroll-snap-type: x proximity;
+  padding-bottom: 8px;
+  &::-webkit-scrollbar { display: none; }
+  &::after { content: ''; flex: 0 0 1px; }
+}
+
+.today-card {
+  min-width: 80%;
+  scroll-snap-align: start;
+  border-radius: 12px;
+}
+
+.code-pill {
+  color: white;
+  font-weight: 700;
+  font-size: 15px;
+  letter-spacing: 2px;
+  border-radius: 20px;
+  padding: 4px 14px;
+}
+
+.clock-badge {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #8B5CF6, #EC4899);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.badge-status-text {
+  font-size: 10px;
+  white-space: nowrap;
+}
+
+.rate-btn {
+  background: linear-gradient(90deg, #8B5CF6, #EC4899);
+  color: white;
+  font-weight: 700;
+  font-size: 12px;
+  white-space: nowrap;
+}
+
+.progress-track {
+  width: 100%;
+  height: 5px;
+  background: #E2E8F0;
+  border-radius: 3px;
+  overflow: hidden;
+}
+
+.progress-fill {
+  height: 100%;
+  border-radius: 3px;
+  background: linear-gradient(90deg, #8B5CF6, #EC4899);
+  transition: width 0.4s ease;
+
+  &--finished {
+    background: #22c55e;
+  }
+}
+</style>

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

@@ -397,6 +397,16 @@
       "welcome": "Welcome,",
       "my_schedules": "My schedules"
     },
+    "today_schedules": {
+      "start_with": "Service starting with",
+      "started_by": "Service started by",
+      "finished_by": "Service completed by",
+      "code_label": "Code",
+      "in_progress": "in progress",
+      "end_time_label": "Ends at",
+      "rate_btn": "Rate",
+      "help_btn": "help"
+    },
     "next_schedules": {
       "title": "Next services",
       "no_provider": "No provider assigned",

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

@@ -397,6 +397,16 @@
       "welcome": "Bienvenido (a),",
       "my_schedules": "Mis jornadas"
     },
+    "today_schedules": {
+      "start_with": "Servicio iniciado con",
+      "started_by": "Servicio iniciado por",
+      "finished_by": "Servicio concluido por",
+      "code_label": "Código",
+      "in_progress": "en progreso",
+      "end_time_label": "Término a las",
+      "rate_btn": "Avaliar",
+      "help_btn": "ayuda"
+    },
     "next_schedules": {
       "title": "Próximos servicios",
       "no_provider": "Sin prestador asignado",

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

@@ -402,6 +402,16 @@
       "welcome": "Bem-vindo (a),",
       "my_schedules": "Minhas diárias"
     },
+    "today_schedules": {
+      "start_with": "Início do serviço com",
+      "started_by": "Serviço iniciado por",
+      "finished_by": "Serviço concluído por",
+      "code_label": "Código",
+      "in_progress": "em andamento",
+      "end_time_label": "Término às",
+      "rate_btn": "Avaliar",
+      "help_btn": "ajuda"
+    },
     "next_schedules": {
       "title": "Próximos serviços",
       "no_provider": "Sem prestador definido",

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

@@ -14,6 +14,7 @@
         @view-details="openAcceptedDialog"
         @cancel="cancelSchedule"
       />
+      <DashboardTodaySchedules v-if="todaySchedules.length > 0" :data="todaySchedules"/>
       <DashboardScrollAreaSchedules />
       <DashboardPendingCustomSchedules />
       <DashboardNextSchedules v-if="nextSchedules.length > 0" :data="nextSchedules" @view-details="openNextScheduleDialog" />
@@ -31,10 +32,10 @@ import DashboardPendingSchedules from 'src/components/dashboard/DashboardPending
 import ScheduleAcceptedDialog from 'src/components/dashboard/ScheduleAcceptedDialog.vue';
 import DashboardScrollAreaSchedules from 'src/components/dashboard/DashboardScrollAreaSchedules.vue';
 import DashboardNextSchedules from 'src/components/dashboard/DashboardNextSchedules.vue';
-import NextSchedulesDetailsDialog from 'src/components/dashboard/NextSchedulesDetailsDialog.vue';
 import DashboardLastDoneSchedules from 'src/components/dashboard/DashboardLastDoneSchedules.vue';
 import DashboardFavoriteProviders from 'src/components/dashboard/DashboardFavoriteProviders.vue';
 import DashboardProvidersClose from 'src/components/dashboard/DashboardProvidersClose.vue';
+import DashboardTodaySchedules from 'src/components/dashboard/DashboardTodaySchedules.vue';
 import FinalSuccesModal from '../schedules/components/FinalSuccesModal.vue';
 import DashboardPendingCustomSchedules from 'src/pages/dashboard/components/DashboardPendingCustomSchedules.vue';
 import { useRouter } from 'vue-router'
@@ -42,6 +43,7 @@ import { onMounted, ref } from 'vue';
 import { useDialogPluginComponent, useQuasar } from 'quasar';
 import { dadosDashboard } from 'src/api/dashboard';
 import ScheduleCancelDialog from 'src/components/dashboard/ScheduleCancelDialog.vue';
+import NextSchedulesDetailsDialog from 'src/components/dashboard/NextSchedulesDetailsDialog.vue';
 
 const router = useRouter()
 const headerBar = ref({});
@@ -51,6 +53,7 @@ const nextSchedules = ref([]);
 const lastDoneSchedules = ref([]);
 const favoriteProviders = ref([]);
 const providersClose = ref([]);
+const todaySchedules = ref([]);
 const $q = useQuasar();
 const loading = ref(true);
 
@@ -77,6 +80,7 @@ const reloadDashboard = async () => {
     lastDoneSchedules.value = response.lastDoneSchedules ?? [];
     favoriteProviders.value = response.favoriteProviders ?? [];
     providersClose.value = response.providersClose ?? [];
+    todaySchedules.value = response.todaySchedules ?? [];
   }
   if( showSuccessModal.value ) {
     $q.dialog({