mirror of
https://github.com/urosran/cally.git
synced 2025-11-26 08:24:55 +00:00
Calendar page refactor
This commit is contained in:
@ -1,14 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { CalendarProvider } from "@/contexts/CalendarContext"; // Import the new CalendarPage component
|
|
||||||
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
||||||
import {SettingsContextProvider} from "@/contexts/SettingsContext";
|
import {SettingsContextProvider} from "@/contexts/SettingsContext";
|
||||||
|
|
||||||
export default function Screen() {
|
export default function Screen() {
|
||||||
return (
|
return (
|
||||||
<SettingsContextProvider>
|
<SettingsContextProvider>
|
||||||
<CalendarProvider>
|
|
||||||
<CalendarPage/>
|
<CalendarPage/>
|
||||||
</CalendarProvider>
|
|
||||||
</SettingsContextProvider>
|
</SettingsContextProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React, {useEffect} from "react";
|
import React, {useEffect} from "react";
|
||||||
import {DefaultTheme, ThemeProvider} from "@react-navigation/native";
|
import {DefaultTheme, ThemeProvider} from "@react-navigation/native";
|
||||||
import {
|
import {
|
||||||
useFonts,
|
|
||||||
Manrope_200ExtraLight,
|
Manrope_200ExtraLight,
|
||||||
Manrope_300Light,
|
Manrope_300Light,
|
||||||
Manrope_400Regular,
|
Manrope_400Regular,
|
||||||
@ -9,21 +8,22 @@ import {
|
|||||||
Manrope_600SemiBold,
|
Manrope_600SemiBold,
|
||||||
Manrope_700Bold,
|
Manrope_700Bold,
|
||||||
Manrope_800ExtraBold,
|
Manrope_800ExtraBold,
|
||||||
|
useFonts,
|
||||||
} from "@expo-google-fonts/manrope";
|
} from "@expo-google-fonts/manrope";
|
||||||
import {
|
import {
|
||||||
PlusJakartaSans_200ExtraLight,
|
PlusJakartaSans_200ExtraLight,
|
||||||
PlusJakartaSans_300Light,
|
|
||||||
PlusJakartaSans_400Regular,
|
|
||||||
PlusJakartaSans_500Medium,
|
|
||||||
PlusJakartaSans_600SemiBold,
|
|
||||||
PlusJakartaSans_700Bold,
|
|
||||||
PlusJakartaSans_800ExtraBold,
|
|
||||||
PlusJakartaSans_200ExtraLight_Italic,
|
PlusJakartaSans_200ExtraLight_Italic,
|
||||||
|
PlusJakartaSans_300Light,
|
||||||
PlusJakartaSans_300Light_Italic,
|
PlusJakartaSans_300Light_Italic,
|
||||||
|
PlusJakartaSans_400Regular,
|
||||||
PlusJakartaSans_400Regular_Italic,
|
PlusJakartaSans_400Regular_Italic,
|
||||||
|
PlusJakartaSans_500Medium,
|
||||||
PlusJakartaSans_500Medium_Italic,
|
PlusJakartaSans_500Medium_Italic,
|
||||||
|
PlusJakartaSans_600SemiBold,
|
||||||
PlusJakartaSans_600SemiBold_Italic,
|
PlusJakartaSans_600SemiBold_Italic,
|
||||||
|
PlusJakartaSans_700Bold,
|
||||||
PlusJakartaSans_700Bold_Italic,
|
PlusJakartaSans_700Bold_Italic,
|
||||||
|
PlusJakartaSans_800ExtraBold,
|
||||||
PlusJakartaSans_800ExtraBold_Italic,
|
PlusJakartaSans_800ExtraBold_Italic,
|
||||||
} from "@expo-google-fonts/plus-jakarta-sans";
|
} from "@expo-google-fonts/plus-jakarta-sans";
|
||||||
import {
|
import {
|
||||||
@ -51,24 +51,16 @@ import * as SplashScreen from "expo-splash-screen";
|
|||||||
import "react-native-reanimated";
|
import "react-native-reanimated";
|
||||||
import {AuthContextProvider} from "@/contexts/AuthContext";
|
import {AuthContextProvider} from "@/contexts/AuthContext";
|
||||||
import {QueryClient, QueryClientProvider} from "react-query";
|
import {QueryClient, QueryClientProvider} from "react-query";
|
||||||
import {
|
import {TextProps, ThemeManager, Toast, Typography,} from "react-native-ui-lib";
|
||||||
ThemeManager,
|
|
||||||
Typography,
|
|
||||||
Toast,
|
|
||||||
TextProps,
|
|
||||||
} from "react-native-ui-lib";
|
|
||||||
import functions from "@react-native-firebase/functions";
|
|
||||||
import auth from "@react-native-firebase/auth";
|
|
||||||
import firestore from "@react-native-firebase/firestore";
|
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
functions().useEmulator("localhost", 5001);
|
// functions().useEmulator("localhost", 5001);
|
||||||
firestore().useEmulator("localhost", 5471);
|
// firestore().useEmulator("localhost", 5471);
|
||||||
auth().useEmulator("http://localhost:9099");
|
// auth().useEmulator("http://localhost:9099");
|
||||||
}
|
}
|
||||||
|
|
||||||
type TextStyleBase =
|
type TextStyleBase =
|
||||||
@ -236,7 +228,7 @@ export default function RootLayout() {
|
|||||||
|
|
||||||
ThemeManager.setComponentTheme(
|
ThemeManager.setComponentTheme(
|
||||||
"Text",
|
"Text",
|
||||||
(props: ExtendedTextProps, context: unknown) => {
|
(props: ExtendedTextProps) => {
|
||||||
const textStyle = (
|
const textStyle = (
|
||||||
Object.keys(props) as Array<keyof ExtendedTextProps>
|
Object.keys(props) as Array<keyof ExtendedTextProps>
|
||||||
).find((key) => typographies[key as TextStyle]) as
|
).find((key) => typographies[key as TextStyle]) as
|
||||||
|
|||||||
@ -1,38 +1,26 @@
|
|||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {
|
import {MaterialIcons,} from "@expo/vector-icons";
|
||||||
AntDesign,
|
import {Button, Card, Dialog, PanningProvider, Text, View,} from "react-native-ui-lib";
|
||||||
Feather,
|
|
||||||
MaterialCommunityIcons,
|
|
||||||
MaterialIcons,
|
|
||||||
} from "@expo/vector-icons";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
ButtonSize,
|
|
||||||
Card,
|
|
||||||
Dialog,
|
|
||||||
PanningProvider,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
} from "react-native-ui-lib";
|
|
||||||
import {StyleSheet, TouchableOpacity} from "react-native";
|
import {StyleSheet, TouchableOpacity} from "react-native";
|
||||||
import { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
|
|
||||||
import AddChoreDialog from "../todos/AddChoreDialog";
|
import AddChoreDialog from "../todos/AddChoreDialog";
|
||||||
import {ToDosContextProvider} from "@/contexts/ToDosContext";
|
import {ToDosContextProvider} from "@/contexts/ToDosContext";
|
||||||
import UploadImageDialog from "./UploadImageDialog";
|
import UploadImageDialog from "./UploadImageDialog";
|
||||||
import CameraIcon from "@/assets/svgs/CameraIcon";
|
import CameraIcon from "@/assets/svgs/CameraIcon";
|
||||||
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
||||||
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
|
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
|
||||||
|
import {useSetAtom} from "jotai";
|
||||||
|
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
export const AddEventDialog = () => {
|
export const AddEventDialog = () => {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const [showManualInputModal, setShowManualInputModal] = useState(false);
|
|
||||||
const [choreDialogVisible, setChoreDialogVisible] = useState<boolean>(false);
|
const [choreDialogVisible, setChoreDialogVisible] = useState<boolean>(false);
|
||||||
const [showUploadDialog, setShowUploadDialog] = useState<boolean>(false);
|
const [showUploadDialog, setShowUploadDialog] = useState<boolean>(false);
|
||||||
|
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
|
||||||
|
|
||||||
const handleOpenManualInputModal = () => {
|
const handleOpenManualInputModal = () => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setShowManualInputModal(true);
|
setSelectedNewEndDate(new Date());
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -147,10 +135,6 @@ export const AddEventDialog = () => {
|
|||||||
isVisible={choreDialogVisible}
|
isVisible={choreDialogVisible}
|
||||||
setIsVisible={setChoreDialogVisible}
|
setIsVisible={setChoreDialogVisible}
|
||||||
/>
|
/>
|
||||||
<ManuallyAddEventModal
|
|
||||||
show={showManualInputModal}
|
|
||||||
close={() => setShowManualInputModal(false)}
|
|
||||||
/>
|
|
||||||
<UploadImageDialog
|
<UploadImageDialog
|
||||||
show={showUploadDialog}
|
show={showUploadDialog}
|
||||||
setShow={setShowUploadDialog}
|
setShow={setShowUploadDialog}
|
||||||
|
|||||||
102
components/pages/calendar/CalendarHeader.tsx
Normal file
102
components/pages/calendar/CalendarHeader.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import React, {memo} from 'react';
|
||||||
|
import {Picker, PickerModes, SegmentedControl, Text, View} from "react-native-ui-lib";
|
||||||
|
import {MaterialIcons} from "@expo/vector-icons";
|
||||||
|
import {modeMap, months} from './constants';
|
||||||
|
import {StyleSheet} from "react-native";
|
||||||
|
import {useAtom} from "jotai";
|
||||||
|
import {modeAtom, selectedDateAtom} from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
|
|
||||||
|
export const CalendarHeader = memo(() => {
|
||||||
|
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
|
||||||
|
const [mode, setMode] = useAtom(modeAtom)
|
||||||
|
|
||||||
|
const handleSegmentChange = (index: number) => {
|
||||||
|
const selectedMode = modeMap.get(index);
|
||||||
|
if (selectedMode) {
|
||||||
|
setMode(selectedMode as "day" | "week" | "month");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMonthChange = (month: string) => {
|
||||||
|
const currentDay = selectedDate.getDate();
|
||||||
|
const currentYear = selectedDate.getFullYear();
|
||||||
|
|
||||||
|
const newMonthIndex = months.indexOf(month);
|
||||||
|
|
||||||
|
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
||||||
|
|
||||||
|
setSelectedDate(updatedDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 20,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
backgroundColor: "white",
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View row centerV gap-3>
|
||||||
|
<Text style={{fontFamily: "Manrope_500Medium", fontSize: 17}}>
|
||||||
|
{selectedDate.getFullYear()}
|
||||||
|
</Text>
|
||||||
|
<Picker
|
||||||
|
value={months[selectedDate.getMonth()]}
|
||||||
|
placeholder={"Select Month"}
|
||||||
|
style={{fontFamily: "Manrope_500Medium", fontSize: 17}}
|
||||||
|
mode={PickerModes.SINGLE}
|
||||||
|
onChange={(itemValue) => handleMonthChange(itemValue as string)}
|
||||||
|
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"}/>}
|
||||||
|
topBarProps={{
|
||||||
|
title: selectedDate.getFullYear().toString(),
|
||||||
|
titleStyle: {fontFamily: "Manrope_500Medium", fontSize: 17},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{months.map((month) => (
|
||||||
|
<Picker.Item key={month} label={month} value={month}/>
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<SegmentedControl
|
||||||
|
segments={[{label: "D"}, {label: "W"}, {label: "M"}]}
|
||||||
|
backgroundColor="#ececec"
|
||||||
|
inactiveColor="#919191"
|
||||||
|
activeBackgroundColor="#ea156c"
|
||||||
|
activeColor="white"
|
||||||
|
outlineColor="white"
|
||||||
|
outlineWidth={3}
|
||||||
|
segmentLabelStyle={styles.segmentslblStyle}
|
||||||
|
onChangeIndex={handleSegmentChange}
|
||||||
|
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
segmentslblStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Manrope_600SemiBold",
|
||||||
|
},
|
||||||
|
calHeader: {
|
||||||
|
borderWidth: 0,
|
||||||
|
},
|
||||||
|
dayModeHeader: {
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignContent: "center",
|
||||||
|
width: 38,
|
||||||
|
right: 42,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,100 +1,9 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React from "react";
|
||||||
import { LayoutChangeEvent, StyleSheet } from "react-native";
|
import {View,} from "react-native-ui-lib";
|
||||||
import { Calendar } from "react-native-big-calendar";
|
|
||||||
import {
|
|
||||||
Picker,
|
|
||||||
PickerModes,
|
|
||||||
SegmentedControl,
|
|
||||||
View,
|
|
||||||
} from "react-native-ui-lib";
|
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
|
||||||
import { AddEventDialog } from "@/components/pages/calendar/AddEventDialog";
|
|
||||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||||
import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch";
|
import {InnerCalendar} from "@/components/pages/calendar/InnerCalendar";
|
||||||
import { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
|
|
||||||
import { CalendarEvent } from "@/contexts/CalendarContext";
|
|
||||||
import { useSettingsContext } from "@/contexts/SettingsContext";
|
|
||||||
import EditEventDialog from "./EditEventDialog";
|
|
||||||
import { useGetEvents } from "@/hooks/firebase/useGetEvents";
|
|
||||||
import { Text } from "react-native-ui-lib";
|
|
||||||
|
|
||||||
const modeMap = new Map([
|
|
||||||
[0, "day"],
|
|
||||||
[1, "week"],
|
|
||||||
[2, "month"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const months = [
|
|
||||||
"January",
|
|
||||||
"February",
|
|
||||||
"March",
|
|
||||||
"April",
|
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function CalendarPage() {
|
export default function CalendarPage() {
|
||||||
const { calendarColor } = useSettingsContext();
|
|
||||||
const [editVisible, setEditVisible] = useState<boolean>(false);
|
|
||||||
const [eventForEdit, setEventForEdit] = useState<CalendarEvent>();
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
segmentslblStyle: {
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: "Manrope_600SemiBold",
|
|
||||||
},
|
|
||||||
calHeader: {
|
|
||||||
borderWidth: 0,
|
|
||||||
},
|
|
||||||
dayModeHeader: {
|
|
||||||
alignSelf: "flex-start",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignContent: "center",
|
|
||||||
width: 38,
|
|
||||||
right: 42,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isFamilyView, setIsFamilyView] = useState<boolean>(false);
|
|
||||||
const [calendarHeight, setCalendarHeight] = useState(0);
|
|
||||||
const [mode, setMode] = useState<"week" | "month" | "day">("week");
|
|
||||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
|
||||||
const [selectedNewEventDate, setSelectedNewEndDate] = useState<
|
|
||||||
Date | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
const calendarContainerRef = useRef(null);
|
|
||||||
const { data: events } = useGetEvents(isFamilyView);
|
|
||||||
|
|
||||||
const onLayout = (event: LayoutChangeEvent) => {
|
|
||||||
const { height } = event.nativeEvent.layout;
|
|
||||||
setCalendarHeight(height);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSegmentChange = (index: number) => {
|
|
||||||
const selectedMode = modeMap.get(index);
|
|
||||||
if (selectedMode) {
|
|
||||||
setMode(selectedMode as "day" | "week" | "month");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMonthChange = (month: string) => {
|
|
||||||
const currentDay = selectedDate.getDate();
|
|
||||||
const currentYear = selectedDate.getFullYear();
|
|
||||||
|
|
||||||
const newMonthIndex = months.indexOf(month);
|
|
||||||
|
|
||||||
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
|
||||||
|
|
||||||
setSelectedDate(updatedDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{flex: 1, height: "100%", padding: 10}}
|
style={{flex: 1, height: "100%", padding: 10}}
|
||||||
@ -103,105 +12,9 @@ export default function CalendarPage() {
|
|||||||
>
|
>
|
||||||
<HeaderTemplate
|
<HeaderTemplate
|
||||||
message={"Let's get your week started!"}
|
message={"Let's get your week started!"}
|
||||||
isWelcome={true}
|
isWelcome
|
||||||
/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{ flex: 1, backgroundColor: "#fff", borderRadius: 30 }}
|
|
||||||
ref={calendarContainerRef}
|
|
||||||
onLayout={onLayout}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingVertical: 8,
|
|
||||||
borderRadius: 20,
|
|
||||||
borderBottomLeftRadius: 0,
|
|
||||||
borderBottomRightRadius: 0,
|
|
||||||
backgroundColor: "white",
|
|
||||||
marginBottom: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View row centerV gap-3>
|
|
||||||
<Text style={{ fontFamily: "Manrope_500Medium", fontSize: 17 }}>
|
|
||||||
{selectedDate.getFullYear()}
|
|
||||||
</Text>
|
|
||||||
<Picker
|
|
||||||
value={months[selectedDate.getMonth()]} // Get the month from the date
|
|
||||||
placeholder={"Select Month"}
|
|
||||||
style={{ fontFamily: "Manrope_500Medium", fontSize: 17 }}
|
|
||||||
mode={PickerModes.SINGLE}
|
|
||||||
onChange={(itemValue) => handleMonthChange(itemValue as string)}
|
|
||||||
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"} />}
|
|
||||||
topBarProps={{
|
|
||||||
title: selectedDate.getFullYear().toString(),
|
|
||||||
titleStyle: { fontFamily: "Manrope_500Medium", fontSize: 17 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{months.map((month) => (
|
|
||||||
<Picker.Item key={month} label={month} value={month} />
|
|
||||||
))}
|
|
||||||
</Picker>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View>
|
|
||||||
<SegmentedControl
|
|
||||||
segments={[{ label: "D" }, { label: "W" }, { label: "M" }]}
|
|
||||||
backgroundColor="#ececec"
|
|
||||||
inactiveColor="#919191"
|
|
||||||
activeBackgroundColor="#ea156c"
|
|
||||||
activeColor="white"
|
|
||||||
outlineColor="white"
|
|
||||||
outlineWidth={3}
|
|
||||||
segmentLabelStyle={styles.segmentslblStyle}
|
|
||||||
onChangeIndex={handleSegmentChange}
|
|
||||||
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{calendarHeight > 0 && (
|
|
||||||
<Calendar
|
|
||||||
bodyContainerStyle={styles.calHeader}
|
|
||||||
mode={mode}
|
|
||||||
events={isFamilyView ? events ?? [] : events ?? []}
|
|
||||||
eventCellStyle={(event) => ({ backgroundColor: event.eventColor })}
|
|
||||||
onPressEvent={(event) => {
|
|
||||||
setEditVisible(true);
|
|
||||||
setEventForEdit(event);
|
|
||||||
}}
|
|
||||||
height={calendarHeight}
|
|
||||||
activeDate={selectedDate}
|
|
||||||
date={selectedDate}
|
|
||||||
onPressCell={setSelectedNewEndDate}
|
|
||||||
headerContentStyle={mode === "day" ? styles.dayModeHeader : {}}
|
|
||||||
onSwipeEnd={(date) => {
|
|
||||||
setSelectedDate(date);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<CalendarViewSwitch viewSwitch={setIsFamilyView} />
|
|
||||||
<AddEventDialog />
|
|
||||||
{eventForEdit && (
|
|
||||||
<EditEventDialog
|
|
||||||
isVisible={editVisible}
|
|
||||||
setIsVisible={() => {
|
|
||||||
setEditVisible(!editVisible);
|
|
||||||
}}
|
|
||||||
event={eventForEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ManuallyAddEventModal
|
|
||||||
key={`${selectedNewEventDate}`}
|
|
||||||
initialDate={selectedNewEventDate}
|
|
||||||
show={!!selectedNewEventDate}
|
|
||||||
close={() => setSelectedNewEndDate(undefined)}
|
|
||||||
/>
|
/>
|
||||||
|
<InnerCalendar/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { View, Text, Button, TouchableOpacity } from "react-native-ui-lib";
|
import {Text, TouchableOpacity, View} from "react-native-ui-lib";
|
||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
|
||||||
import {StyleSheet} from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
|
import {useSetAtom} from "jotai";
|
||||||
|
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
interface ICalendarViewProps {
|
|
||||||
viewSwitch: (value: boolean) => void;
|
const CalendarViewSwitch = () => {
|
||||||
}
|
|
||||||
const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
|
|
||||||
const [calView, setCalView] = useState<boolean>(false);
|
const [calView, setCalView] = useState<boolean>(false);
|
||||||
|
const viewSwitch = useSetAtom(isFamilyViewAtom)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@ -34,7 +34,7 @@ const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setCalView(true);
|
setCalView(true);
|
||||||
calendarViewProps.viewSwitch(true);
|
viewSwitch(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@ -53,7 +53,7 @@ const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setCalView(false);
|
setCalView(false);
|
||||||
calendarViewProps.viewSwitch(false);
|
viewSwitch(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
|
|||||||
@ -1,42 +1,31 @@
|
|||||||
import { View, Text, Button, Switch } from "react-native-ui-lib";
|
import {Button, ButtonSize, DateTimePicker, Dialog, Switch, Text, TextField, View} from "react-native-ui-lib";
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { Feather, AntDesign, Ionicons } from "@expo/vector-icons";
|
import {AntDesign, Feather, Ionicons} from "@expo/vector-icons";
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
TextField,
|
|
||||||
DateTimePicker,
|
|
||||||
Picker,
|
|
||||||
ButtonSize,
|
|
||||||
} from "react-native-ui-lib";
|
|
||||||
import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
|
import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
|
||||||
import {StyleSheet} from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||||
import { CalendarEvent } from "@/contexts/CalendarContext";
|
|
||||||
import ClockIcon from "@/assets/svgs/ClockIcon";
|
import ClockIcon from "@/assets/svgs/ClockIcon";
|
||||||
import LockIcon from "@/assets/svgs/LockIcon";
|
import LockIcon from "@/assets/svgs/LockIcon";
|
||||||
import MenuIcon from "@/assets/svgs/MenuIcon";
|
import MenuIcon from "@/assets/svgs/MenuIcon";
|
||||||
import {useUpdateEvent} from "@/hooks/firebase/useUpdateEvent";
|
import {useUpdateEvent} from "@/hooks/firebase/useUpdateEvent";
|
||||||
|
import {editVisibleAtom, eventForEditAtom} from "@/components/pages/calendar/atoms";
|
||||||
|
import {useAtom} from "jotai";
|
||||||
|
|
||||||
interface IEditEventDialog {
|
|
||||||
event: CalendarEvent;
|
const EditEventDialog = () => {
|
||||||
isVisible: boolean;
|
const [isVisible, setIsVisible] = useAtom(editVisibleAtom)
|
||||||
setIsVisible: (value: boolean) => void;
|
const [event, setEvent] = useAtom(eventForEditAtom)
|
||||||
}
|
|
||||||
const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|
||||||
const [event, setEvent] = useState<CalendarEvent>(editEventProps.event);
|
|
||||||
|
|
||||||
const {mutateAsync: updateEvent} = useUpdateEvent();
|
const {mutateAsync: updateEvent} = useUpdateEvent();
|
||||||
|
|
||||||
useEffect(() => {
|
if (!event) return null
|
||||||
setEvent(editEventProps.event);
|
|
||||||
}, [editEventProps.isVisible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
bottom={true}
|
bottom={true}
|
||||||
height={"90%"}
|
height={"90%"}
|
||||||
panDirection={PanningDirectionsEnum.DOWN}
|
panDirection={PanningDirectionsEnum.DOWN}
|
||||||
onDismiss={() => editEventProps.setIsVisible(false)}
|
onDismiss={() => setIsVisible(false)}
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
@ -46,7 +35,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
paddingTop: 4,
|
paddingTop: 4,
|
||||||
margin: 0,
|
margin: 0,
|
||||||
}}
|
}}
|
||||||
visible={editEventProps.isVisible}
|
visible={isVisible}
|
||||||
>
|
>
|
||||||
<View row spread>
|
<View row spread>
|
||||||
<Button
|
<Button
|
||||||
@ -54,13 +43,13 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
style={styles.topBtn}
|
style={styles.topBtn}
|
||||||
label="Cancel"
|
label="Cancel"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
editEventProps.setIsVisible(false);
|
setIsVisible(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<View marginT-12>
|
<View marginT-12>
|
||||||
<DropModalIcon
|
<DropModalIcon
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
editEventProps.setIsVisible(false);
|
setIsVisible(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@ -71,7 +60,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
try {
|
try {
|
||||||
if (event.id) {
|
if (event.id) {
|
||||||
updateEvent(event).then(() => editEventProps.setIsVisible(false));
|
updateEvent(event).then(() => setIsVisible(false));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -85,7 +74,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
value={event.title}
|
value={event.title}
|
||||||
onChangeText={(text) => {
|
onChangeText={(text) => {
|
||||||
setEvent((prevEvent) => ({
|
setEvent((prevEvent) => ({
|
||||||
...prevEvent,
|
...prevEvent!,
|
||||||
title: text,
|
title: text,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
@ -110,7 +99,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
marginL-10
|
marginL-10
|
||||||
value={event.allDay}
|
value={event.allDay}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
setEvent((prev) => ({ ...prev, allDay: value }))
|
setEvent((prev) => ({...prev!, allDay: value}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@ -125,7 +114,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
marginL-8
|
marginL-8
|
||||||
maximumDate={event.end}
|
maximumDate={event.end}
|
||||||
onChange={(date) => {
|
onChange={(date) => {
|
||||||
setEvent((prev) => ({ ...prev, start: date }));
|
setEvent((prev) => ({...prev!, start: date}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@ -133,11 +122,12 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
text70
|
text70
|
||||||
value={event.start}
|
value={event.start}
|
||||||
onChange={(date) => {
|
onChange={(date) => {
|
||||||
setEvent((prev) => ({ ...prev, start: date }));
|
setEvent((prev) => ({...prev!, start: date}));
|
||||||
}}
|
}}
|
||||||
maximumDate={event.end}
|
maximumDate={event.end}
|
||||||
dateTimeFormatter={(date, mode) => date.toLocaleTimeString("en-us",
|
dateTimeFormatter={(date) => date.toLocaleTimeString("en-us",
|
||||||
{ hour: "numeric",
|
{
|
||||||
|
hour: "numeric",
|
||||||
minute: "numeric"
|
minute: "numeric"
|
||||||
})}
|
})}
|
||||||
mode="time"
|
mode="time"
|
||||||
@ -155,7 +145,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
text70
|
text70
|
||||||
marginL-8
|
marginL-8
|
||||||
onChange={(date) => {
|
onChange={(date) => {
|
||||||
setEvent((prev) => ({ ...prev, end: date }));
|
setEvent((prev) => ({...prev!, end: date}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@ -164,10 +154,11 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
value={event.end}
|
value={event.end}
|
||||||
minimumDate={event.start}
|
minimumDate={event.start}
|
||||||
onChange={(date) => {
|
onChange={(date) => {
|
||||||
setEvent((prev) => ({ ...prev, end: date }));
|
setEvent((prev) => ({...prev!, end: date}));
|
||||||
}}
|
}}
|
||||||
dateTimeFormatter={(date, mode) => date.toLocaleTimeString("en-us",
|
dateTimeFormatter={(date) => date.toLocaleTimeString("en-us",
|
||||||
{ hour: "numeric",
|
{
|
||||||
|
hour: "numeric",
|
||||||
minute: "numeric"
|
minute: "numeric"
|
||||||
})}
|
})}
|
||||||
mode="time"
|
mode="time"
|
||||||
@ -262,7 +253,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
|
|||||||
marginL-10
|
marginL-10
|
||||||
value={event.private}
|
value={event.private}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
setEvent((prev) => ({ ...prev, private: value }))
|
setEvent((prev) => ({...prev!, private: value}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
63
components/pages/calendar/EventCalendar.tsx
Normal file
63
components/pages/calendar/EventCalendar.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React, {memo} from 'react';
|
||||||
|
import {Calendar} from "react-native-big-calendar";
|
||||||
|
import {StyleSheet} from "react-native";
|
||||||
|
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
|
||||||
|
import {useAtom, useAtomValue, useSetAtom} from "jotai";
|
||||||
|
import {
|
||||||
|
editVisibleAtom,
|
||||||
|
eventForEditAtom,
|
||||||
|
modeAtom,
|
||||||
|
selectedDateAtom,
|
||||||
|
selectedNewEventDateAtom
|
||||||
|
} from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
|
interface EventCalendarProps {
|
||||||
|
calendarHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EventCalendar: React.FC<EventCalendarProps> = memo(({calendarHeight}) => {
|
||||||
|
const {data: events} = useGetEvents();
|
||||||
|
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
|
||||||
|
const mode = useAtomValue(modeAtom)
|
||||||
|
const setEditVisible = useSetAtom(editVisibleAtom)
|
||||||
|
const setEventForEdit = useSetAtom(eventForEditAtom)
|
||||||
|
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Calendar
|
||||||
|
bodyContainerStyle={styles.calHeader}
|
||||||
|
mode={mode}
|
||||||
|
events={events ?? []}
|
||||||
|
eventCellStyle={(event) => ({backgroundColor: event.eventColor})}
|
||||||
|
onPressEvent={(event) => {
|
||||||
|
setEditVisible(true);
|
||||||
|
setEventForEdit(event);
|
||||||
|
}}
|
||||||
|
height={calendarHeight}
|
||||||
|
activeDate={selectedDate}
|
||||||
|
date={selectedDate}
|
||||||
|
onPressCell={setSelectedNewEndDate}
|
||||||
|
headerContentStyle={mode === "day" ? styles.dayModeHeader : {}}
|
||||||
|
onSwipeEnd={(date) => {
|
||||||
|
setSelectedDate(date);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
segmentslblStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Manrope_600SemiBold",
|
||||||
|
},
|
||||||
|
calHeader: {
|
||||||
|
borderWidth: 0,
|
||||||
|
},
|
||||||
|
dayModeHeader: {
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignContent: "center",
|
||||||
|
width: 38,
|
||||||
|
right: 42,
|
||||||
|
},
|
||||||
|
});
|
||||||
41
components/pages/calendar/InnerCalendar.tsx
Normal file
41
components/pages/calendar/InnerCalendar.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {View} from "react-native-ui-lib";
|
||||||
|
import React, {useRef, useState} from "react";
|
||||||
|
import {LayoutChangeEvent} from "react-native";
|
||||||
|
import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch";
|
||||||
|
import {AddEventDialog} from "@/components/pages/calendar/AddEventDialog";
|
||||||
|
import EditEventDialog from "@/components/pages/calendar/EditEventDialog";
|
||||||
|
import {ManuallyAddEventModal} from "@/components/pages/calendar/ManuallyAddEventModal";
|
||||||
|
import {CalendarHeader} from "@/components/pages/calendar/CalendarHeader";
|
||||||
|
import {EventCalendar} from "@/components/pages/calendar/EventCalendar";
|
||||||
|
|
||||||
|
export const InnerCalendar = () => {
|
||||||
|
const [calendarHeight, setCalendarHeight] = useState(0);
|
||||||
|
const calendarContainerRef = useRef(null);
|
||||||
|
|
||||||
|
const onLayout = (event: LayoutChangeEvent) => {
|
||||||
|
const {height} = event.nativeEvent.layout;
|
||||||
|
setCalendarHeight(height);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View
|
||||||
|
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30}}
|
||||||
|
ref={calendarContainerRef}
|
||||||
|
onLayout={onLayout}
|
||||||
|
>
|
||||||
|
<CalendarHeader/>
|
||||||
|
{calendarHeight > 0 && (
|
||||||
|
<EventCalendar
|
||||||
|
calendarHeight={calendarHeight}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<CalendarViewSwitch/>
|
||||||
|
|
||||||
|
<AddEventDialog/>
|
||||||
|
<EditEventDialog/>
|
||||||
|
<ManuallyAddEventModal/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,12 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
Avatar,
|
|
||||||
Button,
|
Button,
|
||||||
ButtonSize,
|
ButtonSize,
|
||||||
Colors,
|
Colors,
|
||||||
DateTimePicker,
|
DateTimePicker,
|
||||||
LoaderScreen,
|
LoaderScreen,
|
||||||
Modal,
|
Modal,
|
||||||
Picker,
|
|
||||||
Switch,
|
Switch,
|
||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
@ -16,26 +14,20 @@ import {
|
|||||||
import {ScrollView} from "react-native-gesture-handler";
|
import {ScrollView} from "react-native-gesture-handler";
|
||||||
import {useSafeAreaInsets} from "react-native-safe-area-context";
|
import {useSafeAreaInsets} from "react-native-safe-area-context";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {
|
import {AntDesign, Feather, Ionicons,} from "@expo/vector-icons";
|
||||||
AntDesign,
|
|
||||||
Feather,
|
|
||||||
Ionicons,
|
|
||||||
MaterialIcons,
|
|
||||||
} from "@expo/vector-icons";
|
|
||||||
import {PickerMultiValue} from "react-native-ui-lib/src/components/picker/types";
|
import {PickerMultiValue} from "react-native-ui-lib/src/components/picker/types";
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
|
||||||
import {useCreateEvent} from "@/hooks/firebase/useCreateEvent";
|
import {useCreateEvent} from "@/hooks/firebase/useCreateEvent";
|
||||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||||
import { addHours, setDate } from "date-fns";
|
import {addHours} from "date-fns";
|
||||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||||
import { CalendarEvent, useCalendarContext } from "@/contexts/CalendarContext";
|
import {StyleSheet} from "react-native";
|
||||||
import { repeatOptions } from "@/contexts/ToDosContext";
|
|
||||||
import { ImageBackground, StyleSheet } from "react-native";
|
|
||||||
import ClockIcon from "@/assets/svgs/ClockIcon";
|
import ClockIcon from "@/assets/svgs/ClockIcon";
|
||||||
import LockIcon from "@/assets/svgs/LockIcon";
|
import LockIcon from "@/assets/svgs/LockIcon";
|
||||||
import MenuIcon from "@/assets/svgs/MenuIcon";
|
import MenuIcon from "@/assets/svgs/MenuIcon";
|
||||||
import CameraIcon from "@/assets/svgs/CameraIcon";
|
import CameraIcon from "@/assets/svgs/CameraIcon";
|
||||||
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
|
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
|
||||||
|
import {useAtom} from "jotai";
|
||||||
|
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
const daysOfWeek = [
|
const daysOfWeek = [
|
||||||
{label: "Monday", value: "monday"},
|
{label: "Monday", value: "monday"},
|
||||||
@ -47,19 +39,17 @@ const daysOfWeek = [
|
|||||||
{label: "Sunday", value: "sunday"},
|
{label: "Sunday", value: "sunday"},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ManuallyAddEventModal = ({
|
export const ManuallyAddEventModal = () => {
|
||||||
show,
|
|
||||||
close,
|
|
||||||
initialDate,
|
|
||||||
}: {
|
|
||||||
show: boolean;
|
|
||||||
close: () => void;
|
|
||||||
initialDate?: Date;
|
|
||||||
}) => {
|
|
||||||
const { addEvent } = useCalendarContext();
|
|
||||||
const { user } = useAuthContext();
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
|
const [selectedNewEventDate, setSelectedNewEndDate] = useAtom(selectedNewEventDateAtom)
|
||||||
|
|
||||||
|
const {show, close, initialDate} = {
|
||||||
|
show: !!selectedNewEventDate,
|
||||||
|
close: () => setSelectedNewEndDate(undefined),
|
||||||
|
initialDate: selectedNewEventDate
|
||||||
|
}
|
||||||
|
|
||||||
const [title, setTitle] = useState<string>("");
|
const [title, setTitle] = useState<string>("");
|
||||||
|
|
||||||
const [isAllDay, setIsAllDay] = useState(false);
|
const [isAllDay, setIsAllDay] = useState(false);
|
||||||
@ -84,6 +74,8 @@ export const ManuallyAddEventModal = ({
|
|||||||
|
|
||||||
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent();
|
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent();
|
||||||
|
|
||||||
|
if (!selectedNewEventDate) return null;
|
||||||
|
|
||||||
const formatDateTime = (date?: Date | string) => {
|
const formatDateTime = (date?: Date | string) => {
|
||||||
if (!date) return undefined;
|
if (!date) return undefined;
|
||||||
return new Date(date).toLocaleDateString("en-US", {
|
return new Date(date).toLocaleDateString("en-US", {
|
||||||
|
|||||||
9
components/pages/calendar/atoms.ts
Normal file
9
components/pages/calendar/atoms.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { atom } from 'jotai';
|
||||||
|
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
|
||||||
|
|
||||||
|
export const editVisibleAtom = atom<boolean>(false);
|
||||||
|
export const eventForEditAtom = atom<CalendarEvent | undefined>(undefined);
|
||||||
|
export const isFamilyViewAtom = atom<boolean>(false);
|
||||||
|
export const modeAtom = atom<"week" | "month" | "day">("week");
|
||||||
|
export const selectedDateAtom = atom<Date>(new Date());
|
||||||
|
export const selectedNewEventDateAtom = atom<Date | undefined>(undefined);
|
||||||
20
components/pages/calendar/constants.ts
Normal file
20
components/pages/calendar/constants.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const modeMap = new Map([
|
||||||
|
[0, "day"],
|
||||||
|
[1, "week"],
|
||||||
|
[2, "month"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const months = [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
];
|
||||||
13
components/pages/calendar/interfaces.ts
Normal file
13
components/pages/calendar/interfaces.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export interface CalendarEvent {
|
||||||
|
id?: number | string; // Unique identifier for the event
|
||||||
|
user?: string;
|
||||||
|
title: string; // Event title or name
|
||||||
|
description?: string; // Optional description for the event
|
||||||
|
start: Date; // Start date and time of the event
|
||||||
|
end: Date; // End date and time of the event
|
||||||
|
location?: string; // Optional event location
|
||||||
|
allDay?: boolean; // Specifies if the event lasts all day
|
||||||
|
eventColor?: string; // Optional color to represent the event
|
||||||
|
participants?: string[]; // Optional list of participants or attendees
|
||||||
|
private?: boolean;
|
||||||
|
}
|
||||||
@ -1,17 +1,17 @@
|
|||||||
import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib";
|
import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {useSignIn} from "@/hooks/firebase/useSignIn";
|
import {useSignIn} from "@/hooks/firebase/useSignIn";
|
||||||
import {StyleSheet} from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
import Toast from 'react-native-toast-message';
|
import Toast from 'react-native-toast-message';
|
||||||
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
|
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
|
||||||
import {Camera, CameraView} from 'expo-camera';
|
import {Camera, CameraView} from 'expo-camera';
|
||||||
import {BarCodeScanner} from "expo-barcode-scanner";
|
|
||||||
|
|
||||||
const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => {
|
const SignInPage = ({setTab}: {
|
||||||
|
setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">>
|
||||||
|
}) => {
|
||||||
const [email, setEmail] = useState<string>("");
|
const [email, setEmail] = useState<string>("");
|
||||||
const [password, setPassword] = useState<string>("");
|
const [password, setPassword] = useState<string>("");
|
||||||
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
||||||
const [scanned, setScanned] = useState<boolean>(false);
|
|
||||||
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
|
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
|
||||||
|
|
||||||
const {mutateAsync: signIn, error, isError} = useSignIn();
|
const {mutateAsync: signIn, error, isError} = useSignIn();
|
||||||
@ -88,7 +88,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
|
|||||||
style={{marginBottom: 20}}
|
style={{marginBottom: 20}}
|
||||||
backgroundColor="#fd1775"
|
backgroundColor="#fd1775"
|
||||||
/>
|
/>
|
||||||
{isError && <Text center style={{marginBottom: 20}}>{`${error}`}</Text>}
|
{isError && <Text center style={{marginBottom: 20}}>{`${error?.toString()?.split("]")?.[1]}`}</Text>}
|
||||||
|
|
||||||
<View row centerH marginB-5 gap-5>
|
<View row centerH marginB-5 gap-5>
|
||||||
<Text text70>
|
<Text text70>
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { View, Text, Button } from "react-native-ui-lib";
|
import {Button, Text, View} from "react-native-ui-lib";
|
||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {StyleSheet} from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
import { Entypo, Ionicons, Octicons } from "@expo/vector-icons";
|
import {Octicons} from "@expo/vector-icons";
|
||||||
import CalendarSettingsPage from "./CalendarSettingsPage";
|
import CalendarSettingsPage from "./CalendarSettingsPage";
|
||||||
import ChoreRewardSettings from "./ChoreRewardSettings";
|
import ChoreRewardSettings from "./ChoreRewardSettings";
|
||||||
import UserSettings from "./UserSettings";
|
import UserSettings from "./UserSettings";
|
||||||
import { AuthContextProvider } from "@/contexts/AuthContext";
|
|
||||||
import ProfileIcon from "@/assets/svgs/ProfileIcon";
|
import ProfileIcon from "@/assets/svgs/ProfileIcon";
|
||||||
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
||||||
import PrivacyPolicyIcon from "@/assets/svgs/PrivacyPolicyIcon";
|
import PrivacyPolicyIcon from "@/assets/svgs/PrivacyPolicyIcon";
|
||||||
@ -18,6 +17,7 @@ const pageIndex = {
|
|||||||
chore: 3,
|
chore: 3,
|
||||||
policy: 4,
|
policy: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
const [selectedPage, setSelectedPage] = useState<number>(0);
|
const [selectedPage, setSelectedPage] = useState<number>(0);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -99,6 +99,7 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
|
|||||||
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
|
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
|
||||||
setUser(authUser);
|
setUser(authUser);
|
||||||
|
|
||||||
|
|
||||||
if (authUser) {
|
if (authUser) {
|
||||||
await refreshProfileData(authUser);
|
await refreshProfileData(authUser);
|
||||||
const pushToken = await registerForPushNotificationsAsync();
|
const pushToken = await registerForPushNotificationsAsync();
|
||||||
|
|||||||
@ -1,194 +0,0 @@
|
|||||||
// CalendarContext.tsx
|
|
||||||
import React, { createContext, useContext, useState, ReactNode } from "react";
|
|
||||||
|
|
||||||
// Define the CalendarEvent interface
|
|
||||||
export interface CalendarEvent {
|
|
||||||
id?: number | string; // Unique identifier for the event
|
|
||||||
user?: string;
|
|
||||||
title: string; // Event title or name
|
|
||||||
description?: string; // Optional description for the event
|
|
||||||
start: Date; // Start date and time of the event
|
|
||||||
end: Date; // End date and time of the event
|
|
||||||
location?: string; // Optional event location
|
|
||||||
allDay?: boolean; // Specifies if the event lasts all day
|
|
||||||
color?: string; // Optional color to represent the event
|
|
||||||
participants?: string[]; // Optional list of participants or attendees
|
|
||||||
private?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the context type
|
|
||||||
interface CalendarContextType {
|
|
||||||
events: CalendarEvent[];
|
|
||||||
familyEvents: CalendarEvent[];
|
|
||||||
addEvent: (event: CalendarEvent) => void; // Function to add an event
|
|
||||||
removeEvent: (id: number) => void; // Function to remove an event by ID
|
|
||||||
updateEvent: (changes: Partial<CalendarEvent>, id?: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the CalendarContext
|
|
||||||
const CalendarContext = createContext<CalendarContextType | undefined>(
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create a provider component
|
|
||||||
export const CalendarProvider: React.FC<{ children: ReactNode }> = ({
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const [events, setEvents] = useState<CalendarEvent[]>([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "Team Meeting",
|
|
||||||
description: "Discuss project milestones and deadlines.",
|
|
||||||
start: new Date("2024-09-15T10:00:00"),
|
|
||||||
end: new Date("2024-09-15T11:00:00"),
|
|
||||||
location: "Office Conference Room",
|
|
||||||
allDay: false,
|
|
||||||
color: "#FF5733",
|
|
||||||
participants: ["Alice", "Bob", "Charlie"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "Doctor's Appointment",
|
|
||||||
description: "Annual check-up with Dr. Smith.",
|
|
||||||
start: new Date("2024-09-20T14:30:00"),
|
|
||||||
end: new Date("2024-09-20T15:30:00"),
|
|
||||||
location: "Health Clinic",
|
|
||||||
allDay: false,
|
|
||||||
color: "#33FF57",
|
|
||||||
participants: ["You"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Birthday Party",
|
|
||||||
description: "Celebrating Sarah's 30th birthday.",
|
|
||||||
start: new Date("2024-09-25T18:00:00"),
|
|
||||||
end: new Date("2024-09-25T21:00:00"),
|
|
||||||
location: "Sarah's House",
|
|
||||||
allDay: false,
|
|
||||||
color: "#3357FF",
|
|
||||||
participants: ["You", "Sarah", "Tom", "Lily"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Project Deadline",
|
|
||||||
description: "Final submission for the project.",
|
|
||||||
start: new Date("2024-10-01T00:00:00"),
|
|
||||||
end: new Date("2024-10-01T23:59:00"),
|
|
||||||
location: "Online",
|
|
||||||
allDay: false,
|
|
||||||
color: "#FF33A1",
|
|
||||||
participants: ["You"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: "Halloween Costume Party",
|
|
||||||
description: "Join us for a spooky night of fun!",
|
|
||||||
start: new Date("2024-10-31T19:00:00"),
|
|
||||||
end: new Date("2024-10-31T23:00:00"),
|
|
||||||
location: "Downtown Club",
|
|
||||||
allDay: false,
|
|
||||||
color: "#FFB733",
|
|
||||||
participants: ["You", "Friends"],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [familyEvents, setFamilyEvents] = useState<CalendarEvent[]>([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
user: "jakesId",
|
|
||||||
title: "Team Meeting",
|
|
||||||
description: "Discuss project milestones and deadlines.",
|
|
||||||
start: new Date("2024-09-10T10:00:00"),
|
|
||||||
end: new Date("2024-09-10T11:00:00"),
|
|
||||||
location: "Office Conference Room",
|
|
||||||
allDay: false,
|
|
||||||
color: "#FF5733",
|
|
||||||
participants: ["Alice", "Bob", "Charlie"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
user: "mikesId",
|
|
||||||
title: "Doctor's Appointment",
|
|
||||||
description: "Annual check-up with Dr. Smith.",
|
|
||||||
start: new Date("2024-09-21T14:30:00"),
|
|
||||||
end: new Date("2024-09-21T15:30:00"),
|
|
||||||
location: "Health Clinic",
|
|
||||||
allDay: false,
|
|
||||||
color: "#33FF57",
|
|
||||||
participants: ["You"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
user: "jakesId",
|
|
||||||
title: "Birthday Party",
|
|
||||||
description: "Celebrating Sarah's 30th birthday.",
|
|
||||||
start: new Date("2024-09-5T18:00:00"),
|
|
||||||
end: new Date("2024-09-5T21:00:00"),
|
|
||||||
location: "Sarah's House",
|
|
||||||
allDay: false,
|
|
||||||
color: "#3357FF",
|
|
||||||
participants: ["You", "Sarah", "Tom", "Lily"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
user: "davidsId",
|
|
||||||
title: "Project Deadline",
|
|
||||||
description: "Final submission for the project.",
|
|
||||||
start: new Date("2024-10-03T00:00:00"),
|
|
||||||
end: new Date("2024-10-03T23:59:00"),
|
|
||||||
location: "Online",
|
|
||||||
allDay: false,
|
|
||||||
color: "#FF33A1",
|
|
||||||
participants: ["You"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
user: "jakesId",
|
|
||||||
title: "Halloween Costume Party",
|
|
||||||
description: "Join us for a spooky night of fun!",
|
|
||||||
start: new Date("2024-10-02T19:00:00"),
|
|
||||||
end: new Date("2024-10-02T23:00:00"),
|
|
||||||
location: "Downtown Club",
|
|
||||||
allDay: false,
|
|
||||||
color: "#FFB733",
|
|
||||||
participants: ["You", "Friends"],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Function to add an event
|
|
||||||
const addEvent = (event: CalendarEvent) => {
|
|
||||||
event.id = events.length + 1;
|
|
||||||
setEvents((prevEvents) => [...prevEvents, event]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to remove an event by ID
|
|
||||||
const removeEvent = (id: number) => {
|
|
||||||
setEvents((prevEvents) => prevEvents.filter((event) => event.id !== id));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to update an event
|
|
||||||
const updateEvent = ( changes: Partial<CalendarEvent>, id?: number) => {
|
|
||||||
setEvents((prevEvents) =>
|
|
||||||
prevEvents.map((event) =>
|
|
||||||
event.id === id ? { ...event, ...changes } : event
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CalendarContext.Provider
|
|
||||||
value={{ events, addEvent, removeEvent, updateEvent, familyEvents }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</CalendarContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Custom hook to use the CalendarContext
|
|
||||||
export const useCalendarContext = () => {
|
|
||||||
const context = useContext(CalendarContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error("useCalendar must be used within a CalendarProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
@ -10,5 +10,5 @@ export interface EventData {
|
|||||||
surpriseEvent?: boolean,
|
surpriseEvent?: boolean,
|
||||||
notes?: string,
|
notes?: string,
|
||||||
reminders?: string[]
|
reminders?: string[]
|
||||||
id?: string,
|
id?: string | number,
|
||||||
}
|
}
|
||||||
@ -2,9 +2,12 @@ import {useQuery} from "react-query";
|
|||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
import {colorMap} from "@/contexts/SettingsContext";
|
import {colorMap} from "@/contexts/SettingsContext";
|
||||||
|
import {useAtomValue} from "jotai";
|
||||||
|
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
|
||||||
|
|
||||||
export const useGetEvents = (isFamilyView: boolean) => {
|
export const useGetEvents = () => {
|
||||||
const { user, profileData } = useAuthContext();
|
const { user, profileData } = useAuthContext();
|
||||||
|
const isFamilyView = useAtomValue(isFamilyViewAtom)
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["events", user?.uid, isFamilyView],
|
queryKey: ["events", user?.uid, isFamilyView],
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {useAuthContext} from "@/contexts/AuthContext";
|
|
||||||
import {useMutation, useQueryClient} from "react-query";
|
import {useMutation, useQueryClient} from "react-query";
|
||||||
import firestore from "@react-native-firebase/firestore";
|
import firestore from "@react-native-firebase/firestore";
|
||||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||||
|
|
||||||
export const useUpdateEvent = () => {
|
export const useUpdateEvent = () => {
|
||||||
const {user: currentUser} = useAuthContext()
|
|
||||||
const queryClients = useQueryClient()
|
const queryClients = useQueryClient()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -13,7 +11,7 @@ export const useUpdateEvent = () => {
|
|||||||
try {
|
try {
|
||||||
await firestore()
|
await firestore()
|
||||||
.collection("Events")
|
.collection("Events")
|
||||||
.doc(eventData.id)
|
.doc(`${eventData.id}`)
|
||||||
.update(eventData);
|
.update(eventData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|||||||
@ -441,7 +441,7 @@
|
|||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
||||||
PRODUCT_NAME = "CallyFamilyPlanner";
|
PRODUCT_NAME = CallyFamilyPlanner;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -472,7 +472,7 @@
|
|||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
|
||||||
PRODUCT_NAME = "CallyFamilyPlanner";
|
PRODUCT_NAME = CallyFamilyPlanner;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
|||||||
Reference in New Issue
Block a user