Parcourir la source

feat: :sparkles: feat (disponibilidade prestador) alterada agenda para visualizacao modo pílula

alterado o icone que indica que o dia da agenda contem um bloqueio, para adicionar uma pílula, com preenchimento parcial com cores diferentes indicando período da manha/tarde

fase:dev | origin:apresentacao-02-04-26
Gustavo Zanatta il y a 2 semaines
Parent
commit
413fb075d5

+ 328 - 0
src/components/profile/ProfileAvailabilityCalendar.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="avail-calendar">
+
+    <div class="avail-cal__nav row items-center justify-between q-px-md q-pt-md q-pb-sm q-gutter-x-sm">
+      <q-btn flat round dense icon="mdi-chevron-left" size="sm" color="text" @click="prevMonth" />
+      <span class="avail-cal__nav-label month-label">{{ monthLabel }}</span>
+      <q-btn flat round dense icon="mdi-chevron-right" size="sm" color="text" @click="nextMonth" />
+
+      <q-btn flat round dense icon="mdi-chevron-left" size="xs" color="text" @click="prevYear" />
+      <span class="avail-cal__nav-label year-label">{{ currentYear }}</span>
+      <q-btn flat round dense icon="mdi-chevron-right" size="xs" color="text" @click="nextYear" />
+    </div>
+
+    <div class="avail-cal__weekdays row q-px-sm q-pb-xs">
+      <div
+        v-for="wd in weekdayLabels"
+        :key="wd"
+        class="avail-cal__weekday col text-center text-weight-bold"
+      >
+        {{ wd }}
+      </div>
+    </div>
+
+    <div class="avail-cal__grid q-px-sm q-pb-md">
+      <div
+        v-for="n in firstDayOffset"
+        :key="`empty-${n}`"
+        class="avail-cal__cell"
+      />
+
+      <div
+        v-for="day in daysInMonth"
+        :key="day"
+        class="avail-cal__cell column items-center justify-start"
+      >
+        <button
+          class="avail-cal__day-btn"
+          :class="{
+            'avail-cal__day--today': isToday(day),
+            'avail-cal__day--past': isPast(day),
+          }"
+          @click="onDayClick(day)"
+        >
+          {{ day }}
+        </button>
+
+        <div v-if="hasAnyBlock(day)" class="avail-cal__pill" @click="onDayClick(day)">
+          <div
+            class="avail-cal__pill-half avail-cal__pill-left"
+            :class="morningState(day)"
+          />
+          <div
+            class="avail-cal__pill-half avail-cal__pill-right"
+            :class="afternoonState(day)"
+          />
+        </div>
+      </div>
+    </div>
+
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+
+const props = defineProps({
+  blockedDays: {
+    type: Array,
+    default: () => [],
+  },
+})
+
+const emit = defineEmits(['date-click', 'navigation'])
+
+const { locale } = useI18n()
+
+const today = new Date()
+const currentYear  = ref(today.getFullYear())
+const currentMonth = ref(today.getMonth() + 1) // 1-12
+
+const weekdayLabels = computed(() => {
+  const base = new Date(2023, 0, 1)
+  return Array.from({ length: 7 }, (_, i) => {
+    const d = new Date(base)
+    d.setDate(d.getDate() + i)
+    return d.toLocaleDateString(locale.value || 'pt-BR', { weekday: 'narrow' })
+      .replace('.', '')
+      .slice(0, 3)
+      .toUpperCase()
+  })
+})
+
+const monthLabel = computed(() => {
+  const d = new Date(currentYear.value, currentMonth.value - 1, 1)
+  return d.toLocaleDateString(locale.value || 'pt-BR', { month: 'long' })
+})
+
+const daysInMonth = computed(() => {
+  return new Date(currentYear.value, currentMonth.value, 0).getDate()
+})
+
+const firstDayOffset = computed(() => {
+  return new Date(currentYear.value, currentMonth.value - 1, 1).getDay()
+})
+
+const prevMonth = () => {
+  if (currentMonth.value === 1) {
+    currentMonth.value = 12
+    currentYear.value--
+  } else {
+    currentMonth.value--
+  }
+  emitNavigation()
+}
+
+const nextMonth = () => {
+  if (currentMonth.value === 12) {
+    currentMonth.value = 1
+    currentYear.value++
+  } else {
+    currentMonth.value++
+  }
+  emitNavigation()
+}
+
+const prevYear = () => {
+  currentYear.value--
+  emitNavigation()
+}
+
+const nextYear = () => {
+  currentYear.value++
+  emitNavigation()
+}
+
+const emitNavigation = () => {
+  emit('navigation', { year: currentYear.value, month: currentMonth.value })
+}
+
+const pad = (n) => String(n).padStart(2, '0')
+
+const dateStrForDay = (day) =>
+  `${currentYear.value}-${pad(currentMonth.value)}-${pad(day)}`
+
+const isToday = (day) => {
+  return (
+    day === today.getDate() &&
+    currentMonth.value === today.getMonth() + 1 &&
+    currentYear.value === today.getFullYear()
+  )
+}
+
+const isPast = (day) => {
+  const d = new Date(currentYear.value, currentMonth.value - 1, day)
+  const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate())
+  return d < todayStart
+}
+
+const blockedPeriodsForDay = (day) => {
+  const dateStr = dateStrForDay(day)
+  return props.blockedDays
+    .filter((bd) => bd.date === dateStr)
+    .map((bd) => bd.period)
+}
+
+const hasAnyBlock = (day) => blockedPeriodsForDay(day).length > 0
+
+const isMorningBlocked = (day) => {
+  const periods = blockedPeriodsForDay(day)
+  return periods.includes('morning') || periods.includes('all')
+}
+
+const isAfternoonBlocked = (day) => {
+  const periods = blockedPeriodsForDay(day)
+  return periods.includes('afternoon') || periods.includes('all')
+}
+
+const morningState = (day) => ({
+  'pill-half--blocked':  isMorningBlocked(day),
+  'pill-half--free':     !isMorningBlocked(day),
+})
+
+const afternoonState = (day) => ({
+  'pill-half--blocked':  isAfternoonBlocked(day),
+  'pill-half--free':     !isAfternoonBlocked(day),
+})
+
+const onDayClick = (day) => {
+  const dateStr = `${currentYear.value}/${pad(currentMonth.value)}/${pad(day)}`
+  emit('date-click', dateStr)
+}
+</script>
+
+<style scoped lang="scss">
+$morning-color:   #f916f9;
+$afternoon-color: #6366F1;
+$blocked-opacity: 1;
+$free-opacity:    0.05;
+
+.avail-calendar {
+  background: #fff;
+  border-radius: 20px;
+  overflow: hidden;
+  user-select: none;
+}
+
+/* ── Navegação ───────────────────────────────────────── */
+.avail-cal__nav {
+  background: #fff;
+}
+
+.avail-cal__nav-label {
+  font-weight: 700;
+  color: #1E293B;
+
+  &.month-label {
+    color: #6366F1;
+    font-size: 15px;
+    text-transform: capitalize;
+  }
+
+  &.year-label {
+    color: #6366F1;
+    font-size: 15px;
+  }
+}
+
+/* ── Cabeçalho weekdays ──────────────────────────────── */
+.avail-cal__weekdays {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+}
+
+.avail-cal__weekday {
+  font-size: 11px;
+  color: #6366F1;
+  opacity: 0.8;
+  padding: 4px 0;
+}
+
+/* ── Grid de dias ────────────────────────────────────── */
+.avail-cal__grid {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 0;
+}
+
+.avail-cal__cell {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 2px 0 6px;
+}
+
+/* ── Botão do número ─────────────────────────────────── */
+.avail-cal__day-btn {
+  width: 32px;
+  height: 32px;
+  border: none;
+  background: transparent;
+  border-radius: 50%;
+  font-family: 'Inter', sans-serif;
+  font-size: 13px;
+  font-weight: 500;
+  color: #1E293B;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: background 0.15s;
+
+  &:hover {
+    background: #f1f5f9;
+  }
+
+  &.avail-cal__day--today {
+    color: #7c4dff;
+    background: rgba(124, 77, 255, 0.08);
+  }
+
+  &.avail-cal__day--past {
+    color: #CBD5E1;
+    cursor: default;
+    pointer-events: none;
+  }
+}
+
+/* ── Pílula ──────────────────────────────────────────── */
+.avail-cal__pill {
+  display: flex;
+  width: 28px;
+  height: 9px;
+  border-radius: 999px;
+  overflow: hidden;
+  cursor: pointer;
+  margin-top: 2px;
+}
+
+.avail-cal__pill-half {
+  flex: 1;
+  transition: opacity 0.2s, background-color 0.2s;
+}
+
+.avail-cal__pill-left {
+  background-color: $morning-color;
+  border-radius: 999px 0 0 999px;
+
+  &.pill-half--blocked {
+    opacity: $blocked-opacity;
+  }
+  &.pill-half--free {
+    opacity: $free-opacity;
+  }
+}
+
+.avail-cal__pill-right {
+  background-color: $afternoon-color;
+  border-radius: 0 999px 999px 0;
+
+  &.pill-half--blocked {
+    opacity: $blocked-opacity;
+  }
+  &.pill-half--free {
+    opacity: $free-opacity;
+  }
+}
+</style>

+ 15 - 16
src/components/profile/ProfileAvailabilityDialog.vue

@@ -69,7 +69,7 @@
             {{ $t('profile.availability.agenda_subtitle') }}
           </div>
 
-          <div class="shadow-card q-mb-xl " style="border-radius: 20px; overflow: hidden;">
+          <!-- <div class="shadow-card q-mb-xl " style="border-radius: 20px; overflow: hidden;">
             <q-date
               ref="calendar"
               v-model="calendarDate"
@@ -83,6 +83,14 @@
               @navigation="onNavigation"
             >
             </q-date>
+          </div> -->
+
+          <div class="shadow-card q-mb-xl" style="border-radius: 20px; overflow: hidden;">
+            <ProfileAvailabilityCalendar
+              :blocked-days="blockedDays"
+              @date-click="onDateClick"
+              @navigation="onNavigation"
+            />
           </div>
 
           <div class="q-pb-xl"></div>
@@ -170,7 +178,7 @@
 
 <script setup>
 import { ref, computed, onMounted } from 'vue';
-import { useDialogPluginComponent, useQuasar, /*date*/ } from 'quasar';
+import { useDialogPluginComponent, useQuasar } from 'quasar';
 import { useI18n } from 'vue-i18n';
 import {
   getProviderWorkingDays,
@@ -184,6 +192,7 @@ import {
 } from 'src/api/providerBlockedDay';
 import { userStore } from 'src/stores/user';
 import ProfileBlockDayDialog from './ProfileBlockDayDialog.vue';
+import ProfileAvailabilityCalendar from './ProfileAvailabilityCalendar.vue';
 
 defineEmits([...useDialogPluginComponent.emits]);
 
@@ -201,7 +210,7 @@ const blockedDays = ref([]);
 const loadingBlock = ref(false);
 const loadingUnblock = ref({});
 
-const calendar = ref(null);
+// const calendar = ref(null);
 const calendarDate = ref(null);
 const currentViewDate = ref(new Date());
 
@@ -216,9 +225,9 @@ const isSelected = (day, period) =>
 const isDayFullySelected = (day) =>
   isSelected(day, 'morning') && isSelected(day, 'afternoon');
 
-const blockedDatesForCalendar = computed(() =>
-  blockedDays.value.map((bd) => bd.date.replace(/-/g, '/'))
-);
+// const blockedDatesForCalendar = computed(() =>
+//   blockedDays.value.map((bd) => bd.date.replace(/-/g, '/'))
+// );
 
 const currentDateBlockedDays = computed(() => {
   if (!currentActionDate.value) return [];
@@ -314,16 +323,6 @@ const onDateClick = (date) => {
   showActionSheet.value = true;
 };
 
-// const nextMonth = () => {
-//   const current = currentViewDate.value || new Date();
-//   calendarDate.value = date.formatDate(date.addToDate(current, { months: 1 }), 'YYYY/MM/DD');
-// };
-
-// const prevMonth = () => {
-//   const current = currentViewDate.value || new Date();
-//   calendarDate.value = date.formatDate(date.subtractFromDate(current, { months: 1 }), 'YYYY/MM/DD');
-// };
-
 const onNavigation = (view) => {
   currentViewDate.value = new Date(view.year, view.month - 1, 1);
 };