Pārlūkot izejas kodu

Merge branch 'feature/diariaapp-kay-agendamentos-sob-medida-apps' of Softpar/sfp_front_vue_diarista_cliente into development

zntt 1 nedēļu atpakaļ
vecāks
revīzija
8c206bece9

+ 5 - 0
src/api/providerAvailability.js

@@ -9,3 +9,8 @@ export const getProviderBlockedDays = async (providerId) => {
   const { data } = await api.get(`/provider/blocked-days/${providerId}`)
   return data.payload
 }
+
+export const getProviderSchedules = async (providerId) => {
+  const response = await api.get(`/provider/${providerId}/schedules`)
+  return response.data.payload
+}

+ 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,

+ 158 - 57
src/pages/search/components/SchedulingDialog.vue

@@ -129,10 +129,12 @@ 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';
+import OrderSummaryDialog from './OrderSummaryDialog.vue'
 
 const props = defineProps({
   provider: {
@@ -145,108 +147,206 @@ 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(() =>
+  [...new Set(workingDays.value.map((wd) => wd.day))]
+);
+
+
+const blockedDateSet = computed(() =>
+  new Set(
+    blockedDays.value
+      .filter((bd) => bd.period === 'all')
+      .map((bd) => bd.date)
+  )
+);
+
+
+const normalizeDate = (dateStr) => (dateStr ?? '').replace(/\//g, '-');
+
+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 blockedWeekStartSet = computed(() =>
+  new Set(providerClientBlocks.value.fully_blocked_weeks ?? [])
+);
+
+const getServerWeekCount = (selectedDate) => {
+  const weekStart = getWeekStart(selectedDate);
+  return (providerClientBlocks.value.existing_schedules ?? []).filter(
+    (schedule) => getWeekStart(schedule.date) === weekStart
+  ).length;
+};
+
+const getLocalWeekCount = (selectedDate) => {
+  const weekStart = getWeekStart(selectedDate);
+  return bookings.value.filter((booking) => getWeekStart(booking.date) === weekStart).length;
+};
+
+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 = 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 || isWeekBlocked) return false;
+
+  if (wouldExceedWeekLimit(raw)) return false;
+
+  return true;
+};
+
 
 const onDateSelected = (val) => {
   if (!val) return;
+
   selectedDate.value = null;
-  const valFormatted = val.replace(/\//g, '-');
+  const valFormatted = normalizeDate(val);
+
 
   const blocksOfDate = blockedDays.value.filter(
     (bd) => bd.date === valFormatted && bd.period !== 'all'
   );
 
+  
   const dayOfWeek = new Date(`${valFormatted}T12:00:00`).getDay();
+
   const dayPeriods = workingDays.value
     .filter((wd) => wd.day === dayOfWeek)
     .map((wd) => wd.period);
 
   const workingDayBlocks = [];
+
   if (!dayPeriods.includes('afternoon')) {
     workingDayBlocks.push({ init_hour: '14:00:00', end_hour: '20:00:00' });
   }
+
   if (!dayPeriods.includes('morning')) {
-    workingDayBlocks.push({ init_hour: '7:00:00', end_hour: '13:00:00' });
+    workingDayBlocks.push({ init_hour: '07:00:00', end_hour: '13:00:00' });
   }
 
-  const partialBlocks = [...blocksOfDate, ...workingDayBlocks];
+  
+  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,
+    ...localBookingBlocks,
+    ...serverBookingBlocks,
+  ];
+
+  // fluxo
+  $q.dialog({
+  component: ServiceSelectionSheet,
+  componentProps: {
+    provider: props.provider,
+    selectedDate: val,
+    partialBlocks
+  },
+}).onOk(({ serviceType, date: date_, provider: prov }) => {
 
   $q.dialog({
-    component: ServiceSelectionSheet,
-    componentProps: { provider: props.provider, selectedDate: val, partialBlocks },
-  }).onOk(({ serviceType, date: date_, provider: prov }) => {
-    $q.dialog({
-      component: ServiceTimeSelectionDialog,
-      componentProps: { serviceType, selectedDate: date_, provider: prov, partialBlocks },
-    }).onOk((booking) => {
-      $q.dialog({
-        component: OrderSummaryDialog,
-        componentProps: { provider: props.provider, initialBooking: booking },
-      });
-    });
-  });
-};
+    component: ServiceTimeSelectionDialog,
+    componentProps: {
+      serviceType,
+      selectedDate: date_,
+      provider: prov,
+      partialBlocks
+    },
+  }).onOk((booking) => {
 
-const selectedDate = ref(null);
-const workingDays = ref([]);
-const blockedDays = ref([]);
-const loadingAvailability = ref(true);
+    bookings.value.push(booking);
 
-const reviews = ref([]);
-const loadingReviews = ref(true);
+    $q.dialog({
+      component: OrderSummaryDialog,
+      componentProps: {
+        provider: props.provider,
+        initialBooking: booking
+      }
+    });
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+  });
 
-const avatarStyle = computed(() => {
-  const idx = (props.provider?.provider_id ?? 0) % avatarColors.length;
-  return avatarColors[idx];
 });
-
-const clientAvatarStyle = (review) => {
-  const idx = (review.id ?? 0) % avatarColors.length;
-  return avatarColors[idx];
 };
 
-const availableWeekDays = computed(() =>
-  [...new Set(workingDays.value.map((wd) => wd.day))]
-);
-
-const blockedDateSet = computed(() =>
-  new Set(blockedDays.value.map((bd) => bd.date && bd.period == 'all'))
-);
-
-const dateOptions = (d) => {
-  const today = date.formatDate(new Date(), 'YYYY/MM/DD');
-  if (d < today) return false;
-  const raw = d.replace(/\//g, '-');
-  const parsed = new Date(`${raw}T12:00:00`);
-  const dayOfWeek = parsed.getDay();
-  const isWorkingDay = availableWeekDays.value.includes(dayOfWeek);
-  const isBlocked = blockedDateSet.value.has(raw);
-  return isWorkingDay && !isBlocked;
-};
 
 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;
   }
 };
 
+
 const loadReviews = async () => {
   loadingReviews.value = true;
   try {
@@ -260,7 +360,8 @@ const loadReviews = async () => {
 };
 
 onMounted(() => {
-  Promise.all([loadAvailability(), loadReviews()]);
+  loadAvailability();
+  loadReviews();
 });
 </script>