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()}
+ )}
);