Merge branch 'refs/heads/dev'

This commit is contained in:
Milan Paunovic
2024-12-08 11:38:59 +01:00
13 changed files with 542 additions and 380 deletions

View File

@ -1,8 +1,18 @@
import React from "react"; import React from "react";
import { Drawer } from "expo-router/drawer"; import { Drawer } from "expo-router/drawer";
import { useSignOut } from "@/hooks/firebase/useSignOut"; import { useSignOut } from "@/hooks/firebase/useSignOut";
import {DrawerContentScrollView, DrawerNavigationOptions, DrawerNavigationProp} from "@react-navigation/drawer"; import {
import {Button, ButtonSize, Text, TouchableOpacity, View,} from "react-native-ui-lib"; DrawerContentScrollView,
DrawerNavigationOptions,
DrawerNavigationProp,
} from "@react-navigation/drawer";
import {
Button,
ButtonSize,
Text,
TouchableOpacity,
View,
} from "react-native-ui-lib";
import { ImageBackground, StyleSheet } from "react-native"; import { ImageBackground, StyleSheet } from "react-native";
import DrawerButton from "@/components/shared/DrawerButton"; import DrawerButton from "@/components/shared/DrawerButton";
import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon"; import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon";
@ -24,6 +34,8 @@ import {DeviceType} from "expo-device";
import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon"; import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon";
import DrawerIcon from "@/assets/svgs/DrawerIcon"; import DrawerIcon from "@/assets/svgs/DrawerIcon";
import { RouteProp } from "@react-navigation/core"; import { RouteProp } from "@react-navigation/core";
import RefreshButton from "@/components/shared/RefreshButton";
import { useCalSync } from "@/hooks/useCalSync";
type DrawerParamList = { type DrawerParamList = {
index: undefined; index: undefined;
@ -43,12 +55,11 @@ interface HeaderRightProps {
} }
const MemoizedViewSwitch = React.memo<ViewSwitchProps>(({ navigation }) => ( const MemoizedViewSwitch = React.memo<ViewSwitchProps>(({ navigation }) => (
<View marginR-16>
<ViewSwitch navigation={navigation} /> <ViewSwitch navigation={navigation} />
</View>
)); ));
const HeaderRight = React.memo<HeaderRightProps>(({routeName, navigation}) => { const HeaderRight = React.memo<HeaderRightProps>(
({ routeName, navigation }) => {
const showViewSwitch = ["calendar", "todos", "index"].includes(routeName); const showViewSwitch = ["calendar", "todos", "index"].includes(routeName);
if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) { if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) {
@ -56,13 +67,23 @@ const HeaderRight = React.memo<HeaderRightProps>(({routeName, navigation}) => {
} }
return <MemoizedViewSwitch navigation={navigation} />; return <MemoizedViewSwitch navigation={navigation} />;
}); }
);
export default function TabLayout() { export default function TabLayout() {
const { mutateAsync: signOut } = useSignOut(); const { mutateAsync: signOut } = useSignOut();
const setIsFamilyView = useSetAtom(isFamilyViewAtom); const setIsFamilyView = useSetAtom(isFamilyViewAtom);
const setPageIndex = useSetAtom(settingsPageIndex); const setPageIndex = useSetAtom(settingsPageIndex);
const setUserView = useSetAtom(userSettingsView); const setUserView = useSetAtom(userSettingsView);
const setToDosIndex = useSetAtom(toDosPageIndex); const setToDosIndex = useSetAtom(toDosPageIndex);
const { resyncAllCalendars, isSyncing } = useCalSync();
const onRefresh = React.useCallback(async () => {
try {
await resyncAllCalendars();
} catch (error) {
console.error("Refresh failed:", error);
}
}, [resyncAllCalendars]);
const screenOptions = ({ const screenOptions = ({
navigation, navigation,
@ -72,7 +93,8 @@ export default function TabLayout() {
route: RouteProp<DrawerParamList>; route: RouteProp<DrawerParamList>;
}): DrawerNavigationOptions => ({ }): DrawerNavigationOptions => ({
headerShown: true, headerShown: true,
headerTitleAlign: Device.deviceType === DeviceType.TABLET ? "left" : "center", headerTitleAlign:
Device.deviceType === DeviceType.TABLET ? "left" : "center",
headerTitleStyle: { headerTitleStyle: {
fontFamily: "Manrope_600SemiBold", fontFamily: "Manrope_600SemiBold",
fontSize: Device.deviceType === DeviceType.TABLET ? 22 : 17, fontSize: Device.deviceType === DeviceType.TABLET ? 22 : 17,
@ -86,20 +108,34 @@ export default function TabLayout() {
</TouchableOpacity> </TouchableOpacity>
), ),
headerRight: () => { headerRight: () => {
const showViewSwitch = ["calendar", "todos", "index"].includes(route.name); const showViewSwitch = ["calendar", "todos", "index"].includes(
route.name
);
const isCalendarPage = ["calendar", "index"].includes(route.name);
if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) { if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) {
return null; return isCalendarPage ? (
<View marginR-16>
<RefreshButton onRefresh={onRefresh} isSyncing={isSyncing} />
</View>
) : null;
} }
return <MemoizedViewSwitch navigation={navigation}/>; return (
<View marginR-16 row centerV>
{isCalendarPage && (
<View marginR-16><RefreshButton onRefresh={onRefresh} isSyncing={isSyncing} /></View>
)}
<MemoizedViewSwitch navigation={navigation} />
</View>
);
}, },
drawerStyle: { drawerStyle: {
width: Device.deviceType === DeviceType.TABLET ? "30%" : "90%", width: Device.deviceType === DeviceType.TABLET ? "30%" : "90%",
backgroundColor: "#f9f8f7", backgroundColor: "#f9f8f7",
height: "100%", height: "100%",
}, },
}) });
return ( return (
<Drawer <Drawer

View File

@ -61,7 +61,6 @@ export default function Screen() {
justifyContent: "center", justifyContent: "center",
paddingRight: 200, paddingRight: 200,
}} }}
refreshControl={refreshControl}
bounces={true} bounces={true}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
pointerEvents={isSyncing ? "auto" : "none"} pointerEvents={isSyncing ? "auto" : "none"}
@ -74,7 +73,6 @@ export default function Screen() {
<ScrollView <ScrollView
style={{flex: 1, height: "100%"}} style={{flex: 1, height: "100%"}}
contentContainerStyle={{flex: 1, height: "100%"}} contentContainerStyle={{flex: 1, height: "100%"}}
refreshControl={refreshControl}
bounces={true} bounces={true}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
> >

View File

@ -0,0 +1,20 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
const CheckmarkIcon = (props: SvgProps) => (
<Svg
width={13}
height={10}
viewBox="0 0 13 10"
fill={props.color || "white"}
{...props}
>
<Path
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.95}
d="m1.48 5.489 3.2 3.178 7.2-7.15"
/>
</Svg>
)
export default CheckmarkIcon

View File

@ -15,7 +15,12 @@ import { UserProfile } from "@/hooks/firebase/types/profileTypes";
import { ScrollView } from "react-native-gesture-handler"; import { ScrollView } from "react-native-gesture-handler";
const groupToDosByDate = (toDos: IToDo[]) => { const groupToDosByDate = (toDos: IToDo[]) => {
let sortedTodos = toDos.sort((a, b) => a.date - b.date); let sortedTodos = toDos.sort((a, b) => {
const dateA = a.date === null ? new Date() : a.date;
const dateB = b.date === null ? new Date() : b.date;
return dateA - dateB;
});
return sortedTodos.reduce( return sortedTodos.reduce(
(groups, toDo) => { (groups, toDo) => {
let dateKey; let dateKey;
@ -34,9 +39,7 @@ const groupToDosByDate = (toDos: IToDo[]) => {
}); });
}; };
if (toDo.date === null) { if (toDo.date === null || isToday(toDo.date)) {
dateKey = "No Date";
} else if (isToday(toDo.date)) {
dateKey = "Today"; dateKey = "Today";
} else if (isTomorrow(toDo.date)) { } else if (isTomorrow(toDo.date)) {
dateKey = "Tomorrow"; dateKey = "Tomorrow";
@ -228,14 +231,18 @@ const SingleUserChoreList = ({ user }: { user: UserProfile }) => {
return ( return (
<View <View
marginB-402
marginT-10 marginT-10
paddingH-10
backgroundColor="#f9f8f7" backgroundColor="#f9f8f7"
style={{ minHeight: 800, borderRadius: 9.11 }} style={{
minHeight: 600,
maxHeight: 600,
borderRadius: 9.11,
overflow: "hidden",
}}
width={355} width={355}
> >
<ScrollView> <ScrollView showsVerticalScrollIndicator={false}>
<View paddingH-10 paddingB-90>
{noDateToDos.length > 0 && ( {noDateToDos.length > 0 && (
<View key="No Date"> <View key="No Date">
<View row spread paddingH-19 marginB-12> <View row spread paddingH-19 marginB-12>
@ -264,6 +271,7 @@ const SingleUserChoreList = ({ user }: { user: UserProfile }) => {
)} )}
{datedToDos.map(renderTodoGroup)} {datedToDos.map(renderTodoGroup)}
</View>
</ScrollView> </ScrollView>
</View> </View>
); );

View File

@ -8,6 +8,7 @@ import { ImageBackground, StyleSheet } from "react-native";
import { colorMap } from "@/constants/colorMap"; import { colorMap } from "@/constants/colorMap";
import { ScrollView } from "react-native-gesture-handler"; import { ScrollView } from "react-native-gesture-handler";
import { ProfileType, useAuthContext } from "@/contexts/AuthContext"; import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
import AddChore from "../../todos/AddChore";
const TabletChoresPage = () => { const TabletChoresPage = () => {
const {data: users} = useGetFamilyMembers(); const {data: users} = useGetFamilyMembers();
@ -98,6 +99,9 @@ const TabletChoresPage = () => {
))} ))}
</View> </View>
</ScrollView> </ScrollView>
<View style={styles.addBtn}>
<AddChore />
</View>
</TabletContainer> </TabletContainer>
); );
}; };
@ -113,6 +117,11 @@ const styles = StyleSheet.create({
fontSize: 22.43, fontSize: 22.43,
color: "#2c2c2c", color: "#2c2c2c",
}, },
addBtn: {
position: 'absolute',
bottom: 50,
right: 220
}
}); });
export default TabletChoresPage; export default TabletChoresPage;

View File

@ -5,9 +5,9 @@ import { InnerCalendar } from "@/components/pages/calendar/InnerCalendar";
export default function CalendarPage() { export default function CalendarPage() {
return ( return (
<View <View
style={{ flex: 1, height: "100%", padding: 10 }} style={{ flex: 1, height: "100%", padding: 0 }}
paddingH-22 paddingH-0
paddingT-22 paddingT-0
> >
{/*<HeaderTemplate {/*<HeaderTemplate
message={"Let's get your week started !"} message={"Let's get your week started !"}

View File

@ -25,9 +25,10 @@ export const InnerCalendar = () => {
return ( return (
<> <>
<View <View
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30, marginBottom: 10, overflow: "hidden"}} style={{flex: 1, backgroundColor: "#fff", borderRadius: 0, marginBottom: 0, overflow: "hidden"}}
ref={calendarContainerRef} ref={calendarContainerRef}
onLayout={onLayout} onLayout={onLayout}
paddingB-15
> >
<CalendarHeader/> <CalendarHeader/>
{calendarHeight > 0 && ( {calendarHeight > 0 && (

View File

@ -40,6 +40,7 @@ import DeleteEventDialog from "./DeleteEventDialog";
import { useDeleteEvent } from "@/hooks/firebase/useDeleteEvent"; import { useDeleteEvent } from "@/hooks/firebase/useDeleteEvent";
import AddPersonIcon from "@/assets/svgs/AddPersonIcon"; import AddPersonIcon from "@/assets/svgs/AddPersonIcon";
import { addHours, startOfHour, startOfMinute } from "date-fns"; import { addHours, startOfHour, startOfMinute } from "date-fns";
import { useAuthContext } from "@/contexts/AuthContext";
const daysOfWeek = [ const daysOfWeek = [
{ label: "Monday", value: "monday" }, { label: "Monday", value: "monday" },
@ -53,6 +54,7 @@ const daysOfWeek = [
export const ManuallyAddEventModal = () => { export const ManuallyAddEventModal = () => {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { user } = useAuthContext();
const [selectedNewEventDate, setSelectedNewEndDate] = useAtom( const [selectedNewEventDate, setSelectedNewEndDate] = useAtom(
selectedNewEventDateAtom selectedNewEventDateAtom
@ -115,7 +117,7 @@ export const ManuallyAddEventModal = () => {
editEvent?.end ?? initialDate ?? new Date() editEvent?.end ?? initialDate ?? new Date()
); );
const [selectedAttendees, setSelectedAttendees] = useState<string[]>( const [selectedAttendees, setSelectedAttendees] = useState<string[]>(
editEvent?.attendees ?? [] editEvent?.attendees ?? [user?.uid]
); );
const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]); const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
@ -162,7 +164,7 @@ export const ManuallyAddEventModal = () => {
setStartDate(initialDate ?? new Date()); setStartDate(initialDate ?? new Date());
setEndDate(editEvent?.end ?? initialDate ?? new Date()); setEndDate(editEvent?.end ?? initialDate ?? new Date());
setSelectedAttendees(editEvent?.attendees ?? []); setSelectedAttendees(editEvent?.attendees ?? [user?.uid]);
setLocation(editEvent?.location ?? ""); setLocation(editEvent?.location ?? "");
setRepeatInterval([]); setRepeatInterval([]);
}, [editEvent, selectedNewEventDate]); }, [editEvent, selectedNewEventDate]);
@ -247,10 +249,10 @@ export const ManuallyAddEventModal = () => {
Alert.alert("Alert", "Title field cannot be empty"); Alert.alert("Alert", "Title field cannot be empty");
return false; return false;
} }
// if (!selectedAttendees || selectedAttendees?.length === 0) { if (!selectedAttendees || selectedAttendees?.length === 0) {
// Alert.alert('Alert', 'Cannot have an event without any attendees'); Alert.alert('Alert', 'Cannot have an event without any attendees');
// return false; return false;
// } }
return true; return true;
}; };

View File

@ -25,6 +25,7 @@ const GroceryItem = ({
const { profileData } = useAuthContext(); const { profileData } = useAuthContext();
const { data: creator } = useGetUserById(item.creatorId); const { data: creator } = useGetUserById(item.creatorId);
const isParent = profileData?.userType === ProfileType.PARENT; const isParent = profileData?.userType === ProfileType.PARENT;
const isCaregiver = profileData?.userType === ProfileType.CAREGIVER;
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false); const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false); const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
@ -36,7 +37,7 @@ const GroceryItem = ({
const closeEdit = () => setIsEditingTitle(false); const closeEdit = () => setIsEditingTitle(false);
const getInitials = (firstName: string, lastName: string) => { const getInitials = (firstName: string, lastName: string) => {
return `${firstName.charAt(0)}${lastName.charAt(0)}`; return `${firstName.charAt(0).toUpperCase()}${lastName.charAt(0).toUpperCase()}`;
}; };
return ( return (
@ -81,7 +82,7 @@ const GroceryItem = ({
/> />
) : ( ) : (
<View flex> <View flex>
{isParent ? ( {(isParent || isCaregiver) && !item.bought ? (
<TouchableOpacity onPress={() => setIsEditingTitle(true)}> <TouchableOpacity onPress={() => setIsEditingTitle(true)}>
<Text <Text
text70T text70T
@ -98,7 +99,7 @@ const GroceryItem = ({
) : ( ) : (
<Text <Text
text70T text70T
style={[styles.title, { color: item.bought ? "red" : "black" }]} style={[styles.title, { textDecorationLine: item.bought ? "line-through" : "none", }]}
> >
{item.title} {item.title}
</Text> </Text>
@ -108,7 +109,7 @@ const GroceryItem = ({
{!item.approved ? ( {!item.approved ? (
<View row centerV> <View row centerV>
{isParent && ( {isParent || isCaregiver && (
<> <>
<AntDesign <AntDesign
name="check" name="check"
@ -132,7 +133,7 @@ const GroceryItem = ({
</View> </View>
) : ( ) : (
!isEditingTitle && !isEditingTitle &&
isParent && ( (isParent || isCaregiver) && (
<Checkbox <Checkbox
value={item.bought} value={item.bought}
containerStyle={[styles.checkbox, { borderRadius: 50 }]} containerStyle={[styles.checkbox, { borderRadius: 50 }]}
@ -180,7 +181,7 @@ const GroceryItem = ({
width: 24.64, width: 24.64,
aspectRatio: 1, aspectRatio: 1,
marginRight: 8, marginRight: 8,
backgroundColor: "#ccc", backgroundColor: creator?.eventColor || "gray",
borderRadius: 100, borderRadius: 100,
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",

View File

@ -62,7 +62,7 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
id: "", id: "",
title: title, title: title,
category: category, category: category,
approved: profileData?.userType === ProfileType.PARENT, approved: profileData?.userType === ProfileType.PARENT || profileData?.userType === ProfileType.CAREGIVER,
recurring: false, recurring: false,
frequency: GroceryFrequency.Never, frequency: GroceryFrequency.Never,
bought: false, bought: false,

View File

@ -18,7 +18,7 @@ import {Ionicons} from "@expo/vector-icons";
import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView"; import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
import {Alert, Dimensions, KeyboardAvoidingView, StyleSheet} from "react-native"; import {Alert, Dimensions, KeyboardAvoidingView, StyleSheet} from "react-native";
import DropModalIcon from "@/assets/svgs/DropModalIcon"; import DropModalIcon from "@/assets/svgs/DropModalIcon";
import {IToDo} from "@/hooks/firebase/types/todoData"; import {IToDo, REPEAT_TYPE} from "@/hooks/firebase/types/todoData";
import AssigneesDisplay from "@/components/shared/AssigneesDisplay"; import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers"; import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import CalendarIcon from "@/assets/svgs/CalendarIcon"; import CalendarIcon from "@/assets/svgs/CalendarIcon";
@ -39,7 +39,7 @@ const defaultTodo = {
points: 10, points: 10,
date: new Date(), date: new Date(),
rotate: false, rotate: false,
repeatType: "Every week", repeatType: REPEAT_TYPE.NONE,
assignees: [], assignees: [],
repeatDays: [] repeatDays: []
}; };
@ -138,6 +138,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
padding: 0, padding: 0,
paddingTop: 4, paddingTop: 4,
margin: 0, margin: 0,
maxWidth: 600
}} }}
visible={addChoreDialogProps.isVisible} visible={addChoreDialogProps.isVisible}
> >
@ -260,7 +261,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
))} ))}
</Picker> </Picker>
</View> </View>
{todo.repeatType == "Every week" && <RepeatFreq handleRepeatDaysChange={handleRepeatDaysChange} {todo.repeatType == REPEAT_TYPE.EVERY_WEEK && <RepeatFreq handleRepeatDaysChange={handleRepeatDaysChange}
repeatDays={todo.repeatDays ?? []}/>} repeatDays={todo.repeatDays ?? []}/>}
</View> </View>
<View style={styles.divider}/> <View style={styles.divider}/>

View File

@ -8,7 +8,7 @@ import {IToDo} from "@/hooks/firebase/types/todoData";
import DropdownIcon from "@/assets/svgs/DropdownIcon"; import DropdownIcon from "@/assets/svgs/DropdownIcon";
import {Dropdown} from "react-native-element-dropdown"; import {Dropdown} from "react-native-element-dropdown";
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers"; import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import {useAuthContext} from "@/contexts/AuthContext"; import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
const FILTER_OPTIONS = { const FILTER_OPTIONS = {
@ -17,7 +17,12 @@ const FILTER_OPTIONS = {
}; };
const groupToDosByDate = (toDos: IToDo[]) => { const groupToDosByDate = (toDos: IToDo[]) => {
let sortedTodos = toDos.sort((a, b) => a.date - b.date); let sortedTodos = toDos.sort((a, b) => {
const dateA = a.date === null ? new Date() : a.date;
const dateB = b.date === null ? new Date() : b.date;
return dateA - dateB;
});
return sortedTodos.reduce( return sortedTodos.reduce(
(groups, toDo) => { (groups, toDo) => {
let dateKey; let dateKey;
@ -36,9 +41,15 @@ const groupToDosByDate = (toDos: IToDo[]) => {
}); });
}; };
if (toDo.date === null) { const isOverdue = (date: Date) => {
dateKey = "No Date"; const today = new Date();
} else if (isToday(toDo.date)) { today.setHours(0, 0, 0, 0);
return date < today;
};
if (isOverdue(toDo.date) && !toDo.done) {
dateKey = "Overdue";
} else if (toDo.date === null || isToday(toDo.date)) {
dateKey = "Today"; dateKey = "Today";
} else if (isTomorrow(toDo.date)) { } else if (isTomorrow(toDo.date)) {
dateKey = "Tomorrow"; dateKey = "Tomorrow";
@ -48,7 +59,8 @@ const groupToDosByDate = (toDos: IToDo[]) => {
dateKey = "Next 30 Days"; dateKey = "Next 30 Days";
subDateKey = format(toDo.date, "MMM d"); subDateKey = format(toDo.date, "MMM d");
} else { } else {
return groups; dateKey = "Later";
subDateKey = format(toDo.date, "MMM d, yyyy");
} }
if (!groups[dateKey]) { if (!groups[dateKey]) {
@ -58,7 +70,7 @@ const groupToDosByDate = (toDos: IToDo[]) => {
}; };
} }
if (dateKey === "Next 30 Days" && subDateKey) { if ((dateKey === "Next 30 Days" || dateKey === "Later") && subDateKey) {
if (!groups[dateKey].subgroups[subDateKey]) { if (!groups[dateKey].subgroups[subDateKey]) {
groups[dateKey].subgroups[subDateKey] = []; groups[dateKey].subgroups[subDateKey] = [];
} }
@ -80,12 +92,16 @@ const groupToDosByDate = (toDos: IToDo[]) => {
const resolveFilterOptions = (members, user) => { const resolveFilterOptions = (members, user) => {
let options = members?.map((member) => { let options = [];
members?.forEach((member) => {
let label = member?.firstName; let label = member?.firstName;
if (member.uid === user?.uid) { if (member.uid === user?.uid) {
label = FILTER_OPTIONS.ME; label = FILTER_OPTIONS.ME;
} }
return {value: member?.uid, label: label};
if (member.userType !== ProfileType.FAMILY_DEVICE) {
options.push({value: member?.uid, label: label});
}
}); });
options.push({value: FILTER_OPTIONS.EVERYONE, label: FILTER_OPTIONS.EVERYONE}) options.push({value: FILTER_OPTIONS.EVERYONE, label: FILTER_OPTIONS.EVERYONE})
@ -138,14 +154,17 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
}; };
const noDateToDos = groupedToDos["No Date"]?.items || []; const noDateToDos = groupedToDos["No Date"]?.items || [];
const datedToDos = Object.keys(groupedToDos).filter( const datedToDos = Object.keys(groupedToDos)
(key) => key !== "No Date" .filter((key) => key !== "No Date")
); .sort((a, b) => {
const order = ["Overdue", "Today", "Tomorrow", "Next 7 Days", "Next 30 Days", "Later"];
return order.indexOf(a) - order.indexOf(b);
});
const renderTodoGroup = (dateKey: string) => { const renderTodoGroup = (dateKey: string) => {
const isExpanded = expandedGroups[dateKey] || false; const isExpanded = expandedGroups[dateKey] || false;
if (dateKey === "Next 30 Days") { if (dateKey === "Next 30 Days" || dateKey === "Later") {
const subgroups = Object.entries(groupedToDos[dateKey].subgroups).sort( const subgroups = Object.entries(groupedToDos[dateKey].subgroups).sort(
([dateA], [dateB]) => { ([dateA], [dateB]) => {
const dateAObj = new Date(dateA); const dateAObj = new Date(dateA);

View File

@ -0,0 +1,67 @@
import React, { useRef, useEffect } from 'react';
import { TouchableOpacity, Animated, Easing } from 'react-native';
import { Feather } from '@expo/vector-icons';
interface RefreshButtonProps {
onRefresh: () => Promise<void>;
isSyncing: boolean;
size?: number;
color?: string;
}
const RefreshButton = ({
onRefresh,
isSyncing,
size = 24,
color = "#83807F"
}: RefreshButtonProps) => {
const rotateAnim = useRef(new Animated.Value(0)).current;
const rotationLoop = useRef<Animated.CompositeAnimation | null>(null);
useEffect(() => {
if (isSyncing) {
startContinuousRotation();
} else {
stopRotation();
}
}, [isSyncing]);
const startContinuousRotation = () => {
rotateAnim.setValue(0);
rotationLoop.current = Animated.loop(
Animated.timing(rotateAnim, {
toValue: 1,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true,
})
);
rotationLoop.current.start();
};
const stopRotation = () => {
rotationLoop.current?.stop();
rotateAnim.setValue(0);
};
const rotate = rotateAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
const handlePress = async () => {
if (!isSyncing) {
await onRefresh();
}
};
return (
<TouchableOpacity onPress={handlePress} disabled={isSyncing}>
<Animated.View style={{ transform: [{ rotate }] }}>
<Feather name="refresh-cw" size={size} color={color} />
</Animated.View>
</TouchableOpacity>
);
};
export default RefreshButton;