mirror of
https://github.com/urosran/cally.git
synced 2025-11-26 00:24:53 +00:00
Merge branch 'dev'
This commit is contained in:
@ -320,20 +320,14 @@ export default function TabLayout() {
|
|||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
drawerLabel: "Calendar",
|
drawerLabel: "Calendar",
|
||||||
title:
|
title: "Calendar",
|
||||||
Device.deviceType === DeviceType.TABLET
|
|
||||||
? "Family Calendar"
|
|
||||||
: "Calendar",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Drawer.Screen
|
<Drawer.Screen
|
||||||
name="calendar"
|
name="calendar"
|
||||||
options={{
|
options={{
|
||||||
drawerLabel: "Calendar",
|
drawerLabel: "Calendar",
|
||||||
title:
|
title: "Calendar",
|
||||||
Device.deviceType === DeviceType.TABLET
|
|
||||||
? "Family Calendar"
|
|
||||||
: "Calendar",
|
|
||||||
drawerItemStyle: { display: "none" },
|
drawerItemStyle: { display: "none" },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import {RefreshControl, ScrollView, View} from "react-native";
|
import {RefreshControl, ScrollView, View} from "react-native";
|
||||||
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
||||||
import TabletCalendarPage from "@/components/pages/(tablet_pages)/calendar/TabletCalendarPage";
|
import TabletCalendarPage from "@/components/pages/(tablet_pages)/calendar/TabletCalendarPage";
|
||||||
@ -6,10 +6,20 @@ import * as Device from "expo-device";
|
|||||||
import {DeviceType} from "expo-device";
|
import {DeviceType} from "expo-device";
|
||||||
import {useCalSync} from "@/hooks/useCalSync";
|
import {useCalSync} from "@/hooks/useCalSync";
|
||||||
import {colorMap} from "@/constants/colorMap";
|
import {colorMap} from "@/constants/colorMap";
|
||||||
|
import { useSetAtom } from "jotai";
|
||||||
|
import { selectedUserAtom } from "@/components/pages/calendar/atoms";
|
||||||
|
import { useAuthContext } from "@/contexts/AuthContext";
|
||||||
|
|
||||||
export default function Screen() {
|
export default function Screen() {
|
||||||
const isTablet = Device.deviceType === DeviceType.TABLET;
|
const isTablet = Device.deviceType === DeviceType.TABLET;
|
||||||
const {resyncAllCalendars, isSyncing} = useCalSync();
|
const {resyncAllCalendars, isSyncing} = useCalSync();
|
||||||
|
const setSelectedUser = useSetAtom(selectedUserAtom);
|
||||||
|
const {profileData} = useAuthContext()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!isTablet && profileData) setSelectedUser({firstName: profileData.firstName, lastName: profileData.lastName, eventColor: profileData.eventColor})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
const onRefresh = React.useCallback(async () => {
|
const onRefresh = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -39,7 +39,15 @@ const groupToDosByDate = (toDos: IToDo[]) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (toDo.date === null || isToday(toDo.date)) {
|
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";
|
dateKey = "Today";
|
||||||
} else if (isTomorrow(toDo.date)) {
|
} else if (isTomorrow(toDo.date)) {
|
||||||
dateKey = "Tomorrow";
|
dateKey = "Tomorrow";
|
||||||
@ -49,7 +57,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]) {
|
||||||
@ -59,7 +68,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] = [];
|
||||||
}
|
}
|
||||||
@ -103,14 +112,17 @@ const SingleUserChoreList = ({ user }: { user: UserProfile }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@ -5,14 +5,16 @@ import TabletContainer from "../tablet_components/TabletContainer";
|
|||||||
import SingleUserChoreList from "./SingleUserChoreList";
|
import SingleUserChoreList from "./SingleUserChoreList";
|
||||||
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
|
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
|
||||||
import { ImageBackground, StyleSheet } from "react-native";
|
import { ImageBackground, StyleSheet } from "react-native";
|
||||||
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";
|
import AddChore from "../../todos/AddChore";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { selectedUserAtom } from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
const TabletChoresPage = () => {
|
const TabletChoresPage = () => {
|
||||||
const {data: users} = useGetFamilyMembers();
|
const {data: users} = useGetFamilyMembers();
|
||||||
const { user: currentUser } = useAuthContext();
|
const { user: currentUser } = useAuthContext();
|
||||||
|
const [selectedUser] = useAtom(selectedUserAtom);
|
||||||
|
|
||||||
const sortedUsers = useMemo(() => {
|
const sortedUsers = useMemo(() => {
|
||||||
return users
|
return users
|
||||||
@ -31,79 +33,77 @@ const TabletChoresPage = () => {
|
|||||||
});
|
});
|
||||||
}, [users, currentUser]);
|
}, [users, currentUser]);
|
||||||
|
|
||||||
// Function to lock the screen orientation to landscape
|
|
||||||
const lockScreenOrientation = async () => {
|
const lockScreenOrientation = async () => {
|
||||||
await ScreenOrientation.lockAsync(
|
await ScreenOrientation.lockAsync(
|
||||||
ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT
|
ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
lockScreenOrientation(); // Lock orientation when the component mounts
|
lockScreenOrientation();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// Optional: Unlock to default when the component unmounts
|
ScreenOrientation.unlockAsync();
|
||||||
ScreenOrientation.unlockAsync();
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const capitalizeFirstLetter = (str: string) => {
|
||||||
|
if (!str) return "";
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||||
};
|
};
|
||||||
}, []);
|
|
||||||
|
|
||||||
const capitalizeFirstLetter = (str: string) => {
|
return (
|
||||||
if (!str) return "";
|
<TabletContainer>
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
<ScrollView horizontal>
|
||||||
};
|
<View row gap-25 padding-25>
|
||||||
|
{sortedUsers
|
||||||
return (
|
?.filter((member) =>
|
||||||
<TabletContainer>
|
!selectedUser ||
|
||||||
<ScrollView horizontal>
|
selectedUser.uid === 'family-view' ||
|
||||||
<View row gap-25 padding-25>
|
selectedUser.uid === member.uid
|
||||||
{sortedUsers
|
)
|
||||||
?.filter((member) => member.userType !== ProfileType.FAMILY_DEVICE)
|
.map((user, index) => (
|
||||||
.map((user, index) => (
|
<View key={index}>
|
||||||
<View key={index}>
|
<View row centerV>
|
||||||
<View row centerV>
|
{user.pfp ? (
|
||||||
{user.pfp ? (
|
<ImageBackground
|
||||||
<ImageBackground
|
source={{ uri: user.pfp }}
|
||||||
source={{ uri: user.pfp }}
|
style={styles.pfp}
|
||||||
style={
|
imageStyle={(user.eventColor && {
|
||||||
styles.pfp
|
borderWidth: 2,
|
||||||
}
|
borderColor: user.eventColor,
|
||||||
imageStyle={(user.eventColor && {
|
}) || undefined}
|
||||||
borderWidth: 2,
|
borderRadius={13.33}
|
||||||
borderColor: user.eventColor,
|
/>
|
||||||
}) ||
|
) : (
|
||||||
undefined
|
<View
|
||||||
}
|
center
|
||||||
borderRadius={13.33}
|
style={styles.pfp}
|
||||||
/>
|
backgroundColor={user.eventColor || "#00a8b6"}
|
||||||
) : (
|
>
|
||||||
<View
|
<Text color="white">
|
||||||
center
|
{user.firstName.at(0)}
|
||||||
style={styles.pfp}
|
{user.lastName.at(0)}
|
||||||
backgroundColor={user.eventColor || "#00a8b6"}
|
</Text>
|
||||||
>
|
</View>
|
||||||
<Text color="white">
|
)}
|
||||||
{user.firstName.at(0)}
|
<Text style={styles.name} marginL-15>
|
||||||
{user.lastName.at(0)}
|
{user.firstName}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
<Text style={[styles.name, { color: "#9b9b9b" }]} marginL-5>
|
||||||
)}
|
({capitalizeFirstLetter(user.userType)})
|
||||||
<Text style={styles.name} marginL-15>
|
</Text>
|
||||||
{user.firstName}
|
</View>
|
||||||
</Text>
|
<SingleUserChoreList user={user} />
|
||||||
<Text style={[styles.name, { color: "#9b9b9b" }]} marginL-5>
|
</View>
|
||||||
({capitalizeFirstLetter(user.userType)})
|
))}
|
||||||
</Text>
|
</View>
|
||||||
</View>
|
</ScrollView>
|
||||||
<SingleUserChoreList user={user} />
|
<View style={styles.addBtn}>
|
||||||
|
<AddChore />
|
||||||
</View>
|
</View>
|
||||||
))}
|
</TabletContainer>
|
||||||
</View>
|
);
|
||||||
</ScrollView>
|
|
||||||
<View style={styles.addBtn}>
|
|
||||||
<AddChore />
|
|
||||||
</View>
|
|
||||||
</TabletContainer>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
@ -118,10 +118,10 @@ const styles = StyleSheet.create({
|
|||||||
color: "#2c2c2c",
|
color: "#2c2c2c",
|
||||||
},
|
},
|
||||||
addBtn: {
|
addBtn: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: 50,
|
bottom: 50,
|
||||||
right: 220
|
right: 220
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TabletChoresPage;
|
export default TabletChoresPage;
|
||||||
@ -1,37 +1,42 @@
|
|||||||
import { View, Text } from "react-native-ui-lib";
|
import { View, Text, TouchableOpacity } from "react-native-ui-lib";
|
||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
|
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
|
||||||
import { ImageBackground, StyleSheet } from "react-native";
|
import { ImageBackground, StyleSheet } from "react-native";
|
||||||
import { colorMap } from "@/constants/colorMap";
|
import { colorMap } from "@/constants/colorMap";
|
||||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||||
import { ScrollView } from "react-native-gesture-handler";
|
import { useAtom } from "jotai";
|
||||||
|
import { selectedUserAtom } from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
const UsersList = () => {
|
const UsersList = () => {
|
||||||
const { user: currentUser } = useAuthContext();
|
const { user: currentUser } = useAuthContext();
|
||||||
const { data: familyMembers, refetch: refetchFamilyMembers } =
|
const { data: familyMembers, refetch: refetchFamilyMembers } =
|
||||||
useGetFamilyMembers();
|
useGetFamilyMembers();
|
||||||
|
const [selectedUser, setSelectedUser] = useAtom(selectedUserAtom);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refetchFamilyMembers();
|
refetchFamilyMembers();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const sortedMembers = useMemo(() => {
|
const sortedMembers = useMemo(() => {
|
||||||
return familyMembers
|
const filtered = familyMembers
|
||||||
?.filter((member) => member.userType !== ProfileType.FAMILY_DEVICE)
|
?.filter((member) => member.userType !== ProfileType.FAMILY_DEVICE);
|
||||||
.sort((a, b) => {
|
|
||||||
// Current user first
|
const currentUserData = filtered?.find(m => m.uid === currentUser?.uid);
|
||||||
if (a.uid === currentUser?.uid) return -1;
|
const parents = filtered?.filter(m => m.userType === ProfileType.PARENT && m.uid !== currentUser?.uid) || [];
|
||||||
if (b.uid === currentUser?.uid) return 1;
|
const children = filtered?.filter(m => m.userType === ProfileType.CHILD && m.uid !== currentUser?.uid) || [];
|
||||||
|
const caregivers = filtered?.filter(m => m.userType === ProfileType.CAREGIVER && m.uid !== currentUser?.uid) || [];
|
||||||
// Then sort by user type priority
|
|
||||||
const typePriority = {
|
const familyViewOption = {
|
||||||
[ProfileType.PARENT]: 0,
|
uid: 'family-view',
|
||||||
[ProfileType.CHILD]: 1,
|
firstName: 'Family',
|
||||||
[ProfileType.CAREGIVER]: 2,
|
lastName: 'View',
|
||||||
};
|
userType: 'Family View',
|
||||||
|
eventColor: colorMap.pink
|
||||||
return typePriority[a.userType] - typePriority[b.userType];
|
};
|
||||||
});
|
|
||||||
|
return currentUserData
|
||||||
|
? [currentUserData, ...parents, ...children, familyViewOption, ...caregivers]
|
||||||
|
: [...parents, ...children, familyViewOption, ...caregivers];
|
||||||
}, [familyMembers, currentUser]);
|
}, [familyMembers, currentUser]);
|
||||||
|
|
||||||
const capitalizeFirstLetter = (str: string) => {
|
const capitalizeFirstLetter = (str: string) => {
|
||||||
@ -39,37 +44,54 @@ const UsersList = () => {
|
|||||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(selectedUser);
|
||||||
|
}, [selectedUser]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View centerH paddingT-10 marginB-70>
|
<View centerH paddingT-10 marginB-70>
|
||||||
{sortedMembers?.map((member, index) => (
|
{sortedMembers?.map((member, index) => (
|
||||||
<React.Fragment key={member.uid}>
|
<TouchableOpacity
|
||||||
{member.pfp ? (
|
key={member.uid}
|
||||||
<ImageBackground
|
onPress={() => {
|
||||||
key={index}
|
if (member.uid === 'family-view') {
|
||||||
source={{ uri: member.pfp }}
|
setSelectedUser(null);
|
||||||
style={styles.pfp}
|
} else {
|
||||||
borderRadius={200}
|
setSelectedUser(selectedUser?.uid === member.uid ? null : member);
|
||||||
imageStyle={{ borderWidth: 2, borderColor: "red" }}
|
}
|
||||||
/>
|
}}
|
||||||
) : (
|
style={[
|
||||||
<View
|
styles.memberContainer,
|
||||||
key={index}
|
selectedUser?.uid === member.uid && styles.selectedMember,
|
||||||
style={styles.pfp}
|
]}
|
||||||
center
|
>
|
||||||
backgroundColor={member.eventColor || colorMap.teal}
|
{member.pfp ? (
|
||||||
>
|
<ImageBackground
|
||||||
<Text color="white">
|
key={index}
|
||||||
{member.firstName.at(0)}
|
source={{ uri: member.pfp }}
|
||||||
{member.lastName.at(0)}
|
style={styles.pfp}
|
||||||
</Text>
|
borderRadius={200}
|
||||||
</View>
|
imageStyle={{ borderWidth: 2, borderColor: "red" }}
|
||||||
)}
|
/>
|
||||||
<Text style={styles.fName}>{member.firstName}</Text>
|
) : (
|
||||||
<Text style={styles.role}>
|
<View
|
||||||
{capitalizeFirstLetter(member.userType)}
|
key={index}
|
||||||
</Text>
|
style={styles.pfp}
|
||||||
</React.Fragment>
|
center
|
||||||
))}
|
backgroundColor={member.eventColor || colorMap.teal}
|
||||||
|
>
|
||||||
|
<Text color="white">
|
||||||
|
{member.firstName.at(0)}
|
||||||
|
{member.lastName.at(0)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<Text style={styles.fName}>{member.firstName}</Text>
|
||||||
|
<Text style={styles.role}>
|
||||||
|
{capitalizeFirstLetter(member.userType)}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -91,6 +113,13 @@ const styles = StyleSheet.create({
|
|||||||
color: "#9b9b9b",
|
color: "#9b9b9b",
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
|
memberContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
selectedMember: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default UsersList;
|
export default UsersList;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
modeAtom,
|
modeAtom,
|
||||||
selectedDateAtom,
|
selectedDateAtom,
|
||||||
selectedNewEventDateAtom,
|
selectedNewEventDateAtom,
|
||||||
|
selectedUserAtom,
|
||||||
} from "@/components/pages/calendar/atoms";
|
} from "@/components/pages/calendar/atoms";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
|
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
|
||||||
@ -21,6 +22,8 @@ import {useSyncEvents} from "@/hooks/useSyncOnScroll";
|
|||||||
import {colorMap} from "@/constants/colorMap";
|
import {colorMap} from "@/constants/colorMap";
|
||||||
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||||
import CachedImage from "expo-cached-image";
|
import CachedImage from "expo-cached-image";
|
||||||
|
import { DeviceType } from "expo-device";
|
||||||
|
import * as Device from "expo-device"
|
||||||
|
|
||||||
interface EventCalendarProps {
|
interface EventCalendarProps {
|
||||||
calendarHeight: number;
|
calendarHeight: number;
|
||||||
@ -377,6 +380,9 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
|
|||||||
const [mode, setMode] = useAtom(modeAtom);
|
const [mode, setMode] = useAtom(modeAtom);
|
||||||
const [isFamilyView] = useAtom(isFamilyViewAtom);
|
const [isFamilyView] = useAtom(isFamilyViewAtom);
|
||||||
|
|
||||||
|
//tablet view filter
|
||||||
|
const [selectedUser] = useAtom(selectedUserAtom);
|
||||||
|
|
||||||
const setEditVisible = useSetAtom(editVisibleAtom);
|
const setEditVisible = useSetAtom(editVisibleAtom);
|
||||||
const [isAllDay, setIsAllDay] = useAtom(isAllDayAtom);
|
const [isAllDay, setIsAllDay] = useAtom(isAllDayAtom);
|
||||||
const setEventForEdit = useSetAtom(eventForEditAtom);
|
const setEventForEdit = useSetAtom(eventForEditAtom);
|
||||||
@ -488,8 +494,17 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
|
|||||||
const startOffset = mode === "month" ? 40 : (mode === "week" || mode === "3days") ? 10 : 1;
|
const startOffset = mode === "month" ? 40 : (mode === "week" || mode === "3days") ? 10 : 1;
|
||||||
const endOffset = mode === "month" ? 40 : (mode === "week" || mode === "3days") ? 10 : 1;
|
const endOffset = mode === "month" ? 40 : (mode === "week" || mode === "3days") ? 10 : 1;
|
||||||
|
|
||||||
|
let eventsToFilter = events;
|
||||||
|
|
||||||
|
if (selectedUser && Device.deviceType === DeviceType.TABLET) {
|
||||||
|
eventsToFilter = events?.filter(event =>
|
||||||
|
event.attendees?.includes(selectedUser.uid) ||
|
||||||
|
event.creatorId === selectedUser.uid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const filteredEvents =
|
const filteredEvents =
|
||||||
events?.filter(
|
eventsToFilter?.filter(
|
||||||
(event) =>
|
(event) =>
|
||||||
event.start &&
|
event.start &&
|
||||||
event.end &&
|
event.end &&
|
||||||
@ -521,7 +536,7 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
|
|||||||
// console.log("memoizedEvents computation time:", endTime - startTime, "ms");
|
// console.log("memoizedEvents computation time:", endTime - startTime, "ms");
|
||||||
|
|
||||||
return {enrichedEvents, filteredEvents};
|
return {enrichedEvents, filteredEvents};
|
||||||
}, [events, selectedDate, mode]);
|
}, [events, selectedDate, mode, selectedUser]);
|
||||||
|
|
||||||
const renderCustomDateForMonth = (date: Date) => {
|
const renderCustomDateForMonth = (date: Date) => {
|
||||||
const circleStyle = useMemo<ViewStyle>(
|
const circleStyle = useMemo<ViewStyle>(
|
||||||
|
|||||||
@ -18,4 +18,10 @@ export const settingsPageIndex = atom<number>(0);
|
|||||||
export const userSettingsView = atom<boolean>(true);
|
export const userSettingsView = atom<boolean>(true);
|
||||||
export const toDosPageIndex = atom<number>(0);
|
export const toDosPageIndex = atom<number>(0);
|
||||||
export const refreshTriggerAtom = atom<boolean>(false);
|
export const refreshTriggerAtom = atom<boolean>(false);
|
||||||
export const refreshEnabledAtom = atom<boolean>(true);
|
export const refreshEnabledAtom = atom<boolean>(true);
|
||||||
|
export const selectedUserAtom = atom<{
|
||||||
|
uid: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
eventColor?: string;
|
||||||
|
} | null>(null);
|
||||||
@ -113,24 +113,24 @@ const EditGroceryItem = ({
|
|||||||
}}
|
}}
|
||||||
maxLength={25}
|
maxLength={25}
|
||||||
/>
|
/>
|
||||||
<View row centerV>
|
{(editGrocery.title || editGrocery.title !== "") && <View row centerV>
|
||||||
<AntDesign
|
<AntDesign
|
||||||
name="check"
|
name="check"
|
||||||
size={24}
|
size={24}
|
||||||
style={{
|
style={{
|
||||||
color: "green",
|
color: "green",
|
||||||
marginRight: 15,
|
marginRight: 15,
|
||||||
}}
|
}}
|
||||||
onPress={handleSubmit}
|
onPress={handleSubmit}
|
||||||
/>
|
/>
|
||||||
<CloseXIcon
|
<CloseXIcon
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (editGrocery.closeEdit) {
|
if (editGrocery.closeEdit) {
|
||||||
editGrocery.closeEdit();
|
editGrocery.closeEdit();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>}
|
||||||
</View>
|
</View>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
style={{ marginTop: 15 }}
|
style={{ marginTop: 15 }}
|
||||||
|
|||||||
@ -134,17 +134,29 @@ const GroceryItem = ({
|
|||||||
) : (
|
) : (
|
||||||
!isEditingTitle &&
|
!isEditingTitle &&
|
||||||
(isParent || isCaregiver) && (
|
(isParent || isCaregiver) && (
|
||||||
<Checkbox
|
<View row>
|
||||||
value={item.bought}
|
{item.bought &&
|
||||||
containerStyle={[styles.checkbox, { borderRadius: 50 }]}
|
<AntDesign
|
||||||
style={styles.checked}
|
name="close"
|
||||||
borderRadius={50}
|
size={24}
|
||||||
color="#fd1575"
|
style={{ color: "grey", marginRight: 10 }}
|
||||||
hitSlop={20}
|
onPress={() => {
|
||||||
onValueChange={() =>
|
handleItemApproved(item.id, { approved: false });
|
||||||
updateGroceryItem({ id: item.id, bought: !item.bought })
|
deleteGrocery(item.id);
|
||||||
}
|
}}/>
|
||||||
/>
|
}
|
||||||
|
<Checkbox
|
||||||
|
value={item.bought}
|
||||||
|
containerStyle={[styles.checkbox, { borderRadius: 50 }]}
|
||||||
|
style={styles.checked}
|
||||||
|
borderRadius={50}
|
||||||
|
color="#fd1575"
|
||||||
|
hitSlop={20}
|
||||||
|
onValueChange={() =>
|
||||||
|
updateGroceryItem({ id: item.id, bought: !item.bought })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@ -8,7 +8,21 @@ import {AntDesign} from "@expo/vector-icons";
|
|||||||
import EditGroceryItem from "./EditGroceryItem";
|
import EditGroceryItem from "./EditGroceryItem";
|
||||||
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {IGrocery} from "@/hooks/firebase/types/groceryData";
|
import {IGrocery} from "@/hooks/firebase/types/groceryData";
|
||||||
import AddPersonIcon from "@/assets/svgs/AddPersonIcon";
|
import Ionicons from '@expo/vector-icons/Ionicons';
|
||||||
|
import AddChoreDialog from "@/components/pages/todos/AddChoreDialog";
|
||||||
|
import {REPEAT_TYPE} from "@/hooks/firebase/types/todoData";
|
||||||
|
import {ToDosContextProvider} from "@/contexts/ToDosContext";
|
||||||
|
|
||||||
|
const shoppingTodo = {
|
||||||
|
id: "",
|
||||||
|
title: "Go shopping",
|
||||||
|
points: 10,
|
||||||
|
date: new Date(),
|
||||||
|
rotate: false,
|
||||||
|
repeatType: REPEAT_TYPE.NONE,
|
||||||
|
assignees: [],
|
||||||
|
repeatDays: []
|
||||||
|
};
|
||||||
|
|
||||||
const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
||||||
const {
|
const {
|
||||||
@ -34,11 +48,15 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
|||||||
|
|
||||||
const [pendingVisible, setPendingVisible] = useState<boolean>(true);
|
const [pendingVisible, setPendingVisible] = useState<boolean>(true);
|
||||||
const [approvedVisible, setApprovedVisible] = useState<boolean>(true);
|
const [approvedVisible, setApprovedVisible] = useState<boolean>(true);
|
||||||
|
const [choreDialogVisible, setChoreDialogVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
// Group approved groceries by category
|
// Group approved groceries by category
|
||||||
const approvedGroceriesByCategory = approvedGroceries?.reduce(
|
let approvedGroceriesByCategory = approvedGroceries?.reduce(
|
||||||
(groups: any, item: IGrocery) => {
|
(groups: any, item: IGrocery) => {
|
||||||
const category = item.category || "Uncategorized";
|
let category = item.category || "Uncategorized";
|
||||||
|
if (item.bought) {
|
||||||
|
category = "Done";
|
||||||
|
}
|
||||||
if (!groups[category]) {
|
if (!groups[category]) {
|
||||||
groups[category] = [];
|
groups[category] = [];
|
||||||
}
|
}
|
||||||
@ -80,6 +98,12 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
|||||||
setPendingGroceries(groceries?.filter((item) => !item.approved));
|
setPendingGroceries(groceries?.filter((item) => !item.approved));
|
||||||
}, [groceries]);
|
}, [groceries]);
|
||||||
|
|
||||||
|
const handleCancelAddGrocery = () => {
|
||||||
|
setIsAddingGrocery(false);
|
||||||
|
setTitle("");
|
||||||
|
setCategory(GroceryCategory.None)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View marginH-20 marginB-45>
|
<View marginH-20 marginB-45>
|
||||||
<HeaderTemplate
|
<HeaderTemplate
|
||||||
@ -89,10 +113,8 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
|||||||
>
|
>
|
||||||
<View row centerV>
|
<View row centerV>
|
||||||
<View
|
<View
|
||||||
backgroundColor="#e2eed8"
|
|
||||||
paddingH-15
|
paddingH-15
|
||||||
paddingV-8
|
paddingV-8
|
||||||
marginR-5
|
|
||||||
centerV
|
centerV
|
||||||
style={{borderRadius: 50}}
|
style={{borderRadius: 50}}
|
||||||
>
|
>
|
||||||
@ -110,7 +132,6 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
backgroundColor="#faead2"
|
|
||||||
padding-8
|
padding-8
|
||||||
paddingH-12
|
paddingH-12
|
||||||
marginR-15
|
marginR-15
|
||||||
@ -120,9 +141,15 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
|||||||
{pendingGroceries?.length} pending
|
{pendingGroceries?.length} pending
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity>
|
<TouchableOpacity onPress={() => setChoreDialogVisible(true)}>
|
||||||
<AddPersonIcon width={24}/>
|
<Ionicons name="person-add-outline" size={24} color="grey" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<ToDosContextProvider>
|
||||||
|
{choreDialogVisible && <AddChoreDialog
|
||||||
|
isVisible={choreDialogVisible}
|
||||||
|
setIsVisible={setChoreDialogVisible}
|
||||||
|
selectedTodo={shoppingTodo} /> }
|
||||||
|
</ToDosContextProvider>
|
||||||
</View>
|
</View>
|
||||||
</HeaderTemplate>
|
</HeaderTemplate>
|
||||||
|
|
||||||
@ -220,27 +247,29 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{isAddingGrocery && (
|
<View style={{marginTop: 8}}>
|
||||||
<View style={{marginTop: 8}}>
|
<EditGroceryItem
|
||||||
<EditGroceryItem
|
editGrocery={{
|
||||||
editGrocery={{
|
title: title,
|
||||||
title: title,
|
setCategory: setCategory,
|
||||||
setCategory: setCategory,
|
category: category,
|
||||||
category: category,
|
setTitle: setTitle,
|
||||||
setTitle: setTitle,
|
setSubmit: setSubmitted,
|
||||||
setSubmit: setSubmitted,
|
closeEdit: handleCancelAddGrocery
|
||||||
closeEdit: () => setIsAddingGrocery(false)
|
}}
|
||||||
}}
|
onInputFocus={onInputFocus}
|
||||||
onInputFocus={onInputFocus}
|
/>
|
||||||
/>
|
</View>
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Render Approved Groceries Grouped by Category */}
|
{/* Render Approved Groceries Grouped by Category */}
|
||||||
{approvedGroceries?.length > 0
|
{approvedGroceries?.length > 0
|
||||||
? approvedVisible && (
|
? approvedVisible && (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={Object.keys(approvedGroceriesByCategory)}
|
data={Object.keys(approvedGroceriesByCategory).sort((a, b) => {
|
||||||
|
if (a !== "Done") return -1;
|
||||||
|
if (b === "Done") return 1;
|
||||||
|
return 0;
|
||||||
|
})}
|
||||||
renderItem={({item: category}) => (
|
renderItem={({item: category}) => (
|
||||||
<View key={category}>
|
<View key={category}>
|
||||||
{/* Render Category Header */}
|
{/* Render Category Header */}
|
||||||
|
|||||||
@ -71,7 +71,6 @@ const GroceryWrapper = () => {
|
|||||||
<GroceryList onInputFocus={handleInputFocus} />
|
<GroceryList onInputFocus={handleInputFocus} />
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
{!isAddingGrocery && <AddGroceryItem />}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -240,8 +240,8 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
|||||||
if (value) {
|
if (value) {
|
||||||
setTodo((oldValue) => ({
|
setTodo((oldValue) => ({
|
||||||
...oldValue,
|
...oldValue,
|
||||||
date: new Date(),
|
|
||||||
repeatType: value.toString(),
|
repeatType: value.toString(),
|
||||||
|
repeatDays: []
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -315,20 +315,22 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
|||||||
setSelectedAttendees={setSelectedAssignees}
|
setSelectedAttendees={setSelectedAssignees}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View row centerV style={styles.rotateSwitch}>
|
{todo.repeatType !== REPEAT_TYPE.NONE && selectedAssignees?.length > 1 &&
|
||||||
<Text style={{fontFamily: "PlusJakartaSans_500Medium", fontSize: 16}}>
|
<View row centerV style={styles.rotateSwitch}>
|
||||||
Take Turns
|
<Text style={{fontFamily: "PlusJakartaSans_500Medium", fontSize: 16}}>
|
||||||
</Text>
|
Take Turns
|
||||||
<Switch
|
</Text>
|
||||||
onColor={"#ea156c"}
|
<Switch
|
||||||
value={todo.rotate}
|
onColor={"#ea156c"}
|
||||||
style={{width: 43.06, height: 27.13}}
|
value={todo.rotate}
|
||||||
marginL-10
|
style={{width: 43.06, height: 27.13}}
|
||||||
onValueChange={(value) =>
|
marginL-10
|
||||||
setTodo((oldValue) => ({...oldValue, rotate: value}))
|
onValueChange={(value) =>
|
||||||
}
|
setTodo((oldValue) => ({...oldValue, rotate: value}))
|
||||||
/>
|
}
|
||||||
</View>
|
/>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
<View style={styles.divider}/>
|
<View style={styles.divider}/>
|
||||||
<View marginH-30 marginB-15 row centerV>
|
<View marginH-30 marginB-15 row centerV>
|
||||||
<Ionicons name="gift-outline" size={25} color="#919191"/>
|
<Ionicons name="gift-outline" size={25} color="#919191"/>
|
||||||
|
|||||||
@ -42,8 +42,8 @@ export const useUpdateSubUser = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({queryKey: ["getChildrenByParentId"]})
|
|
||||||
queryClient.invalidateQueries({queryKey: ["familyMembers"]})
|
queryClient.invalidateQueries({queryKey: ["familyMembers"]})
|
||||||
|
queryClient.invalidateQueries({queryKey: ["profiles"]})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user