Files
cally/components/pages/todos/ToDosList.tsx

408 lines
12 KiB
TypeScript

import {Text, TouchableOpacity, View} from "react-native-ui-lib";
import React, {useEffect, useState} from "react";
import {useToDosContext} from "@/contexts/ToDosContext";
import ToDoItem from "./ToDoItem";
import {addDays, format, isToday, isTomorrow, isWithinInterval,} from "date-fns";
import {AntDesign} from "@expo/vector-icons";
import {IToDo} from "@/hooks/firebase/types/todoData";
import DropdownIcon from "@/assets/svgs/DropdownIcon";
import {Dropdown} from "react-native-element-dropdown";
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
import {ActivityIndicator, Dimensions, StyleSheet} from "react-native";
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
const FILTER_OPTIONS = {
ME: "Me",
EVERYONE: "Everyone"
};
const groupToDosByDate = (toDos: IToDo[]) => {
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(
(groups, toDo) => {
let dateKey;
let subDateKey;
const isNext7Days = (date: Date) => {
const today = new Date();
return isWithinInterval(date, { start: today, end: addDays(today, 7) });
};
const isNext30Days = (date: Date) => {
const today = new Date();
return isWithinInterval(date, {
start: today,
end: addDays(today, 30),
});
};
const isOverdue = (date: Date) => {
const today = new 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";
} else if (isTomorrow(toDo.date)) {
dateKey = "Tomorrow";
} else if (isNext7Days(toDo.date)) {
dateKey = "Next 7 Days";
} else if (isNext30Days(toDo.date)) {
dateKey = "Next 30 Days";
subDateKey = format(toDo.date, "MMM d");
} else {
dateKey = "Later";
subDateKey = format(toDo.date, "MMM d, yyyy");
}
if (!groups[dateKey]) {
groups[dateKey] = {
items: [],
subgroups: {},
};
}
if ((dateKey === "Next 30 Days" || dateKey === "Later") && subDateKey) {
if (!groups[dateKey].subgroups[subDateKey]) {
groups[dateKey].subgroups[subDateKey] = [];
}
groups[dateKey].subgroups[subDateKey].push(toDo);
} else {
groups[dateKey].items.push(toDo);
}
return groups;
},
{} as {
[key: string]: {
items: IToDo[];
subgroups: { [key: string]: IToDo[] };
};
}
);
};
const resolveFilterOptions = (members, user) => {
let options = [];
members?.forEach((member) => {
let label = member?.firstName;
if (member.uid === user?.uid) {
label = FILTER_OPTIONS.ME;
}
if (member.userType !== ProfileType.FAMILY_DEVICE) {
options.push({value: member?.uid, label: label});
}
});
options.push({value: FILTER_OPTIONS.EVERYONE, label: FILTER_OPTIONS.EVERYONE})
return options;
}
const ToDosList = ({ isSettings, members }: { isSettings?: boolean, members?: Array<UserProfile> }) => {
const { toDos } = useToDosContext();
const [localTodos, setLocalTodos] = useState([]);
useEffect(() => {
setLocalTodos(toDos);
}, [toDos]);
const [groupedToDos, setGroupedToDos] = useState([]);
const { user } = useAuthContext()
const [expandedGroups, setExpandedGroups] = useState<{
[key: string]: boolean;
}>({});
const [expandNoDate, setExpandNoDate] = useState<boolean>(true);
const [selectedFilter, setSelectedFilter] = useState(null);
const [filterOptions, setFilterOptions] = useState([]);
useEffect(() => {
let options = resolveFilterOptions(members, user);
setFilterOptions(options);
let selectedOption = options?.find((option) => option.value === user?.uid);
setSelectedFilter(selectedOption);
}, [JSON.stringify(members)]);
useEffect(() => {
if (localTodos && selectedFilter) {
let resolvedGroupedTodos;
if (selectedFilter?.value === FILTER_OPTIONS.EVERYONE) {
let filtered = localTodos?.filter((todo) =>
todo.assignees?.some(assigneeId =>
members.some(member => member.uid === assigneeId)
)
);
resolvedGroupedTodos = groupToDosByDate(filtered ?? []);
} else {
let filtered = localTodos?.filter((todo) => todo.assignees?.includes(selectedFilter.value)) || [];
resolvedGroupedTodos = groupToDosByDate(filtered || []);
}
setGroupedToDos(resolvedGroupedTodos || []);
}
}, [selectedFilter, JSON.stringify(localTodos)]);
const toggleExpand = (dateKey: string) => {
setExpandedGroups((prev) => ({
...prev,
[dateKey]: !prev[dateKey],
}));
};
const noDateToDos = groupedToDos["No Date"]?.items || [];
const datedToDos = Object.keys(groupedToDos)
.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 isExpanded = expandedGroups[dateKey] || false;
if (dateKey === "Next 30 Days" || dateKey === "Later") {
const subgroups = Object.entries(groupedToDos[dateKey].subgroups).sort(
([dateA], [dateB]) => {
const dateAObj = new Date(dateA);
const dateBObj = new Date(dateB);
return dateAObj.getTime() - dateBObj.getTime();
}
);
return (
<View key={dateKey}>
<TouchableOpacity
onPress={() => toggleExpand(dateKey)}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 0,
marginBottom: 4,
marginTop: 15,
}}
>
<Text
style={{
fontFamily: "Manrope_700Bold",
fontSize: 15,
color: "#484848",
}}
>
{dateKey}
</Text>
<AntDesign
name={isExpanded ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
/>
</TouchableOpacity>
{isExpanded &&
subgroups.map(([subDate, items]) => {
const sortedItems = [
...items.filter((toDo) => !toDo.done),
...items.filter((toDo) => toDo.done),
];
return (
<View key={subDate} marginT-15>
<View
style={{
marginBottom: 8,
}}
>
<Text
style={{
fontFamily: "Manrope_600SemiBold",
fontSize: 14,
color: "#919191",
}}
>
{subDate}
</Text>
</View>
{sortedItems.map((item) => (
<ToDoItem
isSettings={isSettings}
key={item.id}
item={item}
is7Days={false}
localTodos={localTodos}
setLocalTodos={setLocalTodos}
/>
))}
</View>
);
})}
</View>
);
}
const sortedToDos = [
...groupedToDos[dateKey].items.filter((toDo) => !toDo.done),
...groupedToDos[dateKey].items.filter((toDo) => toDo.done),
];
return (
<View key={dateKey}>
<TouchableOpacity
onPress={() => toggleExpand(dateKey)}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 0,
marginBottom: 4,
marginTop: 15,
}}
>
<Text
style={{
fontFamily: "Manrope_700Bold",
fontSize: 15,
color: "#484848",
}}
>
{dateKey}
</Text>
<AntDesign
name={isExpanded ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
/>
</TouchableOpacity>
{isExpanded &&
sortedToDos.map((item) => (
<ToDoItem
isSettings={isSettings}
key={item.id}
item={item}
is7Days={dateKey === "Next 7 Days"}
localTodos={localTodos}
setLocalTodos={setLocalTodos}
/>
))}
</View>
);
};
const containerHeight = Dimensions.get('screen').height;
return (
<View style={{ marginBottom: 110 }} >
{!toDos &&
<View style={styles.loaderContainer}>
<ActivityIndicator size="large" color="blue" />
</View>
}
<Dropdown
style={{ marginBottom: 15 }}
data={filterOptions}
value={selectedFilter}
placeholder="Filter todos"
placeholderStyle={{
color: "#a2a2a2",
fontFamily: "Manrope_500Medium",
fontSize: 13.2,
}}
labelField="label"
valueField="value"
iconColor="white"
activeColor={"#fd1775"}
containerStyle={styles.dropdownStyle}
itemTextStyle={styles.itemText}
itemContainerStyle={styles.itemStyle}
selectedTextStyle={styles.selectedText}
renderLeftIcon={() => (
<DropdownIcon
style={{ marginRight: 8 }}
/>
)}
renderItem={(item, selected) => {
return (
<View height={36.02} centerV>
<Text style={[
styles.itemText,
selected && { color: 'white' }
]}>
{item.label}
</Text>
</View>
);
}}
onChange={(item) => {
setSelectedFilter(item);
}}
/>
{noDateToDos.length > 0 && (
<View key="No Date">
<View row spread paddingH-19 marginB-12>
<Text
text70
style={{
fontWeight: "bold",
}}
>
Unscheduled
</Text>
<AntDesign
name={expandNoDate ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
onPress={() => {
setExpandNoDate(!expandNoDate);
}}
/>
</View>
{expandNoDate &&
noDateToDos
.sort((a, b) => Number(a.done) - Number(b.done))
.map((item) => (
<ToDoItem isSettings={isSettings} key={item.id} item={item} localTodos={localTodos} setLocalTodos={setLocalTodos} />
))}
</View>
)}
{datedToDos.map(renderTodoGroup)}
</View>
);
};
const styles = StyleSheet.create({
itemText: {
fontFamily: "Manrope_400Regular",
fontSize: 15.42,
paddingLeft: 15,
},
selectedText: {
fontFamily: "Manrope_500Medium",
fontSize: 13.2,
color: "#fd1775",
},
dropdownStyle: { borderRadius: 6.61, minHeight: 10, maxHeight: "100%", width: 187 },
itemStyle: { padding: 0, margin: 0 },
loaderContainer: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
zIndex: 999,
},
});
export default ToDosList;