diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx index 824e01d..7faff1d 100644 --- a/app/(auth)/_layout.tsx +++ b/app/(auth)/_layout.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import {Drawer} from "expo-router/drawer"; import {useSignOut} from "@/hooks/firebase/useSignOut"; import {DrawerContentScrollView,} from "@react-navigation/drawer"; @@ -10,7 +10,7 @@ import NavToDosIcon from "@/assets/svgs/NavToDosIcon"; import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon"; import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon"; import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon"; -import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon"; +import ViewSwitch from "@/components/pages/(tablet_pages)/ViewSwitch"; import {MaterialIcons} from "@expo/vector-icons"; import {useSetAtom} from "jotai"; import { @@ -20,6 +20,9 @@ import { userSettingsView, } from "@/components/pages/calendar/atoms"; import Ionicons from "@expo/vector-icons/Ionicons"; +import Constants from "expo-constants"; +import * as Device from "expo-device"; +import { DeviceType } from "expo-device"; export default function TabLayout() { const {mutateAsync: signOut} = useSignOut(); @@ -32,14 +35,20 @@ export default function TabLayout() { ({ headerShown: true, - drawerStyle: { + headerRight: () => + Device.deviceType === DeviceType.TABLET ? ( + + ) : ( + <> + ), + drawerStyle: { width: "90%", backgroundColor: "#f9f8f7", height: "100%", }, - }} + })} drawerContent={(props) => { return ( diff --git a/app/(auth)/calendar/index.tsx b/app/(auth)/calendar/index.tsx index 1ced3d2..8bbbc71 100644 --- a/app/(auth)/calendar/index.tsx +++ b/app/(auth)/calendar/index.tsx @@ -4,6 +4,10 @@ import { useSetAtom } from "jotai"; import CalendarPage from "@/components/pages/calendar/CalendarPage"; import { refreshTriggerAtom } from "@/components/pages/calendar/atoms"; import { colorMap } from "@/constants/colorMap"; +import { View } from "react-native-ui-lib"; +import TabletCalendarPage from "@/components/pages/(tablet_pages)/calendar/TabletCalendarPage"; +import { DeviceType } from "expo-device"; +import * as Device from "expo-device"; export default function Screen() { const [refreshing, setRefreshing] = useState(false); @@ -24,9 +28,13 @@ export default function Screen() { setRefreshing(false); } }, [setRefreshTrigger]); - + return ( - + {Device.deviceType === DeviceType.TABLET ? ( + + ) : ( + + )} + ); } diff --git a/app/(auth)/todos/index.tsx b/app/(auth)/todos/index.tsx index 7ccb813..33103a5 100644 --- a/app/(auth)/todos/index.tsx +++ b/app/(auth)/todos/index.tsx @@ -1,3 +1,4 @@ +import TabletChoresPage from "@/components/pages/(tablet_pages)/chores/TabletChoresPage"; import AddChore from "@/components/pages/todos/AddChore"; import ProgressCard from "@/components/pages/todos/ProgressCard"; import ToDoItem from "@/components/pages/todos/ToDoItem"; @@ -8,12 +9,17 @@ import { useAuthContext } from "@/contexts/AuthContext"; import { ToDosContextProvider, useToDosContext } from "@/contexts/ToDosContext"; import { AntDesign } from "@expo/vector-icons"; import { ScrollView } from "react-native-gesture-handler"; -import { Button, ButtonSize, View, Text } from "react-native-ui-lib"; +import { Button, ButtonSize, View, Text, Constants } from "react-native-ui-lib"; +import * as Device from "expo-device"; export default function Screen() { return ( - + {Device.deviceType === Device.DeviceType.TABLET ? ( + + ) : ( + + )} ); } diff --git a/assets/svgs/MenuIcon.tsx b/assets/svgs/MenuIcon.tsx index ad114a9..f54091a 100644 --- a/assets/svgs/MenuIcon.tsx +++ b/assets/svgs/MenuIcon.tsx @@ -7,8 +7,8 @@ interface MenuIconProps extends SvgProps { const MenuIcon: React.FC = (props) => ( = (props) => ( = (props) => ( d="M21.1.859H3.994C2.284.859.976 2.167.976 3.877c0 1.71 1.308 3.019 3.018 3.019H21.1V27.02" /> = (props) => ( d="M21.1 27.021H3.994c-1.71 0-3.018-1.308-3.018-3.018V3.878M21.097 3.878H3.991" /> = (props) => ( d="M6.007 6.897v12.075l3.019-1.006 3.018 1.006V6.897" /> -) -export default NavBrainDumpIcon +); +export default NavBrainDumpIcon; diff --git a/assets/svgs/NavCalendarIcon.tsx b/assets/svgs/NavCalendarIcon.tsx index 5744643..c7db638 100644 --- a/assets/svgs/NavCalendarIcon.tsx +++ b/assets/svgs/NavCalendarIcon.tsx @@ -1,9 +1,9 @@ -import * as React from "react" -import Svg, { Path, SvgProps } from "react-native-svg" +import * as React from "react"; +import Svg, { Path, SvgProps } from "react-native-svg"; const NavCalendarIcon: React.FC = (props) => ( = (props) => ( d="M1.825 10.075h25.043m-5.565 5.567L7.39 15.64m4.638 5.566H7.39m0-19.478V4.51m13.913-2.782V4.51M6.277 26.77h16.139c1.558 0 2.337 0 2.933-.303.523-.267.949-.692 1.216-1.216.303-.595.303-1.375.303-2.933V8.962c0-1.558 0-2.337-.303-2.933a2.782 2.782 0 0 0-1.216-1.216c-.596-.303-1.375-.303-2.933-.303H6.277c-1.558 0-2.337 0-2.933.303-.523.267-.949.693-1.216 1.216-.303.596-.303 1.375-.303 2.933v13.356c0 1.558 0 2.338.303 2.933.267.523.693.95 1.216 1.216.596.303 1.375.303 2.933.303Z" /> -) -export default NavCalendarIcon +); +export default NavCalendarIcon; diff --git a/assets/svgs/NavGroceryIcon.tsx b/assets/svgs/NavGroceryIcon.tsx index cb219b8..3bda0bf 100644 --- a/assets/svgs/NavGroceryIcon.tsx +++ b/assets/svgs/NavGroceryIcon.tsx @@ -2,17 +2,18 @@ import * as React from "react"; import Svg, { Path, SvgProps } from "react-native-svg"; const NavGroceryIcon: React.FC = (props) => ( diff --git a/assets/svgs/NavToDosIcon.tsx b/assets/svgs/NavToDosIcon.tsx index 9592aa3..1da328d 100644 --- a/assets/svgs/NavToDosIcon.tsx +++ b/assets/svgs/NavToDosIcon.tsx @@ -3,7 +3,7 @@ import Svg, { Path, SvgProps } from "react-native-svg" const NavToDosIcon: React.FC = (props) => ( ; // Adjust according to your navigation structure +} + +const ViewSwitch: React.FC = ({ navigation }) => { + const [pageIndex, setPageIndex] = useState(navigation.getState().index); + + useEffect(() => { + setPageIndex(navigation.getState().index); + }, [navigation.getState().index]) + + return ( + + { + navigation.navigate("calendar"); + }} + > + + + Calendar + + + + + { + navigation.navigate("todos"); + }} + > + + + Chores + + + + + ); +}; + +export default ViewSwitch; + +const styles = StyleSheet.create({ + switchBtnActive: { + backgroundColor: "#ea156c", + borderRadius: 50, + width: 110, + }, + switchBtn: { + backgroundColor: "#ebebeb", + borderRadius: 50, + width: 110, + }, + switchTxt: { + fontSize: 16, + fontFamily: "Manrope_600SemiBold", + }, +}); diff --git a/components/pages/(tablet_pages)/calendar/TabletCalendarPage.tsx b/components/pages/(tablet_pages)/calendar/TabletCalendarPage.tsx new file mode 100644 index 0000000..8465b9b --- /dev/null +++ b/components/pages/(tablet_pages)/calendar/TabletCalendarPage.tsx @@ -0,0 +1,29 @@ +import { View, Text } from "react-native-ui-lib"; +import React, { useEffect } from "react"; +import * as ScreenOrientation from "expo-screen-orientation"; +import { InnerCalendar } from "../../calendar/InnerCalendar"; +import TabletContainer from "../tablet_components/TabletContainer"; + +const TabletCalendarPage = () => { + const lockScreenOrientation = async () => { + await ScreenOrientation.lockAsync( + ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT + ); + }; + + useEffect(() => { + lockScreenOrientation(); + + return () => { + ScreenOrientation.unlockAsync(); + }; + }, []); + + return ( + + + + ); +}; + +export default TabletCalendarPage; diff --git a/components/pages/(tablet_pages)/chores/SingleUserChoreList.tsx b/components/pages/(tablet_pages)/chores/SingleUserChoreList.tsx new file mode 100644 index 0000000..c1a2e35 --- /dev/null +++ b/components/pages/(tablet_pages)/chores/SingleUserChoreList.tsx @@ -0,0 +1,271 @@ +import { View, Text, TouchableOpacity, Icon } from "react-native-ui-lib"; +import React, { useState } from "react"; +import { useToDosContext } from "@/contexts/ToDosContext"; +import { + addDays, + format, + isToday, + isTomorrow, + isWithinInterval, +} from "date-fns"; +import { AntDesign } from "@expo/vector-icons"; +import { IToDo } from "@/hooks/firebase/types/todoData"; +import ToDoItem from "../../todos/ToDoItem"; +import { UserProfile } from "@/hooks/firebase/types/profileTypes"; + +const groupToDosByDate = (toDos: IToDo[]) => { + let sortedTodos = toDos.sort((a, b) => a.date - b.date); + 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), + }); + }; + + if (toDo.date === null) { + dateKey = "No Date"; + } else if (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 { + return groups; + } + + if (!groups[dateKey]) { + groups[dateKey] = { + items: [], + subgroups: {}, + }; + } + + if (dateKey === "Next 30 Days" && 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 filterToDosByUser = (toDos: IToDo[], uid: string | undefined) => { + if (!uid) return []; + return toDos.filter((todo) => + todo.assignees?.includes(uid) + ); +}; + +const SingleUserChoreList = ({ user }: { user: UserProfile }) => { + const { toDos } = useToDosContext(); + const userTodos = filterToDosByUser(toDos, user.uid); + const groupedToDos = groupToDosByDate(userTodos); + + const [expandedGroups, setExpandedGroups] = useState<{ + [key: string]: boolean; + }>({}); + + const [expandNoDate, setExpandNoDate] = useState(true); + + 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" + ); + + const renderTodoGroup = (dateKey: string) => { + const isExpanded = expandedGroups[dateKey] || false; + + if (dateKey === "Next 30 Days") { + 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 ( + + toggleExpand(dateKey)} + style={{ + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: 0, + marginBottom: 4, + marginTop: 15, + }} + > + + {dateKey} + + + + + {isExpanded && + subgroups.map(([subDate, items]) => { + const sortedItems = [ + ...items.filter((toDo) => !toDo.done), + ...items.filter((toDo) => toDo.done), + ]; + + return ( + + + + {subDate} + + + + {sortedItems.map((item) => ( + + ))} + + ); + })} + + ); + } + + const sortedToDos = [ + ...groupedToDos[dateKey].items.filter((toDo) => !toDo.done), + ...groupedToDos[dateKey].items.filter((toDo) => toDo.done), + ]; + + return ( + + toggleExpand(dateKey)} + style={{ + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + paddingHorizontal: 0, + marginBottom: 4, + marginTop: 15, + }} + > + + {dateKey} + + + + + {isExpanded && + sortedToDos.map((item) => ( + + ))} + + ); + }; + + return ( + + {noDateToDos.length > 0 && ( + + + + Unscheduled + + { + setExpandNoDate(!expandNoDate); + }} + /> + + {expandNoDate && + noDateToDos + .sort((a, b) => Number(a.done) - Number(b.done)) + .map((item) => )} + + )} + + {datedToDos.map(renderTodoGroup)} + + ); +}; + +export default SingleUserChoreList; diff --git a/components/pages/(tablet_pages)/chores/TabletChoresPage.tsx b/components/pages/(tablet_pages)/chores/TabletChoresPage.tsx new file mode 100644 index 0000000..f5f2559 --- /dev/null +++ b/components/pages/(tablet_pages)/chores/TabletChoresPage.tsx @@ -0,0 +1,85 @@ +import React, { useEffect } from "react"; +import { View, Text } from "react-native-ui-lib"; +import * as ScreenOrientation from "expo-screen-orientation"; +import TabletContainer from "../tablet_components/TabletContainer"; +import ToDosPage from "../../todos/ToDosPage"; +import ToDosList from "../../todos/ToDosList"; +import SingleUserChoreList from "./SingleUserChoreList"; +import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers"; +import { ImageBackground, StyleSheet } from "react-native"; +import { colorMap } from "@/constants/colorMap"; +import { ScrollView } from "react-native-gesture-handler"; + +const TabletChoresPage = () => { + const { data: users } = useGetFamilyMembers(); + // Function to lock the screen orientation to landscape + const lockScreenOrientation = async () => { + await ScreenOrientation.lockAsync( + ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT + ); + }; + + useEffect(() => { + lockScreenOrientation(); // Lock orientation when the component mounts + + return () => { + // Optional: Unlock to default when the component unmounts + ScreenOrientation.unlockAsync(); + }; + }, []); + + return ( + + + + {users?.map((user, index) => ( + + + {user.pfp ? ( + + ) : ( + + + {user.firstName.at(0)} + {user.lastName.at(0)} + + + )} + + {user.firstName} + + + ({user.userType}) + + + + + ))} + + + + ); +}; + +const styles = StyleSheet.create({ + pfp: { + width: 46.74, + aspectRatio: 1, + borderRadius: 13.33, + }, + name: { + fontFamily: "Manrope_600SemiBold", + fontSize: 22.43, + color: "#2c2c2c", + }, +}); + +export default TabletChoresPage; diff --git a/components/pages/(tablet_pages)/tablet_components/TabletContainer.tsx b/components/pages/(tablet_pages)/tablet_components/TabletContainer.tsx new file mode 100644 index 0000000..68cb0a1 --- /dev/null +++ b/components/pages/(tablet_pages)/tablet_components/TabletContainer.tsx @@ -0,0 +1,52 @@ +import { View, Text, ViewProps } from "react-native-ui-lib"; +import React, { ReactNode } from "react"; +import { Dimensions, StyleSheet } from "react-native"; +import UsersList from "./UsersList"; +import { ScrollView } from "react-native-gesture-handler"; + +interface TabletContainerProps extends ViewProps { + children: ReactNode; +} + +const { width, height } = Dimensions.get("window"); + +const TabletContainer: React.FC = ({ + children, + ...props +}) => { + return ( + + + {children} + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: "white", + flex: 1, + borderTopColor: "#a9a9a9", + borderTopWidth: 1, + }, + calendarContainer: { + backgroundColor: "white", + height: height, + width: width * 0.89, + }, + profilesContainer: { + width: width * 0.11, + height: height, + borderLeftWidth: 1, + borderLeftColor: "#a9a9a9", + backgroundColor: "white", + }, +}); + +export default TabletContainer; diff --git a/components/pages/(tablet_pages)/tablet_components/UsersList.tsx b/components/pages/(tablet_pages)/tablet_components/UsersList.tsx new file mode 100644 index 0000000..82b488d --- /dev/null +++ b/components/pages/(tablet_pages)/tablet_components/UsersList.tsx @@ -0,0 +1,67 @@ +import { View, Text } from "react-native-ui-lib"; +import React from "react"; +import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers"; +import { ImageBackground, StyleSheet } from "react-native"; +import { colorMap } from "@/constants/colorMap"; + +const UsersList = () => { + const { data: familyMembers } = useGetFamilyMembers(); + + const capitalizeFirstLetter = (str: string) => { + if (!str) return ''; + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); + }; + + return ( + + {familyMembers?.map((member, index) => ( + <> + {member.pfp ? ( + + ) : ( + + {member.firstName.at(0)} + {member.lastName.at(0)} + + } + /> + )} + {member.firstName} + {capitalizeFirstLetter(member.userType)} + + ))} + + ); +}; + +const styles = StyleSheet.create({ + pfp: { + aspectRatio: 1, + width: 113, + borderRadius: 100, + marginBottom: 8, + }, + fName: { + fontFamily: "Manrope_600SemiBold", + fontSize: 18.15, + }, + role: { + fontFamily: "Manrope_600SemiBold", + fontSize: 12.95, + color: "#9b9b9b", + marginBottom: 20, + }, +}); + +export default UsersList; diff --git a/components/pages/todos/ToDoItem.tsx b/components/pages/todos/ToDoItem.tsx index aa50175..81fd8bf 100644 --- a/components/pages/todos/ToDoItem.tsx +++ b/components/pages/todos/ToDoItem.tsx @@ -84,7 +84,7 @@ const ToDoItem = (props: { style={{ textDecorationLine: props.item.done ? "line-through" : "none", fontFamily: "Manrope_500Medium", - color: props.item.done? "#a09f9f": "black", + color: props.item.done ? "#a09f9f" : "black", fontSize: 15, }} onPress={() => { @@ -191,7 +191,7 @@ const ToDoItem = (props: { >