From 84d1ce5bff019b875b75ed62a8c685135cc7da07 Mon Sep 17 00:00:00 2001 From: Dhairyashil Shinde <93669429+dhairyashiil@users.noreply.github.com> Date: Thu, 8 Jan 2026 23:31:59 +0530 Subject: [PATCH] feat(companion): availability detail page for android and extension (#26560) * Availability Detail Page Header and fix event type payment amount badge, and index ui bug bottom navbar * feat(companion): implement read-only availability detail page for Android/Extension - Convert AvailabilityDetailScreen.tsx to read-only display with expandable sections - Add header dropdown menu with edit options in availability-detail.tsx - Update edit-availability-name.tsx with modal presentation pattern - Update edit-availability-hours.tsx with modal presentation pattern - Update edit-availability-override.tsx with modal presentation pattern - Match iOS flow: read-only detail page with edit actions via header menu * fix header on Availability Detail screen for android * make dialoges center on extension * update padding * AvailabilityDetailScreen for android and extension * added click to modal feat * fix Date Overrides Card was always shown --------- Co-authored-by: Volnei Munhoz --- .../app/(tabs)/(availability)/_layout.tsx | 2 +- .../(availability)/availability-detail.tsx | 132 ++- .../(event-types)/event-type-detail.tsx | 40 - companion/app/(tabs)/_layout.tsx | 6 + companion/app/edit-availability-hours.tsx | 35 +- companion/app/edit-availability-name.tsx | 28 +- companion/app/edit-availability-override.tsx | 28 +- .../EventTypeListItemParts.tsx | 10 +- .../useEventTypeListItemData.ts | 16 +- .../screens/AvailabilityDetailScreen.ios.tsx | 93 +- .../screens/AvailabilityDetailScreen.tsx | 1006 ++++------------- .../screens/AvailabilityListScreen.tsx | 18 +- .../screens/BookingDetailScreen.tsx | 2 +- .../screens/EditAvailabilityDayScreen.tsx | 233 ++-- .../screens/EditAvailabilityHoursScreen.tsx | 74 +- .../screens/EditAvailabilityNameScreen.tsx | 143 ++- .../EditAvailabilityOverrideScreen.tsx | 285 +++-- 17 files changed, 941 insertions(+), 1210 deletions(-) diff --git a/companion/app/(tabs)/(availability)/_layout.tsx b/companion/app/(tabs)/(availability)/_layout.tsx index d175a850d2..c4c05b1782 100644 --- a/companion/app/(tabs)/(availability)/_layout.tsx +++ b/companion/app/(tabs)/(availability)/_layout.tsx @@ -5,7 +5,7 @@ export default function AvailabilityLayout() { return ( - + ); } diff --git a/companion/app/(tabs)/(availability)/availability-detail.tsx b/companion/app/(tabs)/(availability)/availability-detail.tsx index 6aa2766ca3..f31ab0aecb 100644 --- a/companion/app/(tabs)/(availability)/availability-detail.tsx +++ b/companion/app/(tabs)/(availability)/availability-detail.tsx @@ -1,47 +1,127 @@ +import { Ionicons } from "@expo/vector-icons"; import { Stack, useLocalSearchParams, useRouter } from "expo-router"; -import { useRef } from "react"; -import { Platform } from "react-native"; +import { useCallback, useRef } from "react"; +import { Text, TouchableOpacity } from "react-native"; import { AvailabilityDetailScreen, type AvailabilityDetailScreenHandle, } from "@/components/screens/AvailabilityDetailScreen"; +import { HeaderButtonWrapper } from "@/components/HeaderButtonWrapper"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +// Type for action handlers exposed by AvailabilityDetailScreen +type ActionHandlers = { + handleSetAsDefault: () => void; + handleDelete: () => void; +}; export default function AvailabilityDetail() { const { id } = useLocalSearchParams<{ id: string }>(); const router = useRouter(); + + // Ref to store action handlers from AvailabilityDetailScreen + const actionHandlersRef = useRef(null); const screenRef = useRef(null); + // Callback to receive action handlers from AvailabilityDetailScreen + const handleActionsReady = useCallback((handlers: ActionHandlers) => { + actionHandlersRef.current = handlers; + }, []); + + // Navigation handlers for edit modal screens + const handleEditNameAndTimezone = useCallback(() => { + router.push(`/edit-availability-name?id=${id}` as never); + }, [router, id]); + + const handleEditWorkingHours = useCallback(() => { + router.push(`/edit-availability-hours?id=${id}` as never); + }, [router, id]); + + const handleEditOverride = useCallback(() => { + router.push(`/edit-availability-override?id=${id}` as never); + }, [router, id]); + + // Action handlers for inline actions + const handleSetAsDefault = useCallback(() => { + if (actionHandlersRef.current?.handleSetAsDefault) { + actionHandlersRef.current.handleSetAsDefault(); + } else if (screenRef.current?.setAsDefault) { + screenRef.current.setAsDefault(); + } + }, []); + + const handleDelete = useCallback(() => { + if (actionHandlersRef.current?.handleDelete) { + actionHandlersRef.current.handleDelete(); + } else if (screenRef.current?.delete) { + screenRef.current.delete(); + } + }, []); + if (!id) { return null; } - const handleSave = () => { - screenRef.current?.save(); - }; - return ( <> - + ( + + + + + + + + + + + Name and Timezone + + + + Working Hours + + + + Date Override + + + + + Set as Default + + + + + Delete Schedule + + + + + ), + }} + /> - {Platform.OS === "ios" && ( - - - router.back()}> - - - - - Edit Availability - - - - Save - - - - )} - - + ); } diff --git a/companion/app/(tabs)/(event-types)/event-type-detail.tsx b/companion/app/(tabs)/(event-types)/event-type-detail.tsx index d37455c468..dec8cde936 100644 --- a/companion/app/(tabs)/(event-types)/event-type-detail.tsx +++ b/companion/app/(tabs)/(event-types)/event-type-detail.tsx @@ -1256,46 +1256,6 @@ export default function EventTypeDetail() { contentContainerStyle={{ padding: 16, paddingBottom: 200 }} contentInsetAdjustmentBehavior="automatic" > - {/* Horizontal tabs only shown on web; Android uses header dropdown menu */} - - - - {tabs.map((tab) => ( - setActiveTab(tab.id)} - > - - - - {tab.label} - - - - ))} - - - - {activeTab === "basics" ? ( + { + router.back(); + }, [router]); + const handleDayPress = useCallback( (dayIndex: number) => { router.push(`/edit-availability-day?id=${id}&day=${dayIndex}` as never); @@ -42,7 +48,12 @@ export default function EditAvailabilityHours() { className="flex-1 items-center justify-center bg-white" style={{ paddingBottom: insets.bottom }} > - + ); @@ -50,7 +61,25 @@ export default function EditAvailabilityHours() { return ( - + ( + + + + + + ), + }} + /> ); diff --git a/companion/app/edit-availability-name.tsx b/companion/app/edit-availability-name.tsx index 5cd3a8cbb5..9ab548ff91 100644 --- a/companion/app/edit-availability-name.tsx +++ b/companion/app/edit-availability-name.tsx @@ -1,6 +1,7 @@ +import { Ionicons } from "@expo/vector-icons"; import { Stack, useLocalSearchParams, useRouter } from "expo-router"; import { useCallback, useEffect, useRef, useState } from "react"; -import { ActivityIndicator, Alert, Text, View } from "react-native"; +import { ActivityIndicator, Alert, Text, TouchableOpacity, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { HeaderButtonWrapper } from "@/components/HeaderButtonWrapper"; import type { EditAvailabilityNameScreenHandle } from "@/components/screens/EditAvailabilityNameScreen"; @@ -34,6 +35,10 @@ export default function EditAvailabilityName() { } }, [id, router]); + const handleClose = useCallback(() => { + router.back(); + }, [router]); + const handleSave = useCallback(() => { if (isSaving) return; screenRef.current?.submit(); @@ -49,7 +54,12 @@ export default function EditAvailabilityName() { className="flex-1 items-center justify-center bg-white" style={{ paddingBottom: insets.bottom }} > - + ); @@ -60,6 +70,20 @@ export default function EditAvailabilityName() { ( + + + + + + ), headerRight: () => ( { + router.back(); + }, [router]); + const handleSave = useCallback(() => { if (isSaving) return; screenRef.current?.submit(); @@ -66,7 +71,12 @@ export default function EditAvailabilityOverride() { className="flex-1 items-center justify-center bg-white" style={{ paddingBottom: insets.bottom }} > - + ); @@ -77,6 +87,20 @@ export default function EditAvailabilityOverride() { ( + + + + + + ), headerRight: () => ( {formattedPrice} + + + + {formattedPrice} + + ) : null} {/* Repeats Badge */} diff --git a/companion/components/event-type-list-item/useEventTypeListItemData.ts b/companion/components/event-type-list-item/useEventTypeListItemData.ts index c5dcccd573..dd0a425ac3 100644 --- a/companion/components/event-type-list-item/useEventTypeListItemData.ts +++ b/companion/components/event-type-list-item/useEventTypeListItemData.ts @@ -20,7 +20,21 @@ export function useEventTypeListItemData(item: EventType): EventTypeListItemData const formattedDuration = formatDuration(duration); const normalizedDescription = item.description ? normalizeMarkdown(item.description) : null; const hasPrice = item.price != null && item.price > 0; - const formattedPrice = hasPrice ? `${item.currency || "$"}${item.price}` : null; + let formattedPrice: string | null = null; + + if (hasPrice && item.price) { + // Price is in cents, convert to main unit + const priceAmount = item.price / 100; + try { + formattedPrice = new Intl.NumberFormat("en-US", { + style: "currency", + currency: item.currency?.toUpperCase() || "USD", + }).format(priceAmount); + } catch { + // Fallback if formatting fails + formattedPrice = `${item.currency?.toUpperCase() || "$"}${priceAmount.toFixed(2)}`; + } + } return { duration, diff --git a/companion/components/screens/AvailabilityDetailScreen.ios.tsx b/companion/components/screens/AvailabilityDetailScreen.ios.tsx index 33bbcfc40c..b90d5e19cb 100644 --- a/companion/components/screens/AvailabilityDetailScreen.ios.tsx +++ b/companion/components/screens/AvailabilityDetailScreen.ios.tsx @@ -114,8 +114,6 @@ export const AvailabilityDetailScreen = forwardRef< endTime: string; }[] >([]); - const [daysExpanded, setDaysExpanded] = useState(true); - const [overridesExpanded, setOverridesExpanded] = useState(true); const processScheduleData = useCallback( (scheduleData: NonNullable>>) => { @@ -327,46 +325,20 @@ export const AvailabilityDetailScreen = forwardRef< - {/* Working Hours Summary Card */} + {/* Weekly Schedule Card - Navigable */} - - - Working Hours - - {Object.keys(availability).length > 0 ? ( - - {formatAvailabilityDisplay(availability).map((line) => ( - - {line} - - ))} + router.push(`/edit-availability-hours?id=${id}` as never)}> + + Weekly Schedule + + + {enabledDaysCount} {enabledDaysCount === 1 ? "day" : "days"} + + - ) : ( - No availability set - )} - - - - {/* Weekly Schedule Card - Expandable */} - - setDaysExpanded(!daysExpanded)} - > - Weekly Schedule - - - {enabledDaysCount} {enabledDaysCount === 1 ? "day" : "days"} - - - - {daysExpanded && ( + {/* Expanded Day List */} {DAYS.map((day, dayIndex) => { const daySlots = availability[dayIndex] || []; @@ -412,38 +384,39 @@ export const AvailabilityDetailScreen = forwardRef< ); })} - )} + - {/* Timezone Card */} + {/* Timezone Card - Navigable */} - - Timezone - {timeZone} - + router.push(`/edit-availability-name?id=${id}` as never)}> + + Timezone + + {timeZone} + + + + - {/* Date Overrides Card - Expandable */} + {/* Date Overrides Card - Navigable */} {overrides.length > 0 && ( setOverridesExpanded(!overridesExpanded)} + onPress={() => router.push(`/edit-availability-override?id=${id}` as never)} > - Date Overrides - - - {overrides.length} {overrides.length === 1 ? "override" : "overrides"} - - + + Date Overrides + + + {overrides.length} {overrides.length === 1 ? "override" : "overrides"} + + + - - {overridesExpanded && ( + {/* Expanded Overrides List */} {overrides.map((override, index) => ( ))} - )} + )} diff --git a/companion/components/screens/AvailabilityDetailScreen.tsx b/companion/components/screens/AvailabilityDetailScreen.tsx index 745d564d1e..85c0dde807 100644 --- a/companion/components/screens/AvailabilityDetailScreen.tsx +++ b/companion/components/screens/AvailabilityDetailScreen.tsx @@ -1,20 +1,23 @@ +/** + * AvailabilityDetailScreen Component (Android/Extension) + * + * Read-only display of availability schedule. + * Editing is done via separate modal screens. + */ + import { Ionicons } from "@expo/vector-icons"; -import { GlassView } from "expo-glass-effect"; import { useRouter } from "expo-router"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react"; -import { ActivityIndicator, Alert, ScrollView, Switch, Text, TextInput, View } from "react-native"; +import { ActivityIndicator, Alert, ScrollView, Text, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AppPressable } from "@/components/AppPressable"; -import { FullScreenModal } from "@/components/FullScreenModal"; -import { TIMEZONES } from "@/constants/timezones"; import { CalComAPIService, type Schedule } from "@/services/calcom"; import type { ScheduleAvailability } from "@/services/types"; import { showErrorAlert } from "@/utils/alerts"; const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; -const DAY_ABBREVIATIONS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; -// Map day names to numbers - module scope for React Compiler optimization +// Map day names to numbers const DAY_NAME_TO_NUMBER: Record = { Sunday: 0, Monday: 1, @@ -32,16 +35,14 @@ const formatTime12Hour = (time24: string): string => { const min = minutes || "00"; const period = hour >= 12 ? "PM" : "AM"; const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour; - // Pad single-digit hours with leading zero for consistent width const hour12Padded = hour12.toString().padStart(2, "0"); return `${hour12Padded}:${min} ${period}`; }; -// Format availability for display +// Format availability for display - groups days with same time range const formatAvailabilityDisplay = ( availability: Record ): string[] => { - // Group slots by time range const timeRangeMap: Record = {}; Object.keys(availability).forEach((dayIndexStr) => { @@ -58,12 +59,11 @@ const formatAvailabilityDisplay = ( } }); - // Format each time range group const formatted: string[] = []; Object.keys(timeRangeMap).forEach((timeKey) => { const days = timeRangeMap[timeKey].sort((a, b) => a - b); const [startTime, endTime] = timeKey.split("-"); - const dayNames = days.map((day) => DAY_ABBREVIATIONS[day]).join(", "); + const dayNames = days.map((day) => DAYS[day]).join(", "); const timeRange = `${formatTime12Hour(startTime)} - ${formatTime12Hour(endTime)}`; formatted.push(`${dayNames}, ${timeRange}`); }); @@ -71,28 +71,24 @@ const formatAvailabilityDisplay = ( return formatted; }; -// Generate time options (15-minute intervals) -const generateTimeOptions = () => { - const options: string[] = []; - for (let hour = 0; hour < 24; hour++) { - for (let minute = 0; minute < 60; minute += 15) { - const h = hour.toString().padStart(2, "0"); - const m = minute.toString().padStart(2, "0"); - options.push(`${h}:${m}`); - } - } - return options; +// Format date for display +const formatDateForDisplay = (dateStr: string): string => { + if (!dateStr) return ""; + const date = new Date(`${dateStr}T00:00:00`); + return date.toLocaleDateString("en-US", { + weekday: "short", + month: "short", + day: "numeric", + year: "numeric", + }); }; -const TIME_OPTIONS = generateTimeOptions(); - export interface AvailabilityDetailScreenProps { id: string; - useNativeHeader?: boolean; + onActionsReady?: (handlers: { handleSetAsDefault: () => void; handleDelete: () => void }) => void; } export interface AvailabilityDetailScreenHandle { - save: () => void; setAsDefault: () => void; delete: () => void; refresh: () => void; @@ -101,42 +97,23 @@ export interface AvailabilityDetailScreenHandle { export const AvailabilityDetailScreen = forwardRef< AvailabilityDetailScreenHandle, AvailabilityDetailScreenProps ->(function AvailabilityDetailScreen({ id, useNativeHeader = false }, ref) { +>(function AvailabilityDetailScreen({ id, onActionsReady }, ref) { const router = useRouter(); const insets = useSafeAreaInsets(); const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); const [_schedule, setSchedule] = useState(null); const [scheduleName, setScheduleName] = useState(""); const [timeZone, setTimeZone] = useState(""); const [isDefault, setIsDefault] = useState(false); const [availability, setAvailability] = useState>({}); - const [showTimezoneModal, setShowTimezoneModal] = useState(false); - const [showTimePicker, setShowTimePicker] = useState<{ - dayIndex: number; - slotIndex: number; - type: "start" | "end"; - } | null>(null); const [overrides, setOverrides] = useState< { - date: string; // Format: "2024-05-20" - startTime: string; // Format: "12:00" or "00:00" for unavailable - endTime: string; // Format: "14:00" or "00:00" for unavailable + date: string; + startTime: string; + endTime: string; }[] >([]); - const [showOverrideModal, setShowOverrideModal] = useState(false); - const [editingOverride, setEditingOverride] = useState(null); - const [overrideDate, setOverrideDate] = useState(""); - const [overrideStartTime, setOverrideStartTime] = useState("09:00"); - const [overrideEndTime, setOverrideEndTime] = useState("17:00"); - const [isUnavailable, setIsUnavailable] = useState(false); - const [showOverrideTimePicker, setShowOverrideTimePicker] = useState<{ - type: "start" | "end"; - } | null>(null); - - // Use all supported timezones from the shared constants - const timezones = TIMEZONES; const processScheduleData = useCallback( (scheduleData: NonNullable>>) => { @@ -149,29 +126,24 @@ export const AvailabilityDetailScreen = forwardRef< setTimeZone(tz); setIsDefault(isDefaultSchedule); - // Convert availability array to day-indexed object const availabilityMap: Record = {}; const availabilityArray = scheduleData.availability; if (availabilityArray && Array.isArray(availabilityArray)) { availabilityArray.forEach((slot) => { - // Handle both string day names and number day formats let days: number[] = []; if (Array.isArray(slot.days)) { days = slot.days .map((day) => { - // If it's a day name string (e.g., "Sunday", "Monday") if (typeof day === "string" && DAY_NAME_TO_NUMBER[day] !== undefined) { return DAY_NAME_TO_NUMBER[day]; } - // If it's a number string (e.g., "0", "1") if (typeof day === "string") { const parsed = parseInt(day, 10); if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 6) { return parsed; } } - // If it's already a number if (typeof day === "number" && day >= 0 && day <= 6) { return day; } @@ -197,7 +169,6 @@ export const AvailabilityDetailScreen = forwardRef< setAvailability(availabilityMap); - // Load overrides if they exist const overridesArray = scheduleData.overrides; if (overridesArray && Array.isArray(overridesArray)) { const formattedOverrides = overridesArray.map((override) => { @@ -220,7 +191,6 @@ export const AvailabilityDetailScreen = forwardRef< try { scheduleData = await CalComAPIService.getScheduleById(Number(id)); } catch (error) { - // Avoid logging raw error objects from API calls (may contain sensitive response data). console.error("Error fetching schedule"); if (__DEV__) { const message = error instanceof Error ? error.message : String(error); @@ -246,139 +216,12 @@ export const AvailabilityDetailScreen = forwardRef< } }, [id, fetchSchedule]); - const toggleDay = (dayIndex: number) => { - setAvailability((prev) => { - const newAvailability = { ...prev }; - if (newAvailability[dayIndex] && newAvailability[dayIndex].length > 0) { - // Disable day - remove availability - delete newAvailability[dayIndex]; - } else { - // Enable day - add default time slot (9 AM - 5 PM) - newAvailability[dayIndex] = [ - { - days: [dayIndex.toString()], - startTime: "09:00:00", - endTime: "17:00:00", - }, - ]; - } - return newAvailability; - }); - }; - - const addTimeSlot = (dayIndex: number) => { - setAvailability((prev) => { - const newAvailability = { ...prev }; - if (!newAvailability[dayIndex]) { - newAvailability[dayIndex] = []; - } - newAvailability[dayIndex].push({ - days: [dayIndex.toString()], - startTime: "09:00:00", - endTime: "17:00:00", - }); - return newAvailability; - }); - }; - - const removeTimeSlot = (dayIndex: number, slotIndex: number) => { - setAvailability((prev) => { - const newAvailability = { ...prev }; - if (newAvailability[dayIndex]) { - newAvailability[dayIndex] = newAvailability[dayIndex].filter((_, i) => i !== slotIndex); - if (newAvailability[dayIndex].length === 0) { - delete newAvailability[dayIndex]; - } - } - return newAvailability; - }); - }; - - const updateTimeSlot = ( - dayIndex: number, - slotIndex: number, - type: "start" | "end", - time: string - ) => { - setAvailability((prev) => { - const newAvailability = { ...prev }; - if (newAvailability[dayIndex]?.[slotIndex]) { - const slot = { ...newAvailability[dayIndex][slotIndex] }; - if (type === "start") { - slot.startTime = `${time}:00`; - } else { - slot.endTime = `${time}:00`; - } - newAvailability[dayIndex][slotIndex] = slot; - } - return newAvailability; - }); - }; - - const handleSave = async () => { - // Validate availability name - if (!scheduleName.trim()) { - Alert.alert("Error", "Please enter an availability name"); + const handleSetAsDefault = useCallback(async () => { + if (isDefault) { + Alert.alert("Info", "This schedule is already set as default"); return; } - setSaving(true); - try { - // Convert availability object back to array format with day names - const availabilityArray: { - days: string[]; // Day names like "Monday", "Tuesday" - startTime: string; // Format: "09:00" - endTime: string; // Format: "10:00" - }[] = []; - - Object.keys(availability).forEach((dayIndexStr) => { - const dayIndex = Number(dayIndexStr); - const slots = availability[dayIndex]; - if (slots && slots.length > 0) { - slots.forEach((slot) => { - // Convert day number to day name - const dayName = DAYS[dayIndex]; - - // Format time from "09:00:00" to "09:00" (remove seconds) - const formatTimeForAPI = (time: string): string => { - return time.substring(0, 5); // Take only HH:MM - }; - - availabilityArray.push({ - days: [dayName], - startTime: formatTimeForAPI(slot.startTime), - endTime: formatTimeForAPI(slot.endTime), - }); - }); - } - }); - - // Format overrides for API (ensure times are in HH:MM format) - const formattedOverrides = overrides.map((override) => ({ - date: override.date, - startTime: override.startTime.substring(0, 5), // Ensure HH:MM format - endTime: override.endTime.substring(0, 5), // Ensure HH:MM format - })); - - await CalComAPIService.updateSchedule(Number(id), { - name: scheduleName.trim(), - timeZone, - availability: availabilityArray, - isDefault: isDefault, - overrides: formattedOverrides, - }); - - Alert.alert("Success", "Availability updated successfully", [ - { text: "OK", onPress: () => router.back() }, - ]); - setSaving(false); - } catch { - showErrorAlert("Error", "Failed to update availability. Please try again."); - setSaving(false); - } - }; - - const handleSetAsDefault = async () => { try { await CalComAPIService.updateSchedule(Number(id), { isDefault: true, @@ -388,9 +231,17 @@ export const AvailabilityDetailScreen = forwardRef< } catch { showErrorAlert("Error", "Failed to set availability as default. Please try again."); } - }; + }, [isDefault, id]); + + const handleDelete = useCallback(() => { + if (isDefault) { + Alert.alert( + "Cannot Delete", + "You cannot delete the default schedule. Please set another schedule as default first." + ); + return; + } - const handleDelete = () => { Alert.alert("Delete Availability", `Are you sure you want to delete "${scheduleName}"?`, [ { text: "Cancel", style: "cancel" }, { @@ -408,669 +259,198 @@ export const AvailabilityDetailScreen = forwardRef< }, }, ]); - }; + }, [isDefault, scheduleName, id, router]); - // Expose methods to parent via ref - useImperativeHandle(ref, () => ({ - save: handleSave, - setAsDefault: handleSetAsDefault, - delete: handleDelete, - refresh: fetchSchedule, - })); + // Expose handlers via ref + useImperativeHandle( + ref, + () => ({ + setAsDefault: handleSetAsDefault, + delete: handleDelete, + refresh: fetchSchedule, + }), + [handleSetAsDefault, handleDelete, fetchSchedule] + ); - const handleAddOverride = () => { - setEditingOverride(null); - setOverrideDate(""); - setOverrideStartTime("09:00"); - setOverrideEndTime("17:00"); - setIsUnavailable(false); - setShowOverrideModal(true); - }; - - const handleEditOverride = (index: number) => { - const override = overrides[index]; - setEditingOverride(index); - setOverrideDate(override.date); - setOverrideStartTime(override.startTime); - setOverrideEndTime(override.endTime); - setIsUnavailable(override.startTime === "00:00" && override.endTime === "00:00"); - setShowOverrideModal(true); - }; - - const handleDeleteOverride = (index: number) => { - Alert.alert("Delete Override", "Are you sure you want to delete this date override?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => { - setOverrides(overrides.filter((_, i) => i !== index)); - }, - }, - ]); - }; - - const handleSaveOverride = () => { - if (!overrideDate) { - Alert.alert("Error", "Please select a date"); - return; + // Expose handlers to parent for header menu + useEffect(() => { + if (onActionsReady) { + onActionsReady({ + handleSetAsDefault, + handleDelete, + }); } + }, [onActionsReady, handleSetAsDefault, handleDelete]); - const newOverride = { - date: overrideDate, - startTime: isUnavailable ? "00:00" : overrideStartTime, - endTime: isUnavailable ? "00:00" : overrideEndTime, - }; - - if (editingOverride !== null) { - // Update existing override - const updated = [...overrides]; - updated[editingOverride] = newOverride; - setOverrides(updated); - } else { - // Add new override - setOverrides([...overrides, newOverride]); - } - - setShowOverrideModal(false); - }; - - const formatDateForDisplay = (dateStr: string): string => { - if (!dateStr) return ""; - const date = new Date(`${dateStr}T00:00:00`); - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }); - }; + // Count enabled days + const enabledDaysCount = Object.keys(availability).length; if (loading) { return ( - + - Loading availability... + Loading availability... ); } return ( - - {/* Glass Header - only show when not using native header */} - {!useNativeHeader && ( - - - router.back()} - > - - - - - Edit Availability - - - - Save - - - - )} - + - - {/* Availability Name */} - - Availability Name - - - - {/* Working Hours Display */} - - Working Hours - {Object.keys(availability).length > 0 ? ( - - {formatAvailabilityDisplay(availability).map((line) => ( - - {line} - - ))} - - ) : ( - No availability set + {/* Schedule Title Section */} + + + + {scheduleName || "Untitled Schedule"} + + {isDefault && ( + )} + - {/* Availability Schedule */} - - Availability - {DAYS.map((day, dayIndex) => { - const daySlots = availability[dayIndex] || []; - const isEnabled = daySlots.length > 0; - const firstSlot = daySlots[0]; + {/* Weekly Schedule Card - Navigable */} + + router.push(`/edit-availability-hours?id=${id}` as never)}> + + Weekly Schedule + + + {enabledDaysCount} {enabledDaysCount === 1 ? "day" : "days"} + + + + + + {/* Expanded Day List */} + + {DAYS.map((day, dayIndex) => { + const daySlots = availability[dayIndex] || []; + const isEnabled = daySlots.length > 0; + + return ( + 0 ? "border-t border-[#E5E5EA]" : "" + }`} + > + + + {DAYS[dayIndex]} + - return ( - - - - toggleDay(dayIndex)} - trackColor={{ false: "#E5E5EA", true: "#34C759" }} - thumbColor="#fff" - style={{ - transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }], - }} - /> - - {DAY_ABBREVIATIONS[dayIndex]} - - {isEnabled && firstSlot ? ( - - - setShowTimePicker({ - dayIndex, - slotIndex: 0, - type: "start", - }) - } - className="rounded-lg border border-[#E5E5EA] bg-white px-2 py-1" - style={{ width: 85 }} - > - - {formatTime12Hour(firstSlot.startTime)} - - - - - - setShowTimePicker({ - dayIndex, - slotIndex: 0, - type: "end", - }) - } - className="rounded-lg border border-[#E5E5EA] bg-white px-2 py-1" - style={{ width: 85 }} - > - - {formatTime12Hour(firstSlot.endTime)} - - - - ) : null} - {isEnabled ? ( - addTimeSlot(dayIndex)} className="p-1"> - - - ) : null} + + {daySlots.map((slot, slotIndex) => ( + 0 ? "mt-1" : ""}`} + > + {formatTime12Hour(slot.startTime)} - {formatTime12Hour(slot.endTime)} + + ))} + + ) : ( + + Unavailable + + )} + ); + })} + + + - {isEnabled && daySlots.length > 1 ? ( - - {daySlots.slice(1).map((slot, slotIndex) => ( - - - setShowTimePicker({ - dayIndex, - slotIndex: slotIndex + 1, - type: "start", - }) - } - className="rounded-lg border border-[#E5E5EA] bg-white px-2 py-1" - style={{ width: 85 }} - > - - {formatTime12Hour(slot.startTime)} - - - - - - setShowTimePicker({ - dayIndex, - slotIndex: slotIndex + 1, - type: "end", - }) - } - className="rounded-lg border border-[#E5E5EA] bg-white px-2 py-1" - style={{ width: 85 }} - > - - {formatTime12Hour(slot.endTime)} - - - removeTimeSlot(dayIndex, slotIndex + 1)} - className="p-1" - > - - - - ))} - - ) : null} - - ); - })} - + {/* Timezone Card - Navigable */} + + router.push(`/edit-availability-name?id=${id}` as never)}> + + Timezone + + {timeZone} + + + + + - {/* Timezone */} - - Timezone + {/* Date Overrides Card - Navigable */} + {overrides.length > 0 && ( + setShowTimezoneModal(true)} - className="flex-row items-center justify-between rounded-lg border border-[#E5E5EA] bg-[#F8F9FA] px-3 py-3" + onPress={() => router.push(`/edit-availability-override?id=${id}` as never)} > - {timeZone} - - - + + Date Overrides + + + {overrides.length} {overrides.length === 1 ? "override" : "overrides"} + + + + - {/* Date Overrides */} - - Date overrides - - Add dates when your availability changes from your daily hours. - - - {overrides.length > 0 ? ( - + {/* Expanded Overrides List */} + {overrides.map((override, index) => ( 0 ? "border-t border-[#E5E5EA]" : ""}`} > - - - {formatDateForDisplay(override.date)} + + {formatDateForDisplay(override.date)} + + {override.startTime === "00:00" && override.endTime === "00:00" ? ( + Unavailable + ) : ( + + {formatTime12Hour(`${override.startTime}:00`)} -{" "} + {formatTime12Hour(`${override.endTime}:00`)} - {override.startTime === "00:00" && override.endTime === "00:00" ? ( - Unavailable (All day) - ) : ( - - {formatTime12Hour(`${override.startTime}:00`)} -{" "} - {formatTime12Hour(`${override.endTime}:00`)} - - )} - - - handleEditOverride(index)} className="p-2"> - - - handleDeleteOverride(index)} className="p-2"> - - - + )} ))} - ) : null} - - - - Add an override - + )} + + {/* No Overrides Message */} + {overrides.length === 0 && ( + + + Date Overrides + No date overrides set + + + )} - - {/* Bottom Action Bar */} - - - - Set as Default - - - - - - - - - - - - - - {/* Timezone Modal */} - setShowTimezoneModal(false)} - > - setShowTimezoneModal(false)} - > - e.stopPropagation()} - > - - Select Timezone - setShowTimezoneModal(false)}> - - - - - {timezones.map((tz) => ( - { - setTimeZone(tz); - setShowTimezoneModal(false); - }} - className="border-b border-[#E5E5EA] py-3" - > - {tz} - - ))} - - - - - - {/* Time Picker Modal */} - setShowTimePicker(null)} - > - setShowTimePicker(null)} - > - e.stopPropagation()} - > - - - Select {showTimePicker?.type === "start" ? "Start" : "End"} Time - - setShowTimePicker(null)}> - - - - - {TIME_OPTIONS.map((time) => { - const currentTime = - showTimePicker?.type === "start" - ? availability[showTimePicker.dayIndex]?.[ - showTimePicker.slotIndex - ]?.startTime.substring(0, 5) - : availability[showTimePicker?.dayIndex || 0]?.[ - showTimePicker?.slotIndex || 0 - ]?.endTime.substring(0, 5); - const isSelected = currentTime === time; - - return ( - { - if (showTimePicker) { - updateTimeSlot( - showTimePicker.dayIndex, - showTimePicker.slotIndex, - showTimePicker.type, - time - ); - setShowTimePicker(null); - } - }} - className={`border-b border-[#E5E5EA] py-3 ${isSelected ? "bg-[#E3F2FD]" : ""}`} - > - - {time} - - - ); - })} - - - - - - {/* Override Modal */} - setShowOverrideModal(false)} - > - setShowOverrideModal(false)} - > - e.stopPropagation()} - > - - - {editingOverride !== null ? "Edit Override" : "Add Override"} - - setShowOverrideModal(false)}> - - - - - - - {/* Date Picker */} - - - Select the dates to override - - - Enter date in YYYY-MM-DD format - - - {/* Unavailable Toggle */} - - - Mark unavailable (All day) - - - - - {/* Time Picker (only if not unavailable) */} - {!isUnavailable ? ( - - - Which hours are you free? - - - setShowOverrideTimePicker({ type: "start" })} - className="flex-1 rounded-lg border border-[#E5E5EA] bg-white px-3 py-3" - > - - {formatTime12Hour(`${overrideStartTime}:00`)} - - - - - setShowOverrideTimePicker({ type: "end" })} - className="flex-1 rounded-lg border border-[#E5E5EA] bg-white px-3 py-3" - > - - {formatTime12Hour(`${overrideEndTime}:00`)} - - - - - ) : null} - - {/* Save Button */} - - - Save Override - - - - - - - - - {/* Override Time Picker Modal */} - setShowOverrideTimePicker(null)} - > - setShowOverrideTimePicker(null)} - > - e.stopPropagation()} - > - - - Select {showOverrideTimePicker?.type === "start" ? "Start" : "End"} Time - - setShowOverrideTimePicker(null)}> - - - - - {TIME_OPTIONS.map((time) => { - const currentTime = - showOverrideTimePicker?.type === "start" ? overrideStartTime : overrideEndTime; - const isSelected = currentTime === time; - return ( - { - if (showOverrideTimePicker?.type === "start") { - setOverrideStartTime(time); - } else { - setOverrideEndTime(time); - } - setShowOverrideTimePicker(null); - }} - className={`border-b border-[#E5E5EA] py-3 ${isSelected ? "bg-[#F0F9FF]" : ""}`} - > - - {formatTime12Hour(`${time}:00`)} - - - ); - })} - - - - ); }); diff --git a/companion/components/screens/AvailabilityListScreen.tsx b/companion/components/screens/AvailabilityListScreen.tsx index f1a0a72c83..364ce0810f 100644 --- a/companion/components/screens/AvailabilityListScreen.tsx +++ b/companion/components/screens/AvailabilityListScreen.tsx @@ -9,6 +9,7 @@ import { ScrollView, Text, TextInput, + TouchableOpacity, View, } from "react-native"; import { AppPressable } from "@/components/AppPressable"; @@ -290,13 +291,13 @@ export function AvailabilityListScreen({ autoCorrect={false} clearButtonMode="while-editing" /> - New - + @@ -439,12 +440,12 @@ export function AvailabilityListScreen({ animationType="fade" onRequestClose={() => onShowCreateModalChange(false)} > - onShowCreateModalChange(false)} > - e.stopPropagation()} @@ -452,7 +453,10 @@ export function AvailabilityListScreen({ > {/* Header */} - Add a new schedule + + Add a new schedule + + Create a new availability schedule. {/* Content */} @@ -497,8 +501,8 @@ export function AvailabilityListScreen({ - - + + )} diff --git a/companion/components/screens/BookingDetailScreen.tsx b/companion/components/screens/BookingDetailScreen.tsx index ae7cc02752..5c0e4a8a6f 100644 --- a/companion/components/screens/BookingDetailScreen.tsx +++ b/companion/components/screens/BookingDetailScreen.tsx @@ -599,7 +599,7 @@ export function BookingDetailScreen({ }} /> )} - + ( + <> + + + Select {showTimePicker?.type === "start" ? "Start" : "End"} Time + + setShowTimePicker(null)}> + + + + + {TIME_OPTIONS.map((time) => { + const slotIndex = showTimePicker?.slotIndex ?? 0; + const currentTime = + showTimePicker?.type === "start" + ? slots[slotIndex]?.startTime + : slots[slotIndex]?.endTime; + const isSelected = currentTime === time; + + return ( + handleTimeSelect(time)}> + + + + {formatTime12Hour(time)} + + {isSelected && ( + + + + )} + + + + ); + })} + + + ); + // Initialize from schedule useEffect(() => { if (schedule) { @@ -308,7 +360,7 @@ export const EditAvailabilityDayScreen = forwardRef< }} > {/* Enable/Disable Toggle */} - + Available on {dayName} ( Slot {index + 1} {slots.length > 1 && ( - handleRemoveSlot(index)} className="p-1"> - + handleRemoveSlot(index)} + className="rounded-full bg-red-50 p-2" + > + )} - setShowTimePicker({ slotIndex: index, type: "start" })} - > - Start - {formatTime12Hour(slot.startTime)} - + {Platform.OS === "web" ? ( + + Start + { + const newSlots = [...slots]; + newSlots[index] = { ...newSlots[index], startTime: e.target.value }; + setSlots(newSlots); + }} + style={{ + fontSize: 17, + fontWeight: 500, + color: "#000", + backgroundColor: "transparent", + border: "none", + outline: "none", + fontFamily: "inherit", + width: "100%", + }} + /> + + ) : ( + setShowTimePicker({ slotIndex: index, type: "start" })} + > + Start + + {formatTime12Hour(slot.startTime)} + + + )} – - setShowTimePicker({ slotIndex: index, type: "end" })} - > - End - {formatTime12Hour(slot.endTime)} - + {Platform.OS === "web" ? ( + + End + { + const newSlots = [...slots]; + newSlots[index] = { ...newSlots[index], endTime: e.target.value }; + setSlots(newSlots); + }} + style={{ + fontSize: 17, + fontWeight: 500, + color: "#000", + backgroundColor: "transparent", + border: "none", + outline: "none", + fontFamily: "inherit", + width: "100%", + }} + /> + + ) : ( + setShowTimePicker({ slotIndex: index, type: "end" })} + > + End + + {formatTime12Hour(slot.endTime)} + + + )} ))} {/* Add Slot Button */} - - - Add Time Slot + + + + Add Time Slot + )} {/* Unavailable Message */} {!isEnabled && ( - - - - You are unavailable on {dayName} + + + + + + Unavailable - - Toggle on to set availability + + You are currently unavailable on {dayName}.{"\n"}Toggle the switch above to add hours. )} @@ -386,50 +496,27 @@ export const EditAvailabilityDayScreen = forwardRef< {/* Time Picker Modal */} setShowTimePicker(null)} > - - - - Select {showTimePicker?.type === "start" ? "Start" : "End"} Time - - setShowTimePicker(null)}> - - - - - {TIME_OPTIONS.map((time) => { - const slotIndex = showTimePicker?.slotIndex ?? 0; - const currentTime = - showTimePicker?.type === "start" - ? slots[slotIndex]?.startTime - : slots[slotIndex]?.endTime; - const isSelected = currentTime === time; - - return ( - handleTimeSelect(time)} - className={`border-b border-gray-100 px-4 py-3.5 ${ - isSelected ? "bg-blue-50" : "" - }`} - > - - - {formatTime12Hour(time)} - - {isSelected && } - - - ); - })} - - + {Platform.OS === "web" ? ( + setShowTimePicker(null)} + > + e.stopPropagation()} + style={shadows.xl()} + > + {renderTimePickerContent()} + + + ) : ( + {renderTimePickerContent()} + )} ); diff --git a/companion/components/screens/EditAvailabilityHoursScreen.tsx b/companion/components/screens/EditAvailabilityHoursScreen.tsx index 3e3d3b8f72..c97f1ee673 100644 --- a/companion/components/screens/EditAvailabilityHoursScreen.tsx +++ b/companion/components/screens/EditAvailabilityHoursScreen.tsx @@ -110,53 +110,53 @@ export const EditAvailabilityHoursScreen = forwardRef - + {DAYS.map((day, dayIndex) => { const daySlots = availability[dayIndex] || []; const isEnabled = daySlots.length > 0; return ( - 0 ? "border-t border-gray-200" : "" - }`} - onPress={() => onDayPress(dayIndex)} - > - {/* Day status indicator */} + onDayPress(dayIndex)}> - - {/* Day name */} - - {day} - + {/* Day status indicator */} + - {/* Time slots or unavailable */} - - {isEnabled ? ( - daySlots.map((slot, slotIndex) => ( - 0 ? "mt-0.5" : ""}`} - > - {formatTime12Hour(slot.startTime)} – {formatTime12Hour(slot.endTime)} - - )) - ) : ( - Unavailable - )} + {/* Day name */} + + {day} + + + {/* Time slots or unavailable */} + + {isEnabled ? ( + daySlots.map((slot, slotIndex) => ( + 0 ? "mt-0.5" : ""}`} + > + {formatTime12Hour(slot.startTime)} – {formatTime12Hour(slot.endTime)} + + )) + ) : ( + Unavailable + )} + + + {/* Chevron */} + - - {/* Chevron */} - ); })} diff --git a/companion/components/screens/EditAvailabilityNameScreen.tsx b/companion/components/screens/EditAvailabilityNameScreen.tsx index 50f11e0a02..a8b96afca1 100644 --- a/companion/components/screens/EditAvailabilityNameScreen.tsx +++ b/companion/components/screens/EditAvailabilityNameScreen.tsx @@ -1,6 +1,15 @@ import { Ionicons } from "@expo/vector-icons"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react"; -import { Alert, KeyboardAvoidingView, ScrollView, Text, TextInput, View } from "react-native"; +import { + Alert, + KeyboardAvoidingView, + Platform, + ScrollView, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AppPressable } from "@/components/AppPressable"; import { FullScreenModal } from "@/components/FullScreenModal"; @@ -8,6 +17,7 @@ import { TIMEZONES as ALL_TIMEZONES } from "@/constants/timezones"; import type { Schedule } from "@/services/calcom"; import { CalComAPIService } from "@/services/calcom"; import { showErrorAlert } from "@/utils/alerts"; +import { shadows } from "@/utils/shadows"; // Format timezones for display const TIMEZONES = ALL_TIMEZONES.map((tz) => ({ @@ -84,6 +94,57 @@ export const EditAvailabilityNameScreen = forwardRef< const selectedTimezoneLabel = TIMEZONES.find((tz) => tz.id === timezone)?.label || timezone; + // Render timezone list content + const renderTimezoneContent = () => ( + <> + + Select Timezone + setShowTimezoneModal(false)}> + + + + + {TIMEZONES.map((tz) => ( + { + setTimezone(tz.id); + setShowTimezoneModal(false); + }} + > + + + + + {tz.label} + + {tz.id} + + {tz.id === timezone && ( + + + + )} + + + + ))} + + + ); + if (!schedule) { return ( @@ -120,63 +181,49 @@ export const EditAvailabilityNameScreen = forwardRef< Timezone - setShowTimezoneModal(true)} - > - - - - {selectedTimezoneLabel} - {timezone} + setShowTimezoneModal(true)}> + + + + + {selectedTimezoneLabel} + {timezone} + + - {/* Timezone Modal */} setShowTimezoneModal(false)} > - - - Select Timezone - setShowTimezoneModal(false)}> - - - - - {TIMEZONES.map((tz) => ( - { - setTimezone(tz.id); - setShowTimezoneModal(false); - }} - className={`border-b border-gray-100 px-4 py-3.5 ${ - tz.id === timezone ? "bg-blue-50" : "" - }`} - > - - - - {tz.label} - - {tz.id} - - {tz.id === timezone && } - - - ))} - - + {Platform.OS === "web" ? ( + setShowTimezoneModal(false)} + > + e.stopPropagation()} + style={shadows.xl()} + > + {renderTimezoneContent()} + + + ) : ( + {renderTimezoneContent()} + )} ); diff --git a/companion/components/screens/EditAvailabilityOverrideScreen.tsx b/companion/components/screens/EditAvailabilityOverrideScreen.tsx index 33b167755b..fadc61f795 100644 --- a/companion/components/screens/EditAvailabilityOverrideScreen.tsx +++ b/companion/components/screens/EditAvailabilityOverrideScreen.tsx @@ -1,12 +1,22 @@ import { Ionicons } from "@expo/vector-icons"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react"; -import { Alert, ScrollView, Switch, Text, TextInput, View } from "react-native"; +import { + Alert, + Platform, + ScrollView, + Switch, + Text, + TextInput, + TouchableOpacity, + View, +} from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AppPressable } from "@/components/AppPressable"; import { FullScreenModal } from "@/components/FullScreenModal"; import type { Schedule } from "@/services/calcom"; import { CalComAPIService } from "@/services/calcom"; import { showErrorAlert } from "@/utils/alerts"; +import { shadows } from "@/utils/shadows"; // Generate time options (15-minute intervals) const generateTimeOptions = () => { @@ -78,6 +88,53 @@ export const EditAvailabilityOverrideScreen = forwardRef< type: "start" | "end"; } | null>(null); + // Render modal content + const renderTimePickerContent = () => ( + <> + + + Select {showTimePicker?.type === "start" ? "Start" : "End"} Time + + setShowTimePicker(null)}> + + + + + {TIME_OPTIONS.map((time) => { + const currentTime = showTimePicker?.type === "start" ? startTime : endTime; + const isSelected = currentTime === time; + + return ( + handleTimeSelect(time)}> + + + + {formatTime12Hour(time)} + + {isSelected && ( + + + + )} + + + + ); + })} + + + ); + // Initialize from existing override if editing useEffect(() => { if (schedule && isEditing && schedule.overrides && overrideIndex !== undefined) { @@ -272,26 +329,43 @@ export const EditAvailabilityOverrideScreen = forwardRef< > {/* Date Input */} Date (YYYY-MM-DD) - + - - + + {Platform.OS === "web" ? ( + setSelectedDate(e.target.value)} + style={{ + flex: 1, + fontSize: 17, + color: "#000", + backgroundColor: "transparent", + border: "none", + outline: "none", + fontFamily: "inherit", + }} + /> + ) : ( + + )} - {selectedDate && ( + {selectedDate ? ( {formatDateForDisplay(selectedDate)} - )} + ) : null} {/* Unavailable Toggle */} - + @@ -311,25 +385,65 @@ export const EditAvailabilityOverrideScreen = forwardRef< {!isUnavailable && ( <> Available Hours - + - setShowTimePicker({ type: "start" })} - > - Start Time - {formatTime12Hour(startTime)} - + {Platform.OS === "web" ? ( + + Start Time + setStartTime(e.target.value)} + style={{ + fontSize: 17, + color: "#000", + backgroundColor: "transparent", + border: "none", + outline: "none", + fontFamily: "inherit", + width: "100%", + }} + /> + + ) : ( + setShowTimePicker({ type: "start" })} + > + Start Time + {formatTime12Hour(startTime)} + + )} – - setShowTimePicker({ type: "end" })} - > - End Time - {formatTime12Hour(endTime)} - + {Platform.OS === "web" ? ( + + End Time + setEndTime(e.target.value)} + style={{ + fontSize: 17, + color: "#000", + backgroundColor: "transparent", + border: "none", + outline: "none", + fontFamily: "inherit", + width: "100%", + }} + /> + + ) : ( + setShowTimePicker({ type: "end" })} + > + End Time + {formatTime12Hour(endTime)} + + )} @@ -341,41 +455,41 @@ export const EditAvailabilityOverrideScreen = forwardRef< Existing Overrides - + {schedule.overrides.map((override, index) => ( 0 ? "border-t border-gray-200" : "" - }`} + className="mb-2.5" onPress={() => onEditOverride?.(index)} > - - - {formatDateForDisplay(override.date ?? "")} - - {override.startTime === "00:00" && override.endTime === "00:00" ? ( - Unavailable - ) : ( - - {formatTime12Hour(override.startTime ?? "00:00")} –{" "} - {formatTime12Hour(override.endTime ?? "00:00")} + + + + {formatDateForDisplay(override.date ?? "")} - )} - - - handleDeleteOverride(index)} - > - - - onEditOverride?.(index)} - > - - + {override.startTime === "00:00" && override.endTime === "00:00" ? ( + Unavailable + ) : ( + + {formatTime12Hour(override.startTime ?? "00:00")} –{" "} + {formatTime12Hour(override.endTime ?? "00:00")} + + )} + + + handleDeleteOverride(index)} + > + + + + ))} @@ -386,46 +500,27 @@ export const EditAvailabilityOverrideScreen = forwardRef< {/* Time Picker Modal */} setShowTimePicker(null)} > - - - - Select {showTimePicker?.type === "start" ? "Start" : "End"} Time - - setShowTimePicker(null)}> - - - - - {TIME_OPTIONS.map((time) => { - const currentTime = showTimePicker?.type === "start" ? startTime : endTime; - const isSelected = currentTime === time; - - return ( - handleTimeSelect(time)} - className={`border-b border-gray-100 px-4 py-3.5 ${ - isSelected ? "bg-blue-50" : "" - }`} - > - - - {formatTime12Hour(time)} - - {isSelected && } - - - ); - })} - - + {Platform.OS === "web" ? ( + setShowTimePicker(null)} + > + e.stopPropagation()} + style={shadows.xl()} + > + {renderTimePickerContent()} + + + ) : ( + {renderTimePickerContent()} + )} );