Преглед изворни кода

feat: :sparkles: feat(agendamento-sob-medida) Controle de disponibilidade e bloqueio de agendamentos

Foi implementado no frontend o controle de disponibilidade e bloqueio de agendamentos, considerando dias trabalhados, bloqueios, conflitos de horário e limite semanal entre cliente e prestador.

fase:dev | origin:escopo
kayo henrique пре 1 недеља
родитељ
комит
58ff0402f4

+ 11 - 0
src/api/schedule.js

@@ -9,3 +9,14 @@ export const updateScheduleStatus = async (id, status) => {
   const { data } = await api.patch(`/schedule/${id}/status`, { status });
   return data.payload;
 };
+
+export const getClientProviderBlocks = async (clientId, providerId) => {
+  const { data } = await api.get('/schedule/client-provider-blocks', {
+    params: {
+      client_id: clientId,
+      provider_id: providerId,
+    },
+  });
+
+  return data.payload;
+};

+ 46 - 11
src/pages/search/components/OrderSummaryDialog.vue

@@ -100,7 +100,7 @@ import { date } from 'quasar';
 import { useI18n } from 'vue-i18n';
 import { getProviderWorkingDays, getProviderBlockedDays } from 'src/api/providerAvailability';
 import { getAddresses } from 'src/api/address';
-import { createSchedule } from 'src/api/schedule';
+import { createSchedule, getClientProviderBlocks } from 'src/api/schedule';
 import { userStore } from 'src/stores/user';
 import ServiceSelectionSheet from './ServiceSelectionSheet.vue';
 import ServiceTimeSelectionDialog from './ServiceTimeSelectionDialog.vue';
@@ -126,16 +126,37 @@ const addDateValue = ref(null);
 const loadingAvailability = ref(false);
 const workingDays = ref([]);
 const blockedDays = ref([]);
+const providerClientBlocks = ref({
+  existing_schedules: [],
+  fully_blocked_weeks: [],
+});
+
+const normalizeDate = (d) => d.replace(/\//g, '-');
 
 const getWeekStart = (dateStr) => {
-  const d = new Date(dateStr.replace(/\//g, '-') + 'T12:00:00');
+  const d = new Date(normalizeDate(dateStr) + 'T12:00:00');
   d.setDate(d.getDate() - d.getDay());
   return d.toISOString().slice(0, 10);
 };
 
-const wouldExceedWeekLimit = (newDateStr) => {
+const blockedWeekStartSet = computed(() =>
+  new Set(providerClientBlocks.value.fully_blocked_weeks ?? [])
+);
+
+const getServerWeekCount = (newDateStr) => {
+  const newWeek = getWeekStart(newDateStr);
+  return (providerClientBlocks.value.existing_schedules ?? []).filter(
+    (schedule) => getWeekStart(schedule.date) === newWeek
+  ).length;
+};
+
+const getLocalWeekCount = (newDateStr) => {
   const newWeek = getWeekStart(newDateStr);
-  const count = bookings.value.filter(b => getWeekStart(b.date) === newWeek).length;
+  return bookings.value.filter((booking) => getWeekStart(booking.date) === newWeek).length;
+};
+
+const wouldExceedWeekLimit = (newDateStr) => {
+  const count = getServerWeekCount(newDateStr) + getLocalWeekCount(newDateStr);
   return count >= 2;
 };
 
@@ -151,26 +172,35 @@ const dateOptions = (d) => {
   const today = date.formatDate(new Date(), 'YYYY/MM/DD');
   if (d < today) return false;
   if (wouldExceedWeekLimit(d)) return false;
-  const raw = d.replace(/\//g, '-');
+  const raw = normalizeDate(d);
   const parsed = new Date(`${raw}T12:00:00`);
   const dayOfWeek = parsed.getDay();
   const isWorking = availableWeekDays.value.includes(dayOfWeek);
   const isBlocked = blockedDateSet.value.has(raw);
-  return isWorking && !isBlocked;
+  const isWeekBlocked = blockedWeekStartSet.value.has(getWeekStart(raw));
+  return isWorking && !isBlocked && !isWeekBlocked;
 };
 
 const loadAvailability = async () => {
   loadingAvailability.value = true;
   try {
-    const [wd, bd] = await Promise.all([
+    const clientId = store.user?.client_id;
+    const defaultClientBlocks = { existing_schedules: [], fully_blocked_weeks: [] };
+
+    const [wd, bd, clientBlocks] = await Promise.all([
       getProviderWorkingDays(props.provider.provider_id),
       getProviderBlockedDays(props.provider.provider_id),
+      clientId
+        ? getClientProviderBlocks(clientId, props.provider.provider_id)
+        : Promise.resolve(defaultClientBlocks),
     ]);
     workingDays.value = wd ?? [];
     blockedDays.value = bd ?? [];
+    providerClientBlocks.value = clientBlocks ?? defaultClientBlocks;
   } catch {
     workingDays.value = [];
     blockedDays.value = [];
+    providerClientBlocks.value = { existing_schedules: [], fully_blocked_weeks: [] };
   } finally {
     loadingAvailability.value = false;
   }
@@ -191,12 +221,10 @@ onMounted(() => Promise.all([loadAvailability(), loadPrimaryAddress()]));
 
 const formatHour = (h) => `${String(h).padStart(2, '0')}:00`;
 
-const normalizeDate = (d) => d.replace(/\//g, '-');
-
 const onAddDateSelected = (val) => {
   if (!val) return;
   addDateValue.value = null;
-  const valFormatted = val.replace(/\//g, '-');
+  const valFormatted = normalizeDate(val);
 
   const blocksOfDate = blockedDays.value.filter(
     bd => bd.date === valFormatted && bd.period !== 'all'
@@ -222,7 +250,14 @@ const onAddDateSelected = (val) => {
       end_hour:  `${b.slot.endHour}:00:00`,
     }));
 
-  const partialBlocks = [...blocksOfDate, ...workingDayBlocks, ...existingBookingBlocks];
+  const serverBookingBlocks = (providerClientBlocks.value.existing_schedules ?? [])
+    .filter((schedule) => schedule.date === valFormatted)
+    .map((schedule) => ({
+      init_hour: schedule.start_time,
+      end_hour: schedule.end_time,
+    }));
+
+  const partialBlocks = [...blocksOfDate, ...workingDayBlocks, ...existingBookingBlocks, ...serverBookingBlocks];
 
   $q.dialog({
     component: ServiceSelectionSheet,

+ 61 - 34
src/pages/search/components/SchedulingDialog.vue

@@ -129,7 +129,9 @@ import { ref, computed, onMounted } from 'vue';
 import { useDialogPluginComponent, useQuasar } from 'quasar';
 import { date } from 'quasar';
 import { getProviderWorkingDays, getProviderBlockedDays } from 'src/api/providerAvailability';
+import { getClientProviderBlocks } from 'src/api/schedule';
 import { getProviderReceivedReviews } from 'src/api/review';
+import { userStore } from 'src/stores/user';
 import ServiceSelectionSheet from './ServiceSelectionSheet.vue';
 import ServiceTimeSelectionDialog from './ServiceTimeSelectionDialog.vue';
 import OrderSummaryDialog from './OrderSummaryDialog.vue'
@@ -145,17 +147,21 @@ defineEmits([...useDialogPluginComponent.emits]);
 
 const { dialogRef } = useDialogPluginComponent();
 const $q = useQuasar();
+const store = userStore();
 
 const selectedDate = ref(null);
 const workingDays = ref([]);
 const blockedDays = ref([]);
 const loadingAvailability = ref(true);
-
 const reviews = ref([]);
 const loadingReviews = ref(true);
 
 
 const bookings = ref([]);
+const providerClientBlocks = ref({
+  existing_schedules: [],
+  fully_blocked_weeks: [],
+});
 
 
 const availableWeekDays = computed(() =>
@@ -172,53 +178,55 @@ const blockedDateSet = computed(() =>
 );
 
 
-const getWeekRange = (dateStr) => {
-  const d = new Date(`${dateStr}T12:00:00`)
-  const day = d.getDay()
-
-  const start = new Date(d)
-  start.setDate(d.getDate() - day)
+const normalizeDate = (dateStr) => (dateStr ?? '').replace(/\//g, '-');
 
-  const end = new Date(start)
-  end.setDate(start.getDate() + 6)
-
-  return {
-    start: start.toISOString().split('T')[0],
-    end: end.toISOString().split('T')[0]
-  }
-}
-
-const wouldExceedWeekLimit = (selectedDate) => {
-  const { start, end } = getWeekRange(selectedDate)
+const getWeekStart = (dateStr) => {
+  const normalizedDate = normalizeDate(dateStr);
+  const d = new Date(`${normalizedDate}T12:00:00`);
+  d.setDate(d.getDate() - d.getDay());
+  return d.toISOString().split('T')[0];
+};
 
-  const count = bookings.value.filter(b => {
-    if (!b.date) return false
+const blockedWeekStartSet = computed(() =>
+  new Set(providerClientBlocks.value.fully_blocked_weeks ?? [])
+);
 
-    const normalizedDate = b.date.replace(/\//g, '-')
+const getServerWeekCount = (selectedDate) => {
+  const weekStart = getWeekStart(selectedDate);
+  return (providerClientBlocks.value.existing_schedules ?? []).filter(
+    (schedule) => getWeekStart(schedule.date) === weekStart
+  ).length;
+};
 
-    return normalizedDate >= start && normalizedDate <= end
-  }).length
+const getLocalWeekCount = (selectedDate) => {
+  const weekStart = getWeekStart(selectedDate);
+  return bookings.value.filter((booking) => getWeekStart(booking.date) === weekStart).length;
+};
 
-  return count >= 2
-}
+const wouldExceedWeekLimit = (selectedDate) => {
+  const serverCount = getServerWeekCount(selectedDate);
+  const localCount = getLocalWeekCount(selectedDate);
+  return (serverCount + localCount) >= 2;
+};
 
 
 const dateOptions = (d) => {
   const today = date.formatDate(new Date(), 'YYYY/MM/DD');
   if (d < today) return false;
 
-  const raw = d.replace(/\//g, '-');
+  const raw = normalizeDate(d);
   const parsed = new Date(`${raw}T12:00:00`);
   const dayOfWeek = parsed.getDay();
 
   const isWorkingDay = availableWeekDays.value.includes(dayOfWeek);
   const isBlocked = blockedDateSet.value.has(raw);
+  const isWeekBlocked = blockedWeekStartSet.value.has(getWeekStart(raw));
 
-  if (!isWorkingDay || isBlocked) return false;
+  if (!isWorkingDay || isBlocked || isWeekBlocked) return false;
 
-if (wouldExceedWeekLimit(raw)) return false;
+  if (wouldExceedWeekLimit(raw)) return false;
 
-return true;
+  return true;
 };
 
 
@@ -226,7 +234,7 @@ const onDateSelected = (val) => {
   if (!val) return;
 
   selectedDate.value = null;
-  const valFormatted = val.replace(/\//g, '-');
+  const valFormatted = normalizeDate(val);
 
 
   const blocksOfDate = blockedDays.value.filter(
@@ -251,18 +259,26 @@ const onDateSelected = (val) => {
   }
 
   
-  const existingBookingBlocks = bookings.value
+  const localBookingBlocks = bookings.value
     .filter(b => b.date.replace(/\//g, '-') === valFormatted)
     .map(b => ({
       init_hour: `${b.slot.startHour}:00:00`,
       end_hour: `${b.slot.endHour}:00:00`
     }));
 
+  const serverBookingBlocks = (providerClientBlocks.value.existing_schedules ?? [])
+    .filter((schedule) => normalizeDate(schedule.date) === valFormatted)
+    .map((schedule) => ({
+      init_hour: schedule.start_time,
+      end_hour: schedule.end_time,
+    }));
+
   
   const partialBlocks = [
     ...blocksOfDate,
     ...workingDayBlocks,
-    ...existingBookingBlocks
+    ...localBookingBlocks,
+    ...serverBookingBlocks,
   ];
 
   // fluxo
@@ -303,17 +319,28 @@ const onDateSelected = (val) => {
 
 const loadAvailability = async () => {
   loadingAvailability.value = true;
+
   try {
-    const [wd, bd] = await Promise.all([
+    const clientId = store.user?.client?.id;
+    const defaultClientBlocks = { existing_schedules: [], fully_blocked_weeks: [] };
+
+    const [wd, bd, clientBlocks] = await Promise.all([
       getProviderWorkingDays(props.provider.provider_id),
       getProviderBlockedDays(props.provider.provider_id),
-      // getProviderWeekDatesBlocks(props.provider_id, provider.client_id)
+      clientId
+        ? getClientProviderBlocks(clientId, props.provider.provider_id)
+        : Promise.resolve(defaultClientBlocks),
     ]);
+
     workingDays.value = wd ?? [];
     blockedDays.value = bd ?? [];
+    providerClientBlocks.value = clientBlocks ?? defaultClientBlocks;
+
   } catch {
     workingDays.value = [];
     blockedDays.value = [];
+    providerClientBlocks.value = { existing_schedules: [], fully_blocked_weeks: [] };
+
   } finally {
     loadingAvailability.value = false;
   }