diff --git a/frontend/app/(app)/trips/[id]/activities/components/activities-tab-content.tsx b/frontend/app/(app)/trips/[id]/activities/components/activities-tab-content.tsx new file mode 100644 index 00000000..7a91b365 --- /dev/null +++ b/frontend/app/(app)/trips/[id]/activities/components/activities-tab-content.tsx @@ -0,0 +1,235 @@ +import { useActivitiesList } from "@/api/activities/custom/useActivitiesList"; +import { Box, Button, EmptyState, SkeletonRect, Text } from "@/design-system"; +import { ColorPalette } from "@/design-system/tokens/color"; +import { Layout } from "@/design-system/tokens/layout"; +import type { + ModelsActivityAPIResponse, + ModelsParsedActivityData, +} from "@/types/types.gen"; +import { router } from "expo-router"; +import { + forwardRef, + useCallback, + useImperativeHandle, + useMemo, + useRef, + useState, +} from "react"; +import { + ActivityIndicator, + FlatList, + Pressable, + StyleSheet, + View, +} from "react-native"; +import { ActivityCard } from "./activity-card"; +import { + AddActivityEntrySheet, + type AddActivityEntrySheetHandle, +} from "./add-activity-entry-sheet"; +import { + AddActivityManualSheet, + type AddActivityManualSheetHandle, +} from "./add-activity-manual-sheet"; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +export type ActivitiesTabContentHandle = { + openAddActivity: () => void; +}; + +type ActivitiesTabContentProps = { + tripID: string; +}; + +type SortOrder = "newest" | "oldest"; + +// ─── Skeleton ──────────────────────────────────────────────────────────────── + +function ActivitiesSkeleton() { + return ( + + {[1, 2, 3].map((i) => ( + + ))} + + ); +} + +// ─── Component ─────────────────────────────────────────────────────────────── + +export const ActivitiesTabContent = forwardRef< + ActivitiesTabContentHandle, + ActivitiesTabContentProps +>(({ tripID }, ref) => { + const entrySheetRef = useRef(null); + const manualSheetRef = useRef(null); + + const [sortOrder, setSortOrder] = useState("newest"); + + const { activities, isLoading, isLoadingMore, fetchMore, prependActivity } = + useActivitiesList(tripID); + + // ─── Sort activities ───────────────────────────────────────────────────── + + const sortedActivities = useMemo(() => { + if (sortOrder === "newest") return activities; + return [...activities].reverse(); + }, [activities, sortOrder]); + + useImperativeHandle(ref, () => ({ + openAddActivity: () => entrySheetRef.current?.open(), + })); + + // ─── Handlers ──────────────────────────────────────────────────────────── + + const handleAutofilled = useCallback((data: ModelsParsedActivityData) => { + entrySheetRef.current?.close(); + setTimeout(() => manualSheetRef.current?.open(data), 300); + }, []); + + const handleManual = useCallback(() => { + entrySheetRef.current?.close(); + setTimeout(() => manualSheetRef.current?.open(), 300); + }, []); + + const handleSaved = useCallback( + (activity: ModelsActivityAPIResponse) => { + manualSheetRef.current?.close(); + prependActivity(activity); + }, + [prependActivity], + ); + + const toggleSort = useCallback(() => { + setSortOrder((prev) => (prev === "newest" ? "oldest" : "newest")); + }, []); + + const renderItem = useCallback( + ({ item }: { item: ModelsActivityAPIResponse }) => ( + + router.push({ + pathname: `/trips/${tripID}/activities/${item.id}` as any, + params: { tripID }, + }) + } + /> + ), + [tripID], + ); + + const renderFooter = useCallback( + () => + isLoadingMore ? ( + + + + ) : null, + [isLoadingMore], + ); + + // ─── Render ────────────────────────────────────────────────────────────── + + return ( + + {isLoading ? ( + + ) : activities.length === 0 ? ( + + + + ) : ( + <> + {/* Header row: count + sort toggle */} + + + {activities.length}{" "} + {activities.length === 1 ? "option" : "options"} added + + ({ opacity: pressed ? 0.7 : 1 })} + > + + {sortOrder === "newest" ? "Newest first" : "Oldest first"} + + + + + item.id ?? ""} + renderItem={renderItem} + onEndReached={fetchMore} + onEndReachedThreshold={0.3} + ListFooterComponent={renderFooter} + contentContainerStyle={styles.list} + ItemSeparatorComponent={() => } + style={styles.flatList} + scrollEnabled={false} + /> + + )} + + {/* Add an activity button */} + +