diff --git a/app/explore/experts/[consultantId]/page.tsx b/app/explore/experts/[consultantId]/page.tsx index 8a2aa2c..fa99907 100644 --- a/app/explore/experts/[consultantId]/page.tsx +++ b/app/explore/experts/[consultantId]/page.tsx @@ -10,7 +10,7 @@ import { } from "@/hooks/useUserData"; import { TConsultantProfile } from "@/types/consultant"; -import { TSlotTiming } from "@/types/slots"; +import { TSlotTiming, TWeeklySlot, TCustomSlot } from "@/types/slots"; import { ConsultantReview, ConsultationPlan, @@ -31,12 +31,14 @@ import { dayMap, convertUTCToLocalDate, formatTime, - getDayAfter, createWeeklySlot, createCustomSlot, mergeOverlappingSlots, getLocalDay, isSameLocalDay, + normalizeWeeklySlot, + normalizeCustomSlot, + isSlotRelevantForDay, } from "./utils"; import { useTimezone } from "./hooks/useTimezone"; @@ -58,7 +60,6 @@ export default function ExpertProfile( }>, ) { const params = use(props.params); - const searchParams = use(props.searchParams); const { timezone: browserTimezone, isLoading: isTimezoneLoading } = useTimezone(); const [userDetails, setUserDetails] = useState(null); @@ -122,27 +123,11 @@ export default function ExpertProfile( console.log('Selected day:', selectedDay); // Get slots for the selected day - const relevantSlots = consultantDetails.slotsOfAvailabilityWeekly.filter(slot => { - const startDay = slot.dayOfWeekforStartTimeInUTC; - const endDay = slot.dayOfWeekforEndTimeInUTC; - - const isRelevant = startDay === selectedDay || - (startDay !== endDay && endDay === selectedDay) || - (startDay !== endDay && getDayAfter(startDay) === selectedDay); - - if (isRelevant) { - console.log('Found relevant slot:', { - startDay, - endDay, - startTime: slot.slotStartTimeInUTC, - endTime: slot.slotEndTimeInUTC - }); - } - - return isRelevant; - }); + const relevantSlots = consultantDetails.slotsOfAvailabilityWeekly + .map(normalizeWeeklySlot) + .filter(slot => isSlotRelevantForDay(slot, selectedDay, timezone)); - const weeklySlots = relevantSlots.flatMap(slot => { + const weeklySlots = relevantSlots.map(slot => { // Convert UTC times to local date objects const startDateTime = convertUTCToLocalDate(slot.slotStartTimeInUTC, selectedDate, timezone); let endDateTime = convertUTCToLocalDate(slot.slotEndTimeInUTC, selectedDate, timezone); @@ -155,54 +140,9 @@ export default function ExpertProfile( timezone }); - // If this slot ends on the next day - if (slot.dayOfWeekforStartTimeInUTC !== slot.dayOfWeekforEndTimeInUTC) { - if (slot.dayOfWeekforStartTimeInUTC === selectedDay) { - // For slots starting on selected day and ending next day, - // show only the portion until midnight - const nextDay = new Date(startDateTime); - nextDay.setDate(nextDay.getDate() + 1); - nextDay.setHours(0, 0, 0, 0); - console.log('Slot crosses to next day, ending at midnight:', nextDay.toISOString()); - return [createWeeklySlot(slot, selectedDate, startDateTime, nextDay, timezone)]; - } else if (slot.dayOfWeekforEndTimeInUTC === selectedDay) { - // For slots ending on selected day (started previous day), - // show only the portion from midnight - const thisDay = new Date(endDateTime); - thisDay.setHours(0, 0, 0, 0); - console.log('Slot started previous day, starting at midnight:', thisDay.toISOString()); - return [createWeeklySlot(slot, selectedDate, thisDay, endDateTime, timezone)]; - } else if (getDayAfter(slot.dayOfWeekforStartTimeInUTC) === selectedDay) { - // For slots spanning multiple days, show full day - const thisDay = new Date(selectedDate); - thisDay.setHours(0, 0, 0, 0); - const nextDay = new Date(selectedDate); - nextDay.setDate(nextDay.getDate() + 1); - nextDay.setHours(0, 0, 0, 0); - console.log('Slot spans multiple days:', { - start: thisDay.toISOString(), - end: nextDay.toISOString() - }); - return [createWeeklySlot(slot, selectedDate, thisDay, nextDay, timezone)]; - } - } - - // For slots within the same day - if (endDateTime <= startDateTime) { - endDateTime = new Date(endDateTime.getTime() + 24 * 60 * 60 * 1000); - console.log('Adjusted end time for same day slot:', endDateTime.toISOString()); - } - - // Only include slots that overlap with the selected date in local time - if (isSameLocalDay(startDateTime, selectedDate, timezone) || - isSameLocalDay(endDateTime, selectedDate, timezone)) { - console.log('Slot overlaps with selected date'); - return [createWeeklySlot(slot, selectedDate, startDateTime, endDateTime, timezone)]; - } - - console.log('Slot does not overlap with selected date'); - return []; - }).filter(Boolean) as TSlotTiming[]; + // Create the slot timing + return createWeeklySlot(slot, selectedDate, startDateTime, endDateTime, timezone); + }); // Sort slots by start time const sortedSlots = weeklySlots.sort((a, b) => @@ -218,6 +158,7 @@ export default function ExpertProfile( setSlotTimings(mergedSlots); } else if (consultantDetails.scheduleType === "CUSTOM") { const customSlots = consultantDetails.slotsOfAvailabilityCustom + .map(normalizeCustomSlot) .filter(slot => { const startDateTime = new Date(slot.slotStartTimeInUTC); return isSameLocalDay(startDateTime, selectedDate, timezone); @@ -225,12 +166,6 @@ export default function ExpertProfile( .map(slot => { const startDateTime = new Date(slot.slotStartTimeInUTC); const endDateTime = new Date(slot.slotEndTimeInUTC); - - // If end time is before start time, it means it ends next day - if (endDateTime <= startDateTime) { - endDateTime.setDate(endDateTime.getDate() + 1); - } - return createCustomSlot(slot, selectedDate, startDateTime, endDateTime, timezone); }); @@ -321,73 +256,49 @@ export default function ExpertProfile( if (consultantDetails.scheduleType === "WEEKLY") { // Convert Date objects to strings for weekly slots - const weeklySlots = consultantDetails.slotsOfAvailabilityWeekly.map( - (slot) => ({ - id: slot.id, - dayOfWeekforStartTimeInUTC: slot.dayOfWeekforStartTimeInUTC, - slotStartTimeInUTC: - typeof slot.slotStartTimeInUTC === "string" - ? slot.slotStartTimeInUTC - : slot.slotStartTimeInUTC.toISOString(), - dayOfWeekforEndTimeInUTC: slot.dayOfWeekforEndTimeInUTC, - slotEndTimeInUTC: - typeof slot.slotEndTimeInUTC === "string" - ? slot.slotEndTimeInUTC - : slot.slotEndTimeInUTC.toISOString(), - }), - ); + const weeklySlots = consultantDetails.slotsOfAvailabilityWeekly.map(normalizeWeeklySlot); return ( + onSlotSelect={slot => { + const normalizedSlot = normalizeWeeklySlot(slot); setSelectedSlot({ - slotId: slot.id, + slotId: normalizedSlot.id, dateInISO: new Date().toISOString(), - dayOfWeek: slot.dayOfWeekforStartTimeInUTC, - slotStartTimeInUTC: slot.slotStartTimeInUTC, - slotEndTimeInUTC: slot.slotEndTimeInUTC, - slotOfAvailabilityId: slot.id, + dayOfWeek: normalizedSlot.dayOfWeekforStartTimeInUTC, + slotStartTimeInUTC: normalizedSlot.slotStartTimeInUTC, + slotEndTimeInUTC: normalizedSlot.slotEndTimeInUTC, + slotOfAvailabilityId: normalizedSlot.id, slotOfAppointmentId: "", - localStartTime: formatTime(slot.slotStartTimeInUTC, timezone), - localEndTime: formatTime(slot.slotEndTimeInUTC, timezone), - }) - } + localStartTime: formatTime(normalizedSlot.slotStartTimeInUTC, timezone), + localEndTime: formatTime(normalizedSlot.slotEndTimeInUTC, timezone), + }); + }} selectedSlotId={selectedSlot?.slotId} /> ); } else if (consultantDetails.scheduleType === "CUSTOM") { // Convert Date objects to strings for custom slots - const customSlots = consultantDetails.slotsOfAvailabilityCustom.map( - (slot) => ({ - id: slot.id, - slotStartTimeInUTC: - typeof slot.slotStartTimeInUTC === "string" - ? slot.slotStartTimeInUTC - : slot.slotStartTimeInUTC.toISOString(), - slotEndTimeInUTC: - typeof slot.slotEndTimeInUTC === "string" - ? slot.slotEndTimeInUTC - : slot.slotEndTimeInUTC.toISOString(), - }), - ); + const customSlots = consultantDetails.slotsOfAvailabilityCustom.map(normalizeCustomSlot); return ( + onSlotSelect={slot => { + const normalizedSlot = normalizeCustomSlot(slot); setSelectedSlot({ - slotId: slot.id, - dateInISO: new Date(slot.slotStartTimeInUTC).toISOString(), - dayOfWeek: dayMap[new Date(slot.slotStartTimeInUTC).getDay()], - slotStartTimeInUTC: slot.slotStartTimeInUTC, - slotEndTimeInUTC: slot.slotEndTimeInUTC, - slotOfAvailabilityId: slot.id, + slotId: normalizedSlot.id, + dateInISO: new Date(normalizedSlot.slotStartTimeInUTC).toISOString(), + dayOfWeek: dayMap[new Date(normalizedSlot.slotStartTimeInUTC).getDay()], + slotStartTimeInUTC: normalizedSlot.slotStartTimeInUTC, + slotEndTimeInUTC: normalizedSlot.slotEndTimeInUTC, + slotOfAvailabilityId: normalizedSlot.id, slotOfAppointmentId: "", - localStartTime: formatTime(slot.slotStartTimeInUTC, timezone), - localEndTime: formatTime(slot.slotEndTimeInUTC, timezone), - }) - } + localStartTime: formatTime(normalizedSlot.slotStartTimeInUTC, timezone), + localEndTime: formatTime(normalizedSlot.slotEndTimeInUTC, timezone), + }); + }} selectedSlotId={selectedSlot?.slotId} /> ); @@ -514,7 +425,7 @@ export default function ExpertProfile( {userDetails.name} has experience across multiple industries, with a particular focus on{" "} {consultantDetails?.subDomains - ?.map((domain) => domain.name) + ?.map((domain: { name: string }) => domain.name) .join(", ")} .

@@ -526,7 +437,7 @@ export default function ExpertProfile(

{userDetails.name} focuses on{" "} - {consultantDetails.tags?.map((tag) => tag.name).join(", ")}. + {consultantDetails.tags?.map((tag: { name: string }) => tag.name).join(", ")}.

diff --git a/app/explore/experts/[consultantId]/utils.ts b/app/explore/experts/[consultantId]/utils.ts index 0ed3bd7..8837553 100644 --- a/app/explore/experts/[consultantId]/utils.ts +++ b/app/explore/experts/[consultantId]/utils.ts @@ -11,6 +11,44 @@ export const dayMap: Record = { 6: DayOfWeek.SATURDAY }; +export const dayToNumber: Record = { + [DayOfWeek.SUNDAY]: 0, + [DayOfWeek.MONDAY]: 1, + [DayOfWeek.TUESDAY]: 2, + [DayOfWeek.WEDNESDAY]: 3, + [DayOfWeek.THURSDAY]: 4, + [DayOfWeek.FRIDAY]: 5, + [DayOfWeek.SATURDAY]: 6 +}; + +// Ensure UTC time is always a string +export function normalizeUTCTime(time: string | Date): string { + return typeof time === 'string' ? time : time.toISOString(); +} + +// Normalize weekly slot to ensure all times are strings +export function normalizeWeeklySlot(slot: TWeeklySlot): TWeeklySlot & { + slotStartTimeInUTC: string; + slotEndTimeInUTC: string; +} { + return { + ...slot, + slotStartTimeInUTC: normalizeUTCTime(slot.slotStartTimeInUTC), + slotEndTimeInUTC: normalizeUTCTime(slot.slotEndTimeInUTC) + }; +} + +// Normalize custom slot to ensure all times are strings +export function normalizeCustomSlot(slot: TCustomSlot): TCustomSlot & { + slotStartTimeInUTC: string; + slotEndTimeInUTC: string; +} { + return { + ...slot, + slotStartTimeInUTC: normalizeUTCTime(slot.slotStartTimeInUTC), + slotEndTimeInUTC: normalizeUTCTime(slot.slotEndTimeInUTC) + }; +} export function getLocalDay(date: Date, timezone?: string | null): number { if (!timezone) return date.getDay(); @@ -30,9 +68,9 @@ export function getLocalDay(date: Date, timezone?: string | null): number { } } -export function convertUTCToLocalDate(utcTime: string | Date, selectedDate: Date, timezone?: string | null): Date { +export function convertUTCToLocalDate(utcTime: string, selectedDate: Date, timezone?: string | null): Date { // Parse the UTC time from 1970-01-01 format - const utcDate = typeof utcTime === 'string' ? new Date(utcTime) : utcTime; + const utcDate = new Date(utcTime); const utcHours = utcDate.getUTCHours(); const utcMinutes = utcDate.getUTCMinutes(); @@ -69,6 +107,17 @@ export function convertUTCToLocalDate(utcTime: string | Date, selectedDate: Date const localDate = new Date(year, month - 1, day, hours, minutes, seconds); + // Adjust date if the local time is on the previous day + const localDay = getLocalDay(localDate, timezone); + const selectedDay = getLocalDay(selectedDate, timezone); + if (localDay !== selectedDay) { + if ((selectedDay === 0 && localDay === 6) || localDay === selectedDay - 1) { + localDate.setDate(localDate.getDate() + 1); + } else if ((selectedDay === 6 && localDay === 0) || localDay === selectedDay + 1) { + localDate.setDate(localDate.getDate() - 1); + } + } + console.log('UTC to Local conversion:', { utcTime: utcDateTime.toISOString(), timezone, @@ -157,6 +206,37 @@ export function isSameLocalDay(date1: Date, date2: Date, timezone?: string | nul } } +export function isSlotRelevantForDay( + slot: TWeeklySlot, + selectedDay: DayOfWeek, + timezone?: string | null +): boolean { + const startDay = slot.dayOfWeekforStartTimeInUTC; + const endDay = slot.dayOfWeekforEndTimeInUTC; + + // Direct match + if (startDay === selectedDay || endDay === selectedDay) { + return true; + } + + // Handle overnight slots + if (startDay !== endDay) { + const selectedDayNum = dayToNumber[selectedDay]; + const startDayNum = dayToNumber[startDay]; + const endDayNum = dayToNumber[endDay]; + + // Handle week wrap-around (e.g., Saturday to Sunday) + if (startDayNum > endDayNum) { + return selectedDayNum >= startDayNum || selectedDayNum <= endDayNum; + } + + // Normal case + return selectedDayNum >= startDayNum && selectedDayNum <= endDayNum; + } + + return false; +} + export function createWeeklySlot( slot: TWeeklySlot, selectedDate: Date, @@ -165,42 +245,38 @@ export function createWeeklySlot( timezone?: string | null ): TSlotTiming { let adjustedEndDateTime = new Date(endDateTime); + const normalizedSlot = normalizeWeeklySlot(slot); // Handle slots that cross midnight - if (slot.dayOfWeekforStartTimeInUTC !== slot.dayOfWeekforEndTimeInUTC) { - // If end time is 00:00, it means it ends at midnight of the next day - if (adjustedEndDateTime.getHours() === 0 && adjustedEndDateTime.getMinutes() === 0) { - adjustedEndDateTime = new Date(endDateTime); - adjustedEndDateTime.setDate(adjustedEndDateTime.getDate() + 1); - } - // If end time is before start time, it means it ends next day - else if (adjustedEndDateTime <= startDateTime) { - adjustedEndDateTime = new Date(endDateTime); - adjustedEndDateTime.setDate(adjustedEndDateTime.getDate() + 1); - } + if (slot.dayOfWeekforStartTimeInUTC !== slot.dayOfWeekforEndTimeInUTC || + endDateTime <= startDateTime || + (endDateTime.getHours() === 0 && endDateTime.getMinutes() === 0)) { + + adjustedEndDateTime = new Date(endDateTime); + adjustedEndDateTime.setDate(adjustedEndDateTime.getDate() + 1); } const slotTiming = { - slotId: slot.id, + slotId: normalizedSlot.id, dateInISO: selectedDate.toISOString(), - dayOfWeek: slot.dayOfWeekforStartTimeInUTC, + dayOfWeek: normalizedSlot.dayOfWeekforStartTimeInUTC, slotStartTimeInUTC: startDateTime.toISOString(), slotEndTimeInUTC: adjustedEndDateTime.toISOString(), - slotOfAvailabilityId: slot.id, + slotOfAvailabilityId: normalizedSlot.id, slotOfAppointmentId: "", localStartTime: formatTime(startDateTime, timezone), localEndTime: formatTime(adjustedEndDateTime, timezone), }; console.log('Created weekly slot:', { - startDay: slot.dayOfWeekforStartTimeInUTC, - endDay: slot.dayOfWeekforEndTimeInUTC, - utcStart: slot.slotStartTimeInUTC, - utcEnd: slot.slotEndTimeInUTC, + startDay: normalizedSlot.dayOfWeekforStartTimeInUTC, + endDay: normalizedSlot.dayOfWeekforEndTimeInUTC, + utcStart: normalizedSlot.slotStartTimeInUTC, + utcEnd: normalizedSlot.slotEndTimeInUTC, localStart: slotTiming.localStartTime, localEnd: slotTiming.localEndTime, timezone, - crossesMidnight: slot.dayOfWeekforStartTimeInUTC !== slot.dayOfWeekforEndTimeInUTC + crossesMidnight: normalizedSlot.dayOfWeekforStartTimeInUTC !== normalizedSlot.dayOfWeekforEndTimeInUTC }); return slotTiming; @@ -214,28 +290,30 @@ export function createCustomSlot( timezone?: string | null ): TSlotTiming { let adjustedEndDateTime = new Date(endDateTime); + const normalizedSlot = normalizeCustomSlot(slot); - // If end time is before start time, it means the slot crosses midnight - if (adjustedEndDateTime <= startDateTime) { + // Handle slots that cross midnight + if (endDateTime <= startDateTime || + (endDateTime.getHours() === 0 && endDateTime.getMinutes() === 0)) { adjustedEndDateTime = new Date(endDateTime); adjustedEndDateTime.setDate(adjustedEndDateTime.getDate() + 1); } const slotTiming = { - slotId: slot.id, + slotId: normalizedSlot.id, dateInISO: selectedDate.toISOString(), dayOfWeek: dayMap[getLocalDay(startDateTime, timezone)], slotStartTimeInUTC: startDateTime.toISOString(), slotEndTimeInUTC: adjustedEndDateTime.toISOString(), - slotOfAvailabilityId: slot.id, + slotOfAvailabilityId: normalizedSlot.id, slotOfAppointmentId: "", localStartTime: formatTime(startDateTime, timezone), localEndTime: formatTime(adjustedEndDateTime, timezone), }; console.log('Created custom slot:', { - utcStart: slot.slotStartTimeInUTC, - utcEnd: slot.slotEndTimeInUTC, + utcStart: normalizedSlot.slotStartTimeInUTC, + utcEnd: normalizedSlot.slotEndTimeInUTC, localStart: slotTiming.localStartTime, localEnd: slotTiming.localEndTime, timezone, @@ -275,4 +353,3 @@ export function mergeOverlappingSlots(slots: TSlotTiming[], timezone?: string | return [...acc, curr]; }, []); } -