Files
cal.diy/companion/hooks/useActiveBookingFilter.tsx
T
Dhairyashil Shinde 9e84f59216 feat(companion): UI Enhancements for Android and Extension (#26434)
* feat: companion-android-ui-upgrade version 1

* recurrings and unconfirmed booking filter and page implementation

* add badge and links to event type list page

* address cubics comments

* feat(companion): unify dropdown menu for Android and extension (#26486)

* feat(companion): unify dropdown menu for Android and extension

- Merge Android-specific dropdown implementations into base component files
- EventTypeListItem: Add DropdownMenu with Preview, Copy link, Edit, Duplicate, Delete actions
- BookingListItem: Add DropdownMenu with booking actions (reschedule, edit location, add guests, etc.)
- RecurringBookingListItem: Add DropdownMenu with recurring booking actions
- AvailabilityListItem: Add DropdownMenu with Set as Default, Duplicate, Delete actions
- BookingDetailScreen: Add DropdownMenu in header for Android with AlertDialog for cancel confirmation
- Delete all .android.tsx files as implementations are now unified

* fix(companion): fix typecheck errors after dropdown unification

- Remove unused props from EventTypeListItem.ios.tsx (copiedEventTypeId, handleEventTypeLongPress)
- Remove unused onActionsPress prop from BookingListItem.ios.tsx
- Remove copiedEventTypeId and handleEventTypeLongPress props from index.ios.tsx and index.tsx
- Remove onLongPress and onActionsPress props from BookingListScreen.tsx
- Remove handleScheduleLongPress, setSelectedSchedule, setShowActionsModal props from AvailabilityListScreen.tsx
- Fix BookingDetailScreen.tsx to use correct action property names (reschedule.visible instead of canReschedule, etc.)

* fix(companion): remove unused code after dropdown unification

- Remove unused handleEventTypeLongPress function and ActionSheetIOS import from index.ios.tsx
- Remove unused copiedEventTypeId state, handleEventTypeLongPress function, and ActionSheetIOS import from index.tsx
- Prefix unused setSelectedBooking with underscore in BookingListScreen.tsx
- Remove unused handleScheduleLongPress function and ActionSheetIOS import from AvailabilityListScreen.tsx

* feat(companion): unify booking filter UI for Android and extension

- Remove SegmentedControl from web/extension booking list page
- Use Header dropdown for booking status filter on both Android and web
- Use unified event type filter dropdown for both platforms
- Remove unused showFilterModal state and related code
- Pass filterOptions, activeFilter, and onFilterChange to Header for all platforms

* fix(companion): add header padding for web/extension to prevent button clipping

- Add headerLeftContainerStyle and headerRightContainerStyle with 12px padding for web platform
- Applied to root Stack and all nested tab Stack layouts
- Fixes issue where header buttons were touching edges and getting chopped off on extension/web
- Android remains unaffected as the fix is web-only

* fix(companion): add HeaderButtonWrapper for web-only header padding

- Create HeaderButtonWrapper component that adds 12px margin on web only
- Wrap all header buttons with HeaderButtonWrapper to prevent clipping
- Revert invalid screenOptions changes that caused typecheck errors
- Apply fix to all screens with native header buttons:
  - reschedule.tsx, edit-location.tsx, add-guests.tsx
  - mark-no-show.tsx, view-recordings.tsx, meeting-session-details.tsx
  - event-type-detail.tsx, BookingDetailScreen.tsx, profile-sheet.tsx
  - edit-availability-day.tsx, edit-availability-name.tsx, edit-availability-override.tsx

* update more ui-ux

* address cubics comments

* address cubics comments & open event type list page first for inttial render of app
2026-01-07 07:19:07 -03:00

139 lines
3.8 KiB
TypeScript

import { useCallback, useMemo, useState } from "react";
import type { NativeSyntheticEvent } from "react-native";
import type { BookingFilters } from "./useBookings";
export type BookingFilter = "upcoming" | "unconfirmed" | "recurring" | "past" | "cancelled";
export interface BookingFilterOption {
key: BookingFilter;
label: string;
}
const FILTER_OPTIONS: BookingFilterOption[] = [
{ key: "upcoming", label: "Upcoming" },
{ key: "unconfirmed", label: "Unconfirmed" },
{ key: "recurring", label: "Recurring" },
{ key: "past", label: "Past" },
{ key: "cancelled", label: "Cancelled" },
];
/**
* Hook to manage active booking filter state and provide filter-related utilities.
*
* Features:
* - Manages active filter state
* - Provides filter options and labels for UI components
* - Converts filter selection to API-compatible parameters
* - Handles segmented control integration
* - Supports change callbacks for dependent filter cleanup
*
* @param initialFilter - Initial filter value (defaults to "upcoming")
* @param onFilterChange - Optional callback invoked when filter changes
* @returns Object containing filter state, handlers, and computed values
*
* @example
* ```tsx
* const {
* activeFilter,
* filterLabels,
* activeIndex,
* filterParams,
* handleFilterChange,
* handleSegmentChange,
* } = useActiveBookingFilter("upcoming", () => {
* // Clear dependent filters like search query, event type filter, etc.
* setSearchQuery("");
* setSelectedEventTypeId(null);
* });
*
* // Use with React Query
* const { data: bookings } = useBookings(filterParams);
*
* // Use with SegmentedControl
* <SegmentedControl
* values={filterLabels}
* selectedIndex={activeIndex}
* onChange={handleSegmentChange}
* />
* ```
*/
export function useActiveBookingFilter(
initialFilter: BookingFilter = "upcoming",
onFilterChange?: (filter: BookingFilter) => void
) {
const [activeFilter, setActiveFilter] = useState<BookingFilter>(initialFilter);
/**
* Converts the active filter to API-compatible query parameters
*/
const filterParams = useMemo<BookingFilters>(() => {
switch (activeFilter) {
case "upcoming":
return { status: ["upcoming"], limit: 50 };
case "unconfirmed":
return { status: ["unconfirmed"], limit: 50 };
case "recurring":
return { status: ["recurring"], limit: 100 };
case "past":
return { status: ["past"], limit: 100 };
case "cancelled":
return { status: ["cancelled"], limit: 100 };
default:
return { status: ["upcoming"], limit: 50 };
}
}, [activeFilter]);
/**
* Array of filter labels for UI components (e.g., SegmentedControl)
*/
const filterLabels = useMemo(() => FILTER_OPTIONS.map((option) => option.label), []);
/**
* Current active filter index for UI components
*/
const activeIndex = useMemo(
() => FILTER_OPTIONS.findIndex((option) => option.key === activeFilter),
[activeFilter]
);
/**
* Changes the active filter and invokes the optional callback
*/
const handleFilterChange = useCallback(
(filter: BookingFilter) => {
setActiveFilter(filter);
onFilterChange?.(filter);
},
[onFilterChange]
);
/**
* Handler for React Native SegmentedControl onChange event
*/
const handleSegmentChange = useCallback(
(event: NativeSyntheticEvent<{ selectedSegmentIndex: number }>) => {
const { selectedSegmentIndex } = event.nativeEvent;
const selectedFilter = FILTER_OPTIONS[selectedSegmentIndex];
if (selectedFilter) {
handleFilterChange(selectedFilter.key);
}
},
[handleFilterChange]
);
return {
// State
activeFilter,
// Computed values
filterOptions: FILTER_OPTIONS,
filterLabels,
activeIndex,
filterParams,
// Handlers
handleFilterChange,
handleSegmentChange,
};
}