mirror of
https://github.com/urosran/cally.git
synced 2025-11-26 16:34:54 +00:00
Merge branch 'dev'
# Conflicts: # ios/cally/Info.plist
This commit is contained in:
@ -1,123 +1,119 @@
|
|||||||
import { Dimensions, ScrollView } from "react-native";
|
import {Dimensions, ScrollView, StyleSheet} from "react-native";
|
||||||
import React, { useState } from "react";
|
import React, {useState} from "react";
|
||||||
import { View, Text, Button } from "react-native-ui-lib";
|
import {Button, Text, TextField, View} from "react-native-ui-lib";
|
||||||
import DumpList from "./DumpList";
|
import DumpList from "./DumpList";
|
||||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||||
import { TextField } from "react-native-ui-lib";
|
import {Feather, MaterialIcons} from "@expo/vector-icons";
|
||||||
import { StyleSheet } from "react-native";
|
|
||||||
import { Feather, MaterialIcons } from "@expo/vector-icons";
|
|
||||||
import { TextInput } from "react-native-gesture-handler";
|
|
||||||
import AddBrainDump from "./AddBrainDump";
|
import AddBrainDump from "./AddBrainDump";
|
||||||
import LinearGradient from "react-native-linear-gradient";
|
import LinearGradient from "react-native-linear-gradient";
|
||||||
|
|
||||||
const BrainDumpPage = () => {
|
const BrainDumpPage = () => {
|
||||||
const [searchText, setSearchText] = useState<string>("");
|
const [searchText, setSearchText] = useState<string>("");
|
||||||
const [isAddVisible, setIsAddVisible] = useState<boolean>(false);
|
const [isAddVisible, setIsAddVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View height={"100%"}>
|
<View height={"100%"}>
|
||||||
<View>
|
|
||||||
<ScrollView
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
>
|
|
||||||
<View marginH-25>
|
|
||||||
<HeaderTemplate
|
|
||||||
message={"Welcome to your notes!"}
|
|
||||||
isWelcome={false}
|
|
||||||
children={
|
|
||||||
<Text
|
|
||||||
style={{ fontFamily: "Manrope_400Regular", fontSize: 14 }}
|
|
||||||
>
|
|
||||||
Drop your notes on-the-go here, and{"\n"}organize them later.
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<View>
|
<View>
|
||||||
<View style={styles.searchField} centerV>
|
<ScrollView
|
||||||
<TextField
|
showsVerticalScrollIndicator={false}
|
||||||
value={searchText}
|
showsHorizontalScrollIndicator={false}
|
||||||
onChangeText={(value) => {
|
>
|
||||||
setSearchText(value);
|
<View marginH-25>
|
||||||
}}
|
<HeaderTemplate
|
||||||
leadingAccessory={
|
message={"Welcome to your notes!"}
|
||||||
<Feather
|
isWelcome={false}
|
||||||
name="search"
|
children={
|
||||||
size={24}
|
<Text
|
||||||
color="#9b9b9b"
|
style={{fontFamily: "Manrope_400Regular", fontSize: 14}}
|
||||||
style={{ paddingRight: 10 }}
|
>
|
||||||
/>
|
Drop your notes on-the-go here, and{"\n"}organize them later.
|
||||||
}
|
</Text>
|
||||||
style={{
|
}
|
||||||
fontFamily: "Manrope_500Medium",
|
/>
|
||||||
fontSize: 15,
|
<View>
|
||||||
}}
|
<View style={styles.searchField} centerV>
|
||||||
placeholder="Search notes..."
|
<TextField
|
||||||
/>
|
value={searchText}
|
||||||
</View>
|
onChangeText={(value) => {
|
||||||
<DumpList searchText={searchText} />
|
setSearchText(value);
|
||||||
|
}}
|
||||||
|
leadingAccessory={
|
||||||
|
<Feather
|
||||||
|
name="search"
|
||||||
|
size={24}
|
||||||
|
color="#9b9b9b"
|
||||||
|
style={{paddingRight: 10}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
fontFamily: "Manrope_500Medium",
|
||||||
|
fontSize: 15,
|
||||||
|
}}
|
||||||
|
placeholder="Search notes..."
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<DumpList searchText={searchText}/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
<LinearGradient
|
||||||
</ScrollView>
|
colors={["#f9f8f700", "#f9f8f7"]}
|
||||||
</View>
|
locations={[0,1]}
|
||||||
<LinearGradient
|
style={{
|
||||||
colors={["#f2f2f2", "transparent"]}
|
position: "absolute",
|
||||||
start={{ x: 0.5, y: 1 }}
|
bottom: 0,
|
||||||
end={{ x: 0.5, y: 0 }}
|
height: 120,
|
||||||
style={{
|
width: Dimensions.get("screen").width,
|
||||||
position: "absolute",
|
justifyContent:'center',
|
||||||
bottom: 0,
|
alignItems:"center"
|
||||||
height: 90,
|
}}
|
||||||
width: Dimensions.get("screen").width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
style={{
|
|
||||||
height: 40,
|
|
||||||
position: "relative",
|
|
||||||
marginLeft: "auto",
|
|
||||||
width: 20,
|
|
||||||
right: 20,
|
|
||||||
bottom: -10,
|
|
||||||
borderRadius: 30,
|
|
||||||
backgroundColor: "#fd1775",
|
|
||||||
}}
|
|
||||||
color="white"
|
|
||||||
enableShadow
|
|
||||||
onPress={() => {
|
|
||||||
setIsAddVisible(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View row centerV centerH>
|
|
||||||
<MaterialIcons name="add" size={22} color={"white"} />
|
|
||||||
<Text
|
|
||||||
white
|
|
||||||
style={{ fontSize: 16, fontFamily: "Manrope_600SemiBold" }}
|
|
||||||
>
|
>
|
||||||
New
|
<Button
|
||||||
</Text>
|
style={{
|
||||||
</View>
|
height: 40,
|
||||||
</Button>
|
position: "relative",
|
||||||
</LinearGradient>
|
width: "90%",
|
||||||
<AddBrainDump
|
bottom: -10,
|
||||||
addBrainDumpProps={{
|
borderRadius: 30,
|
||||||
isVisible: isAddVisible,
|
backgroundColor: "#fd1775",
|
||||||
setIsVisible: setIsAddVisible,
|
}}
|
||||||
}}
|
color="white"
|
||||||
/>
|
enableShadow
|
||||||
</View>
|
onPress={() => {
|
||||||
);
|
setIsAddVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View row centerV centerH>
|
||||||
|
<MaterialIcons name="add" size={22} color={"white"}/>
|
||||||
|
<Text
|
||||||
|
white
|
||||||
|
style={{fontSize: 16, fontFamily: "Manrope_600SemiBold"}}
|
||||||
|
>
|
||||||
|
New
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</Button>
|
||||||
|
</LinearGradient>
|
||||||
|
<AddBrainDump
|
||||||
|
addBrainDumpProps={{
|
||||||
|
isVisible: isAddVisible,
|
||||||
|
setIsVisible: setIsAddVisible,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
searchField: {
|
searchField: {
|
||||||
borderWidth: 0.7,
|
borderWidth: 0.7,
|
||||||
borderColor: "#9b9b9b",
|
borderColor: "#9b9b9b",
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
height: 42,
|
height: 42,
|
||||||
paddingLeft: 10,
|
paddingLeft: 10,
|
||||||
marginVertical: 20,
|
marginVertical: 20,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default BrainDumpPage;
|
export default BrainDumpPage;
|
||||||
|
|||||||
@ -1,134 +1,125 @@
|
|||||||
import React, { memo } from "react";
|
import React, {memo} from "react";
|
||||||
import {
|
import {Button, Picker, PickerModes, SegmentedControl, Text, View,} from "react-native-ui-lib";
|
||||||
Button,
|
import {MaterialIcons} from "@expo/vector-icons";
|
||||||
Picker,
|
import {modeMap, months} from "./constants";
|
||||||
PickerModes,
|
import {StyleSheet} from "react-native";
|
||||||
SegmentedControl,
|
import {useAtom} from "jotai";
|
||||||
Text,
|
import {modeAtom, selectedDateAtom} from "@/components/pages/calendar/atoms";
|
||||||
View,
|
import {isSameDay} from "date-fns";
|
||||||
} from "react-native-ui-lib";
|
import {useAuthContext} from "@/contexts/AuthContext";
|
||||||
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";
|
|
||||||
import { isSameDay } from "date-fns";
|
|
||||||
import { useAuthContext } from "@/contexts/AuthContext";
|
|
||||||
|
|
||||||
export const CalendarHeader = memo(() => {
|
export const CalendarHeader = memo(() => {
|
||||||
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom);
|
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom);
|
||||||
const [mode, setMode] = useAtom(modeAtom);
|
const [mode, setMode] = useAtom(modeAtom);
|
||||||
const { profileData } = useAuthContext();
|
const {profileData} = useAuthContext();
|
||||||
|
|
||||||
const handleSegmentChange = (index: number) => {
|
const handleSegmentChange = (index: number) => {
|
||||||
const selectedMode = modeMap.get(index);
|
const selectedMode = modeMap.get(index);
|
||||||
if (selectedMode) {
|
if (selectedMode) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setMode(selectedMode as "day" | "week" | "month");
|
setMode(selectedMode as "day" | "week" | "month");
|
||||||
}, 150);
|
}, 150);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMonthChange = (month: string) => {
|
const handleMonthChange = (month: string) => {
|
||||||
const currentDay = selectedDate.getDate();
|
const currentDay = selectedDate.getDate();
|
||||||
const currentYear = selectedDate.getFullYear();
|
const currentYear = selectedDate.getFullYear();
|
||||||
const newMonthIndex = months.indexOf(month);
|
const newMonthIndex = months.indexOf(month);
|
||||||
|
|
||||||
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
||||||
setSelectedDate(updatedDate);
|
setSelectedDate(updatedDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSelectedDateToday = isSameDay(selectedDate, new Date());
|
const isSelectedDateToday = isSameDay(selectedDate, new Date());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<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, width: 85 }}
|
|
||||||
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 row centerV>
|
|
||||||
{!isSelectedDateToday && (
|
|
||||||
<Button
|
|
||||||
size={"xSmall"}
|
|
||||||
marginR-0
|
|
||||||
avoidInnerPadding
|
|
||||||
padding-7
|
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 5,
|
flexDirection: "row",
|
||||||
backgroundColor: "white",
|
justifyContent: "space-between",
|
||||||
borderWidth: 0.7,
|
alignItems: "center",
|
||||||
borderColor: "#dadce0",
|
paddingHorizontal: 10,
|
||||||
height: 30,
|
paddingVertical: 8,
|
||||||
|
borderRadius: 20,
|
||||||
|
borderBottomLeftRadius: 0,
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
backgroundColor: "white",
|
||||||
|
marginBottom: 10,
|
||||||
}}
|
}}
|
||||||
labelStyle={{
|
>
|
||||||
fontSize: 12,
|
<View row centerV gap-3>
|
||||||
color: "black",
|
<Text style={{fontFamily: "Manrope_500Medium", fontSize: 17}}>
|
||||||
fontFamily: "Manrope_500Medium",
|
{selectedDate.getFullYear()}
|
||||||
}}
|
</Text>
|
||||||
label={new Date().toLocaleDateString("en-US", {
|
<Picker
|
||||||
timeZone: profileData?.timeZone || "",
|
value={months[selectedDate.getMonth()]}
|
||||||
})}
|
placeholder={"Select Month"}
|
||||||
onPress={() => {
|
style={{fontFamily: "Manrope_500Medium", fontSize: 17, width: 85}}
|
||||||
setSelectedDate(new Date());
|
mode={PickerModes.SINGLE}
|
||||||
setMode("day");
|
onChange={(itemValue) => handleMonthChange(itemValue as string)}
|
||||||
console.log(profileData?.timeZone)
|
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>
|
<View row centerV>
|
||||||
<SegmentedControl
|
{!isSelectedDateToday && (
|
||||||
segments={[{ label: "D" }, { label: "W" }, { label: "M" }]}
|
<Button
|
||||||
backgroundColor="#ececec"
|
size={"xSmall"}
|
||||||
inactiveColor="#919191"
|
marginR-0
|
||||||
activeBackgroundColor="#ea156c"
|
avoidInnerPadding
|
||||||
activeColor="white"
|
style={{
|
||||||
outlineColor="white"
|
borderRadius: 50,
|
||||||
outlineWidth={3}
|
backgroundColor: "white",
|
||||||
segmentLabelStyle={styles.segmentslblStyle}
|
borderWidth: 0.7,
|
||||||
onChangeIndex={handleSegmentChange}
|
borderColor: "#dadce0",
|
||||||
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
|
height: 30,
|
||||||
/>
|
paddingHorizontal: 10
|
||||||
|
}}
|
||||||
|
labelStyle={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: "black",
|
||||||
|
fontFamily: "Manrope_500Medium",
|
||||||
|
}}
|
||||||
|
label={new Date().toLocaleDateString("en-US", {
|
||||||
|
timeZone: profileData?.timeZone || "",
|
||||||
|
})}
|
||||||
|
onPress={() => {
|
||||||
|
setSelectedDate(new Date());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<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>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
);
|
||||||
</View>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
segmentslblStyle: {
|
segmentslblStyle: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontFamily: "Manrope_600SemiBold",
|
fontFamily: "Manrope_600SemiBold",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,163 +1,255 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||||
import { Calendar } from "react-native-big-calendar";
|
import {Calendar} from "react-native-big-calendar";
|
||||||
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
import {ActivityIndicator, StyleSheet, View, ViewStyle} from "react-native";
|
||||||
import { useGetEvents } from "@/hooks/firebase/useGetEvents";
|
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
|
||||||
import { useAtom, useSetAtom } from "jotai";
|
import {useAtom, useSetAtom} from "jotai";
|
||||||
import {
|
import {
|
||||||
editVisibleAtom,
|
editVisibleAtom,
|
||||||
eventForEditAtom,
|
eventForEditAtom,
|
||||||
modeAtom,
|
modeAtom,
|
||||||
selectedDateAtom,
|
selectedDateAtom,
|
||||||
selectedNewEventDateAtom,
|
selectedNewEventDateAtom,
|
||||||
} 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";
|
||||||
|
import {Text} from "react-native-ui-lib";
|
||||||
|
|
||||||
interface EventCalendarProps {
|
interface EventCalendarProps {
|
||||||
calendarHeight: number;
|
calendarHeight: number;
|
||||||
// WAS USED FOR SCROLLABLE CALENDARS, PERFORMANCE WAS NOT OPTIMAL
|
// WAS USED FOR SCROLLABLE CALENDARS, PERFORMANCE WAS NOT OPTIMAL
|
||||||
calendarWidth: number;
|
calendarWidth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTotalMinutes = () => {
|
const getTotalMinutes = () => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return Math.abs(date.getUTCHours() * 60 + date.getUTCMinutes() - 200);
|
return Math.abs(date.getUTCHours() * 60 + date.getUTCMinutes() - 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
|
export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
|
||||||
({ calendarHeight }) => {
|
({calendarHeight}) => {
|
||||||
const { data: events, isLoading } = useGetEvents();
|
const {data: events, isLoading} = useGetEvents();
|
||||||
const { profileData } = useAuthContext();
|
const {profileData} = useAuthContext();
|
||||||
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom);
|
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom);
|
||||||
const [mode, setMode] = useAtom(modeAtom);
|
const [mode, setMode] = useAtom(modeAtom);
|
||||||
|
|
||||||
const setEditVisible = useSetAtom(editVisibleAtom);
|
const setEditVisible = useSetAtom(editVisibleAtom);
|
||||||
const setEventForEdit = useSetAtom(eventForEditAtom);
|
const setEventForEdit = useSetAtom(eventForEditAtom);
|
||||||
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom);
|
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom);
|
||||||
|
|
||||||
const [isRendering, setIsRendering] = useState(true);
|
const [isRendering, setIsRendering] = useState(true);
|
||||||
const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes());
|
const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes());
|
||||||
|
|
||||||
useEffect(() => {
|
const todaysDate = new Date()
|
||||||
if (events && mode) {
|
|
||||||
setIsRendering(true);
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
setIsRendering(false);
|
|
||||||
}, 300);
|
|
||||||
return () => clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
}, [events, mode]);
|
|
||||||
|
|
||||||
const handlePressEvent = useCallback(
|
useEffect(() => {
|
||||||
(event: CalendarEvent) => {
|
if (events && mode) {
|
||||||
if (mode === "day" || mode === "week") {
|
setIsRendering(true);
|
||||||
setEditVisible(true);
|
const timeout = setTimeout(() => {
|
||||||
console.log({ event });
|
setIsRendering(false);
|
||||||
setEventForEdit(event);
|
}, 300);
|
||||||
} else {
|
return () => clearTimeout(timeout);
|
||||||
setMode("day");
|
}
|
||||||
setSelectedDate(event.start);
|
}, [events, mode]);
|
||||||
|
|
||||||
|
const handlePressEvent = useCallback(
|
||||||
|
(event: CalendarEvent) => {
|
||||||
|
if (mode === "day" || mode === "week") {
|
||||||
|
setEditVisible(true);
|
||||||
|
console.log({event});
|
||||||
|
setEventForEdit(event);
|
||||||
|
} else {
|
||||||
|
setMode("day");
|
||||||
|
setSelectedDate(event.start);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setEditVisible, setEventForEdit, mode]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePressCell = useCallback(
|
||||||
|
(date: Date) => {
|
||||||
|
if (mode === "day" || mode === "week") {
|
||||||
|
setSelectedNewEndDate(date);
|
||||||
|
} else {
|
||||||
|
setMode("day");
|
||||||
|
setSelectedDate(date);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[mode, setSelectedNewEndDate, setSelectedDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSwipeEnd = useCallback(
|
||||||
|
(date: Date) => {
|
||||||
|
setSelectedDate(date);
|
||||||
|
},
|
||||||
|
[setSelectedDate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const memoizedEventCellStyle = useCallback(
|
||||||
|
(event: CalendarEvent) => ({backgroundColor: event.eventColor}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const memoizedWeekStartsOn = useMemo(
|
||||||
|
() => (profileData?.firstDayOfWeek === "Mondays" ? 1 : 0),
|
||||||
|
[profileData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSameDate = useCallback((date1: Date, date2: Date) => {
|
||||||
|
return (
|
||||||
|
date1.getDate() === date2.getDate() &&
|
||||||
|
date1.getMonth() === date2.getMonth() &&
|
||||||
|
date1.getFullYear() === date2.getFullYear()
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dayHeaderColor = useMemo(() => {
|
||||||
|
return isSameDate(todaysDate, selectedDate) ? "white" : "#4d4d4d";
|
||||||
|
}, [selectedDate, mode]);
|
||||||
|
|
||||||
|
const dateStyle = useMemo(() => {
|
||||||
|
if (mode === "week") return undefined
|
||||||
|
return isSameDate(todaysDate, selectedDate) && mode === "day"
|
||||||
|
? styles.dayHeader
|
||||||
|
: styles.otherDayHeader;
|
||||||
|
}, [selectedDate, mode]);
|
||||||
|
|
||||||
|
const memoizedHeaderContentStyle = useMemo(() => {
|
||||||
|
if (mode === "day") {
|
||||||
|
return styles.dayModeHeader;
|
||||||
|
} else if (mode === "week") {
|
||||||
|
return styles.weekModeHeader;
|
||||||
|
} else if (mode === "month") {
|
||||||
|
return styles.monthModeHeader;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
|
const memoizedEvents = useMemo(() => events ?? [], [events]);
|
||||||
|
|
||||||
|
const renderCustomDateForMonth = (date: Date) => {
|
||||||
|
const circleStyle = useMemo<ViewStyle>(
|
||||||
|
() => ({
|
||||||
|
position: "absolute",
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: 15,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultStyle = useMemo<ViewStyle>(
|
||||||
|
() => ({
|
||||||
|
...circleStyle,
|
||||||
|
}),
|
||||||
|
[circleStyle]
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentDateStyle = useMemo<ViewStyle>(
|
||||||
|
() => ({
|
||||||
|
...circleStyle,
|
||||||
|
backgroundColor: "#4184f2",
|
||||||
|
}),
|
||||||
|
[circleStyle]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderDate = useCallback(
|
||||||
|
(date: Date) => {
|
||||||
|
const isCurrentDate = isSameDate(todaysDate, date);
|
||||||
|
const appliedStyle = isCurrentDate ? currentDateStyle : defaultStyle;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{alignItems: "center"}}>
|
||||||
|
<View style={appliedStyle}>
|
||||||
|
<Text style={{color: isCurrentDate ? "white" : "black"}}>
|
||||||
|
{date.getDate()}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[todaysDate, currentDateStyle, defaultStyle] // dependencies
|
||||||
|
);
|
||||||
|
|
||||||
|
return renderDate(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOffsetMinutes(getTotalMinutes());
|
||||||
|
}, [events, mode]);
|
||||||
|
|
||||||
|
if (isLoading || isRendering) {
|
||||||
|
return (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="large" color="#0000ff"/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
[setEditVisible, setEventForEdit, mode]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePressCell = useCallback(
|
return (
|
||||||
(date: Date) => {
|
<Calendar
|
||||||
if (mode === "day" || mode === "week") {
|
bodyContainerStyle={styles.calHeader}
|
||||||
setSelectedNewEndDate(date);
|
swipeEnabled
|
||||||
} else {
|
enableEnrichedEvents
|
||||||
setMode("day");
|
mode={mode}
|
||||||
setSelectedDate(date);
|
events={memoizedEvents}
|
||||||
}
|
eventCellStyle={memoizedEventCellStyle}
|
||||||
},
|
onPressEvent={handlePressEvent}
|
||||||
[mode, setSelectedNewEndDate, setSelectedDate]
|
weekStartsOn={memoizedWeekStartsOn}
|
||||||
);
|
height={calendarHeight}
|
||||||
|
activeDate={todaysDate}
|
||||||
const handleSwipeEnd = useCallback(
|
date={selectedDate}
|
||||||
(date: Date) => {
|
onPressCell={handlePressCell}
|
||||||
setSelectedDate(date);
|
headerContentStyle={memoizedHeaderContentStyle}
|
||||||
},
|
onSwipeEnd={handleSwipeEnd}
|
||||||
[setSelectedDate]
|
scrollOffsetMinutes={offsetMinutes}
|
||||||
);
|
dayHeaderStyle={dateStyle}
|
||||||
|
dayHeaderHighlightColor={dayHeaderColor}
|
||||||
const memoizedEventCellStyle = useCallback(
|
renderCustomDateForMonth={renderCustomDateForMonth}
|
||||||
(event: CalendarEvent) => ({ backgroundColor: event.eventColor }),
|
showAdjacentMonths
|
||||||
[]
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const memoizedWeekStartsOn = useMemo(
|
|
||||||
() => (profileData?.firstDayOfWeek === "Mondays" ? 1 : 0),
|
|
||||||
[profileData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const memoizedHeaderContentStyle = useMemo(
|
|
||||||
() => (mode === "day" ? styles.dayModeHeader : {}),
|
|
||||||
[mode]
|
|
||||||
);
|
|
||||||
|
|
||||||
const memoizedEvents = useMemo(() => events ?? [], [events]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOffsetMinutes(getTotalMinutes());
|
|
||||||
}, [events, mode]);
|
|
||||||
|
|
||||||
if (isLoading || isRendering) {
|
|
||||||
return (
|
|
||||||
<View style={styles.loadingContainer}>
|
|
||||||
<ActivityIndicator size="large" color="#0000ff" />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<Calendar
|
|
||||||
bodyContainerStyle={styles.calHeader}
|
|
||||||
swipeEnabled
|
|
||||||
enableEnrichedEvents
|
|
||||||
mode={mode}
|
|
||||||
events={memoizedEvents}
|
|
||||||
eventCellStyle={memoizedEventCellStyle}
|
|
||||||
onPressEvent={handlePressEvent}
|
|
||||||
weekStartsOn={memoizedWeekStartsOn}
|
|
||||||
height={calendarHeight}
|
|
||||||
activeDate={selectedDate}
|
|
||||||
date={selectedDate}
|
|
||||||
onPressCell={handlePressCell}
|
|
||||||
headerContentStyle={memoizedHeaderContentStyle}
|
|
||||||
onSwipeEnd={handleSwipeEnd}
|
|
||||||
scrollOffsetMinutes={offsetMinutes}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
segmentslblStyle: {
|
segmentslblStyle: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontFamily: "Manrope_600SemiBold",
|
fontFamily: "Manrope_600SemiBold",
|
||||||
},
|
},
|
||||||
calHeader: {
|
calHeader: {
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
},
|
},
|
||||||
dayModeHeader: {
|
dayModeHeader: {
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignContent: "center",
|
alignContent: "center",
|
||||||
width: 38,
|
width: 38,
|
||||||
right: 42,
|
right: 42,
|
||||||
height: 13,
|
height: 13,
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
weekModeHeader: {},
|
||||||
flex: 1,
|
monthModeHeader: {},
|
||||||
justifyContent: "center",
|
loadingContainer: {
|
||||||
alignItems: "center",
|
flex: 1,
|
||||||
},
|
justifyContent: "center",
|
||||||
dayHeader: {
|
alignItems: "center",
|
||||||
backgroundColor: "#4184f2",
|
},
|
||||||
aspectRatio: 1,
|
dayHeader: {
|
||||||
borderRadius: 100,
|
backgroundColor: "#4184f2",
|
||||||
alignItems: "center",
|
aspectRatio: 1,
|
||||||
justifyContent: "center",
|
borderRadius: 100,
|
||||||
},
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
otherDayHeader: {
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
color: "#919191",
|
||||||
|
aspectRatio: 1,
|
||||||
|
borderRadius: 100,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,96 +1,78 @@
|
|||||||
import { StyleSheet } from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import {
|
import {Button, View,} from "react-native-ui-lib";
|
||||||
Button,
|
import {useGroceryContext} from "@/contexts/GroceryContext";
|
||||||
Colors,
|
import {FontAwesome6} from "@expo/vector-icons";
|
||||||
Dialog,
|
|
||||||
Drawer,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
PanningProvider,
|
|
||||||
} from "react-native-ui-lib";
|
|
||||||
import { useGroceryContext } from "@/contexts/GroceryContext";
|
|
||||||
import { FontAwesome6 } from "@expo/vector-icons";
|
|
||||||
interface AddGroceryItemProps {
|
|
||||||
visible: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
const AddGroceryItem = () => {
|
|
||||||
const { isAddingGrocery, setIsAddingGrocery } = useGroceryContext();
|
|
||||||
const [visible, setVisible] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const handleShowDialog = () => {
|
const AddGroceryItem = () => {
|
||||||
setVisible(true);
|
const {setIsAddingGrocery} = useGroceryContext();
|
||||||
};
|
|
||||||
const handleHideDialog = () => {
|
return (
|
||||||
setVisible(false);
|
<View
|
||||||
};
|
row
|
||||||
return (
|
spread
|
||||||
<View
|
paddingH-25
|
||||||
row
|
style={{
|
||||||
spread
|
position: "absolute",
|
||||||
paddingH-25
|
bottom: 20,
|
||||||
style={{
|
width: "100%",
|
||||||
position: "absolute",
|
height: 60,
|
||||||
bottom: 20,
|
}}
|
||||||
width: "100%",
|
>
|
||||||
height: 60,
|
<View style={styles.btnContainer} row>
|
||||||
}}
|
<Button
|
||||||
>
|
color="white"
|
||||||
<View style={styles.btnContainer} row>
|
backgroundColor="#fd1775"
|
||||||
<Button
|
label="Add item"
|
||||||
color="white"
|
text70L
|
||||||
backgroundColor="#fd1775"
|
iconSource={() => <FontAwesome6 name="add" size={18} color="white"/>}
|
||||||
label="Add item"
|
style={styles.finishShopBtn}
|
||||||
text70L
|
labelStyle={styles.addBtnLbl}
|
||||||
iconSource={() => <FontAwesome6 name="add" size={18} color="white" />}
|
enableShadow
|
||||||
style={styles.finishShopBtn}
|
onPress={() => {
|
||||||
labelStyle={styles.addBtnLbl}
|
setIsAddingGrocery(true);
|
||||||
enableShadow
|
}}
|
||||||
onPress={() => {
|
/>
|
||||||
setIsAddingGrocery(true);
|
</View>
|
||||||
}}
|
</View>
|
||||||
/>
|
);
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddGroceryItem;
|
export default AddGroceryItem;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
inner: {
|
inner: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: "400",
|
fontWeight: "400",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
divider: {
|
divider: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: 1,
|
height: 1,
|
||||||
backgroundColor: "#E0E0E0",
|
backgroundColor: "#E0E0E0",
|
||||||
marginVertical: 10,
|
marginVertical: 10,
|
||||||
},
|
},
|
||||||
btnContainer: {
|
btnContainer: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
finishShopBtn: {
|
finishShopBtn: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
shoppingBtn: {
|
shoppingBtn: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
marginHorizontal: 3,
|
marginHorizontal: 3,
|
||||||
},
|
},
|
||||||
addBtnLbl: { fontFamily: "Manrope_500Medium", fontSize: 17, marginLeft: 5 },
|
addBtnLbl: {fontFamily: "Manrope_500Medium", fontSize: 17, marginLeft: 5},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,145 +1,167 @@
|
|||||||
import { Text, View } from "react-native-ui-lib";
|
import {Text, TextField, TextFieldRef, View} from "react-native-ui-lib";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, {useEffect, useRef} from "react";
|
||||||
import { TextField, TextFieldRef } from "react-native-ui-lib";
|
import {GroceryCategory, useGroceryContext} from "@/contexts/GroceryContext";
|
||||||
import { GroceryCategory, useGroceryContext } from "@/contexts/GroceryContext";
|
import {Dropdown} from "react-native-element-dropdown";
|
||||||
import { Dropdown } from "react-native-element-dropdown";
|
|
||||||
import CloseXIcon from "@/assets/svgs/CloseXIcon";
|
import CloseXIcon from "@/assets/svgs/CloseXIcon";
|
||||||
import { StyleSheet } from "react-native";
|
import {StyleSheet} from "react-native";
|
||||||
import DropdownIcon from "@/assets/svgs/DropdownIcon";
|
import DropdownIcon from "@/assets/svgs/DropdownIcon";
|
||||||
|
import {AntDesign} from "@expo/vector-icons";
|
||||||
|
|
||||||
interface IEditGrocery {
|
interface IEditGrocery {
|
||||||
id?: string;
|
id?: string;
|
||||||
title: string;
|
title: string;
|
||||||
category: GroceryCategory;
|
category: GroceryCategory;
|
||||||
setTitle: (value: string) => void;
|
setTitle: (value: string) => void;
|
||||||
setCategory?: (category: GroceryCategory) => void;
|
setCategory?: (category: GroceryCategory) => void;
|
||||||
setSubmit?: (value: boolean) => void;
|
setSubmit?: (value: boolean) => void;
|
||||||
closeEdit?: (value: boolean) => void;
|
closeEdit?: () => void;
|
||||||
handleEditSubmit?: Function;
|
handleEditSubmit?: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
|
|
||||||
const { fuzzyMatchGroceryCategory } = useGroceryContext();
|
|
||||||
const inputRef = useRef<TextFieldRef>(null);
|
|
||||||
|
|
||||||
const groceryCategoryOptions = Object.values(GroceryCategory).map(
|
const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
|
||||||
(category) => ({
|
const {fuzzyMatchGroceryCategory} = useGroceryContext();
|
||||||
label: category,
|
const inputRef = useRef<TextFieldRef>(null);
|
||||||
value: category,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const groceryCategoryOptions = Object.values(GroceryCategory).map(
|
||||||
if (inputRef.current) {
|
(category) => ({
|
||||||
inputRef.current.focus(); // Focus on the TextField
|
label: category,
|
||||||
}
|
value: category,
|
||||||
console.log(editGrocery.category);
|
})
|
||||||
}, []);
|
);
|
||||||
|
|
||||||
return (
|
const handleSubmit = () => {
|
||||||
<View
|
inputRef?.current?.blur()
|
||||||
style={{
|
console.log("CALLLLLL")
|
||||||
backgroundColor: "white",
|
if (editGrocery.setSubmit) {
|
||||||
width: "100%",
|
editGrocery.setSubmit(true);
|
||||||
borderRadius: 25,
|
}
|
||||||
paddingHorizontal: 13,
|
if (editGrocery.handleEditSubmit) {
|
||||||
paddingVertical: 10,
|
editGrocery.handleEditSubmit({
|
||||||
marginTop: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View row spread centerV>
|
|
||||||
<TextField
|
|
||||||
text70T
|
|
||||||
style={{}}
|
|
||||||
ref={inputRef}
|
|
||||||
placeholder="Grocery"
|
|
||||||
value={editGrocery.title}
|
|
||||||
onChangeText={(value) => {
|
|
||||||
editGrocery.setTitle(value);
|
|
||||||
}}
|
|
||||||
onSubmitEditing={() => {
|
|
||||||
if (editGrocery.setSubmit) {
|
|
||||||
editGrocery.setSubmit(true);
|
|
||||||
}
|
|
||||||
if (editGrocery.handleEditSubmit) {
|
|
||||||
editGrocery.handleEditSubmit({
|
|
||||||
id: editGrocery.id,
|
id: editGrocery.id,
|
||||||
title: editGrocery.title,
|
title: editGrocery.title,
|
||||||
category: editGrocery.category,
|
category: editGrocery.category,
|
||||||
});
|
|
||||||
}
|
|
||||||
if (editGrocery.closeEdit) {
|
|
||||||
editGrocery.closeEdit(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
maxLength={25}
|
|
||||||
/>
|
|
||||||
<CloseXIcon
|
|
||||||
onPress={() => {
|
|
||||||
if (editGrocery.closeEdit) editGrocery.closeEdit(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Dropdown
|
|
||||||
style={{marginTop: 15}}
|
|
||||||
data={groceryCategoryOptions}
|
|
||||||
placeholder="Select grocery category"
|
|
||||||
placeholderStyle={{ color: "#a2a2a2", fontFamily: "Manrope_500Medium", fontSize: 13.2 }}
|
|
||||||
labelField="label"
|
|
||||||
valueField="value"
|
|
||||||
value={
|
|
||||||
editGrocery.category == GroceryCategory.None
|
|
||||||
? null
|
|
||||||
: editGrocery.category
|
|
||||||
}
|
|
||||||
iconColor="white"
|
|
||||||
activeColor={"#fd1775"}
|
|
||||||
containerStyle={styles.dropdownStyle}
|
|
||||||
itemTextStyle={styles.itemText}
|
|
||||||
itemContainerStyle={styles.itemStyle}
|
|
||||||
selectedTextStyle={styles.selectedText}
|
|
||||||
renderLeftIcon={() => (
|
|
||||||
<DropdownIcon style={{ marginRight: 8 }} color={editGrocery.category == GroceryCategory.None ? "#7b7b7b" : "#fd1775"} />
|
|
||||||
)}
|
|
||||||
renderItem={(item) => {
|
|
||||||
return (
|
|
||||||
<View height={36.02} centerV>
|
|
||||||
<Text style={styles.itemText}>{item.label}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onChange={(item) => {
|
|
||||||
if (editGrocery.handleEditSubmit) {
|
|
||||||
editGrocery.handleEditSubmit({
|
|
||||||
id: editGrocery.id,
|
|
||||||
category: item.value,
|
|
||||||
});
|
});
|
||||||
console.log("kategorija vo diropdown: " + item.value);
|
}
|
||||||
if (editGrocery.closeEdit) editGrocery.closeEdit(false);
|
if (editGrocery.closeEdit) {
|
||||||
} else {
|
editGrocery.closeEdit();
|
||||||
if (editGrocery.setCategory) {
|
}
|
||||||
editGrocery.setCategory(item.value);
|
}
|
||||||
}
|
|
||||||
}
|
useEffect(() => {
|
||||||
}}
|
if (inputRef.current) {
|
||||||
/>
|
inputRef.current.focus();
|
||||||
</View>
|
}
|
||||||
);
|
|
||||||
|
console.log(editGrocery.category);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
backgroundColor: "white",
|
||||||
|
width: "100%",
|
||||||
|
borderRadius: 25,
|
||||||
|
paddingHorizontal: 13,
|
||||||
|
paddingVertical: 10,
|
||||||
|
marginTop: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View row spread centerV>
|
||||||
|
<TextField
|
||||||
|
text70T
|
||||||
|
ref={inputRef}
|
||||||
|
placeholder="Grocery"
|
||||||
|
value={editGrocery.title}
|
||||||
|
onSubmitEditing={handleSubmit}
|
||||||
|
numberOfLines={1}
|
||||||
|
returnKeyType="done"
|
||||||
|
onChangeText={(value) => {
|
||||||
|
editGrocery.setTitle(value);
|
||||||
|
let groceryCategory = fuzzyMatchGroceryCategory(value);
|
||||||
|
if (editGrocery.setCategory) {
|
||||||
|
editGrocery.setCategory(groceryCategory);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
maxLength={25}
|
||||||
|
/>
|
||||||
|
<View row centerV>
|
||||||
|
<AntDesign
|
||||||
|
name="check"
|
||||||
|
size={24}
|
||||||
|
style={{
|
||||||
|
color: "green",
|
||||||
|
marginRight: 15,
|
||||||
|
}}
|
||||||
|
onPress={handleSubmit}
|
||||||
|
/>
|
||||||
|
<CloseXIcon
|
||||||
|
onPress={() => {
|
||||||
|
if (editGrocery.closeEdit) {
|
||||||
|
editGrocery.closeEdit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Dropdown
|
||||||
|
style={{marginTop: 15}}
|
||||||
|
data={groceryCategoryOptions}
|
||||||
|
placeholder="Select grocery category"
|
||||||
|
placeholderStyle={{color: "#a2a2a2", fontFamily: "Manrope_500Medium", fontSize: 13.2}}
|
||||||
|
labelField="label"
|
||||||
|
valueField="value"
|
||||||
|
value={
|
||||||
|
editGrocery.category
|
||||||
|
}
|
||||||
|
iconColor="white"
|
||||||
|
activeColor={"#fd1775"}
|
||||||
|
containerStyle={styles.dropdownStyle}
|
||||||
|
itemTextStyle={styles.itemText}
|
||||||
|
itemContainerStyle={styles.itemStyle}
|
||||||
|
selectedTextStyle={styles.selectedText}
|
||||||
|
renderLeftIcon={() => (
|
||||||
|
<DropdownIcon style={{marginRight: 8}}
|
||||||
|
color={editGrocery.category == GroceryCategory.None ? "#7b7b7b" : "#fd1775"}/>
|
||||||
|
)}
|
||||||
|
renderItem={(item) => {
|
||||||
|
return (
|
||||||
|
<View height={36.02} centerV>
|
||||||
|
<Text style={styles.itemText}>{item.label}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onChange={(item) => {
|
||||||
|
if (editGrocery.handleEditSubmit) {
|
||||||
|
editGrocery.handleEditSubmit({
|
||||||
|
id: editGrocery.id,
|
||||||
|
category: item.value,
|
||||||
|
});
|
||||||
|
if (editGrocery.closeEdit) editGrocery.closeEdit();
|
||||||
|
} else {
|
||||||
|
if (editGrocery.setCategory) {
|
||||||
|
editGrocery.setCategory(item.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
itemText: {
|
itemText: {
|
||||||
fontFamily: "Manrope_400Regular",
|
fontFamily: "Manrope_400Regular",
|
||||||
fontSize: 15.42,
|
fontSize: 15.42,
|
||||||
paddingLeft: 15,
|
paddingLeft: 15,
|
||||||
},
|
},
|
||||||
selectedText: {
|
selectedText: {
|
||||||
fontFamily: "Manrope_500Medium",
|
fontFamily: "Manrope_500Medium",
|
||||||
fontSize: 13.2,
|
fontSize: 13.2,
|
||||||
color: "#fd1775",
|
color: "#fd1775",
|
||||||
},
|
},
|
||||||
dropdownStyle: { borderRadius: 6.61, height: 115.34, width: 187 },
|
dropdownStyle: {borderRadius: 6.61, height: 115.34, width: 187},
|
||||||
itemStyle: { padding: 0, margin: 0 },
|
itemStyle: {padding: 0, margin: 0},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default EditGroceryItem;
|
export default EditGroceryItem;
|
||||||
|
|||||||
@ -23,12 +23,14 @@ const GroceryItem = ({
|
|||||||
|
|
||||||
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
|
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
|
||||||
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
|
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
|
||||||
const [newTitle, setNewTitle] = useState<string>("");
|
const [newTitle, setNewTitle] = useState<string>(item.title ?? "");
|
||||||
const [category, setCategory] = useState<GroceryCategory>(
|
const [category, setCategory] = useState<GroceryCategory>(item.category ?? GroceryCategory.None);
|
||||||
GroceryCategory.None
|
|
||||||
);
|
|
||||||
const [itemCreator, setItemCreator] = useState<UserProfile>(null);
|
const [itemCreator, setItemCreator] = useState<UserProfile>(null);
|
||||||
|
|
||||||
|
const closeEdit = () => {
|
||||||
|
setIsEditingTitle(false);
|
||||||
|
}
|
||||||
|
|
||||||
const handleTitleChange = (newTitle: string) => {
|
const handleTitleChange = (newTitle: string) => {
|
||||||
updateGroceryItem({ id: item?.id, title: newTitle });
|
updateGroceryItem({ id: item?.id, title: newTitle });
|
||||||
};
|
};
|
||||||
@ -38,7 +40,6 @@ const GroceryItem = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNewTitle(item.title);
|
|
||||||
console.log(item);
|
console.log(item);
|
||||||
getItemCreator(item?.creatorId);
|
getItemCreator(item?.creatorId);
|
||||||
}, []);
|
}, []);
|
||||||
@ -81,52 +82,51 @@ const GroceryItem = ({
|
|||||||
setOpenFreqEdit(false);
|
setOpenFreqEdit(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!isEditingTitle ? (
|
{isEditingTitle ?
|
||||||
<View>
|
<EditGroceryItem
|
||||||
{ isParent ? <TouchableOpacity onPress={() => setIsEditingTitle(true)}>
|
editGrocery={{
|
||||||
<Text text70T black style={styles.title}>
|
id: item.id,
|
||||||
{item.title}
|
title: newTitle,
|
||||||
</Text>
|
category: category,
|
||||||
</TouchableOpacity> :
|
setTitle: setNewTitle,
|
||||||
|
setCategory: setCategory,
|
||||||
|
closeEdit: closeEdit,
|
||||||
|
handleEditSubmit: updateGroceryItem,
|
||||||
|
}}
|
||||||
|
/> :
|
||||||
|
<View>
|
||||||
|
{isParent ?
|
||||||
|
<TouchableOpacity onPress={() => setIsEditingTitle(true)}>
|
||||||
|
<Text text70T black style={styles.title}>
|
||||||
|
{item.title}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity> :
|
||||||
<Text text70T black style={styles.title}>
|
<Text text70T black style={styles.title}>
|
||||||
{item.title}
|
{item.title}
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
) : (
|
}
|
||||||
<EditGroceryItem
|
|
||||||
editGrocery={{
|
|
||||||
id: item.id,
|
|
||||||
title: newTitle,
|
|
||||||
category: item.category,
|
|
||||||
setTitle: setNewTitle,
|
|
||||||
setCategory: setCategory,
|
|
||||||
closeEdit: setIsEditingTitle,
|
|
||||||
handleEditSubmit: updateGroceryItem,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!item.approved ? (
|
{!item.approved ? (
|
||||||
<View row centerV marginB-10>
|
<View row centerV marginB-10>
|
||||||
{isParent && <><AntDesign
|
{isParent &&
|
||||||
name="check"
|
<>
|
||||||
size={24}
|
<AntDesign
|
||||||
style={{
|
name="check"
|
||||||
color: "green",
|
size={24}
|
||||||
marginRight: 15,
|
style={{
|
||||||
}}
|
color: "green",
|
||||||
onPress={() => {
|
marginRight: 15,
|
||||||
isParent ? handleItemApproved(item.id, { approved: true }) : null
|
}}
|
||||||
}}
|
onPress={isParent ? () => handleItemApproved(item.id, { approved: true }) : null}
|
||||||
/>
|
/>
|
||||||
<AntDesign
|
<AntDesign
|
||||||
name="close"
|
name="close"
|
||||||
size={24}
|
size={24}
|
||||||
style={{ color: "red" }}
|
style={{ color: "red" }}
|
||||||
onPress={() => {
|
onPress={isParent ? () => handleItemApproved(item.id, { approved: false }) : null}
|
||||||
isParent ? handleItemApproved(item.id, { approved: false }) : null
|
/>
|
||||||
}}
|
</>}
|
||||||
/> </>}
|
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
!isEditingTitle && (
|
!isEditingTitle && (
|
||||||
@ -158,14 +158,15 @@ const GroceryItem = ({
|
|||||||
borderRadius: 22,
|
borderRadius: 22,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
/> : <View
|
/> :
|
||||||
|
<View
|
||||||
style={{
|
style={{
|
||||||
position: "relative",
|
position: "relative",
|
||||||
width: 24.64,
|
width: 24.64,
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
marginRight: 4
|
marginRight: 4
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "#ccc",
|
backgroundColor: "#ccc",
|
||||||
@ -177,13 +178,13 @@ const GroceryItem = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getInitials(itemCreator.firstName, itemCreator.lastName ?? "")}
|
{itemCreator ? getInitials(itemCreator.firstName, itemCreator.lastName) : ""}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>}
|
</View>}
|
||||||
|
|||||||
@ -1,299 +1,294 @@
|
|||||||
import { FlatList, StyleSheet } from "react-native";
|
import {FlatList, StyleSheet} from "react-native";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import { Button, Text, TouchableOpacity, View } from "react-native-ui-lib";
|
import {Text, TouchableOpacity, View} from "react-native-ui-lib";
|
||||||
import GroceryItem from "./GroceryItem";
|
import GroceryItem from "./GroceryItem";
|
||||||
import {
|
import {GroceryCategory, GroceryFrequency, useGroceryContext,} from "@/contexts/GroceryContext";
|
||||||
GroceryCategory,
|
|
||||||
GroceryFrequency,
|
|
||||||
useGroceryContext,
|
|
||||||
} from "@/contexts/GroceryContext";
|
|
||||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||||
import { AntDesign, MaterialIcons } from "@expo/vector-icons";
|
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 AddPersonIcon from "@/assets/svgs/AddPersonIcon";
|
||||||
|
|
||||||
const GroceryList = () => {
|
const GroceryList = () => {
|
||||||
const {
|
const {
|
||||||
groceries,
|
groceries,
|
||||||
updateGroceryItem,
|
updateGroceryItem,
|
||||||
isAddingGrocery,
|
isAddingGrocery,
|
||||||
setIsAddingGrocery,
|
setIsAddingGrocery,
|
||||||
addGrocery,
|
addGrocery,
|
||||||
} = useGroceryContext();
|
} = useGroceryContext();
|
||||||
const { profileData } = useAuthContext();
|
const {profileData} = useAuthContext();
|
||||||
const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>(
|
const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>(
|
||||||
groceries?.filter((item) => item.approved === true)
|
groceries?.filter((item) => item.approved)
|
||||||
);
|
);
|
||||||
const [pendingGroceries, setPendingGroceries] = useState<IGrocery[]>(
|
const [pendingGroceries, setPendingGroceries] = useState<IGrocery[]>(
|
||||||
groceries?.filter((item) => item.approved !== true)
|
groceries?.filter((item) => !item.approved)
|
||||||
);
|
);
|
||||||
const [category, setCategory] = useState<GroceryCategory>(
|
const [category, setCategory] = useState<GroceryCategory>(
|
||||||
GroceryCategory.None
|
GroceryCategory.None
|
||||||
);
|
);
|
||||||
|
|
||||||
const [title, setTitle] = useState<string>("");
|
const [title, setTitle] = useState<string>("");
|
||||||
const [submit, setSubmitted] = useState<boolean>(false);
|
const [submit, setSubmitted] = useState<boolean>(false);
|
||||||
|
|
||||||
const [pendingVisible, setPendingVisible] = useState<boolean>(true);
|
const [pendingVisible, setPendingVisible] = useState<boolean>(true);
|
||||||
const [approvedVisible, setApprovedVisible] = useState<boolean>(true);
|
const [approvedVisible, setApprovedVisible] = useState<boolean>(true);
|
||||||
|
|
||||||
// Group approved groceries by category
|
// Group approved groceries by category
|
||||||
const approvedGroceriesByCategory = approvedGroceries?.reduce(
|
const approvedGroceriesByCategory = approvedGroceries?.reduce(
|
||||||
(groups: any, item: IGrocery) => {
|
(groups: any, item: IGrocery) => {
|
||||||
const category = item.category || "Uncategorized";
|
const category = item.category || "Uncategorized";
|
||||||
if (!groups[category]) {
|
if (!groups[category]) {
|
||||||
groups[category] = [];
|
groups[category] = [];
|
||||||
}
|
}
|
||||||
groups[category].push(item);
|
groups[category].push(item);
|
||||||
return groups;
|
return groups;
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (submit) {
|
if (submit) {
|
||||||
if (title?.length > 2 && title?.length <= 25) {
|
if (title?.length > 2 && title?.length <= 25) {
|
||||||
addGrocery({
|
addGrocery({
|
||||||
id: "",
|
id: "",
|
||||||
title: title,
|
title: title,
|
||||||
category: category,
|
category: category,
|
||||||
approved: profileData?.userType === ProfileType.PARENT,
|
approved: profileData?.userType === ProfileType.PARENT,
|
||||||
recurring: false,
|
recurring: false,
|
||||||
frequency: GroceryFrequency.Never,
|
frequency: GroceryFrequency.Never,
|
||||||
bought: false,
|
bought: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsAddingGrocery(false);
|
setIsAddingGrocery(false);
|
||||||
setSubmitted(false);
|
setSubmitted(false);
|
||||||
setTitle("");
|
setTitle("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [submit]);
|
}, [submit]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**/
|
setapprovedGroceries(groceries?.filter((item) => item.approved));
|
||||||
}, [category]);
|
setPendingGroceries(groceries?.filter((item) => !item.approved));
|
||||||
|
}, [groceries]);
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
setapprovedGroceries(groceries?.filter((item) => item.approved === true));
|
<View marginH-20 marginB-20>
|
||||||
setPendingGroceries(groceries?.filter((item) => item.approved !== true));
|
<HeaderTemplate
|
||||||
}, [groceries]);
|
message={"Welcome to your grocery list"}
|
||||||
|
isWelcome={false}
|
||||||
return (
|
>
|
||||||
<View marginH-20 marginB-20>
|
<View row centerV>
|
||||||
<HeaderTemplate
|
<View
|
||||||
message={"Welcome to your grocery list"}
|
backgroundColor="#e2eed8"
|
||||||
isWelcome={false}
|
paddingH-15
|
||||||
>
|
paddingV-8
|
||||||
<View row centerV>
|
marginR-5
|
||||||
<View
|
centerV
|
||||||
backgroundColor="#e2eed8"
|
style={{borderRadius: 50}}
|
||||||
paddingH-15
|
>
|
||||||
paddingV-8
|
<Text text70BL color="#46a80a" style={styles.counterText}>
|
||||||
marginR-5
|
{approvedGroceries?.length} list{" "}
|
||||||
centerV
|
{approvedGroceries?.length === 1 ? (
|
||||||
style={{ borderRadius: 50 }}
|
<Text text70BL color="#46a80a" style={styles.counterText}>
|
||||||
>
|
item
|
||||||
<Text text70BL color="#46a80a" style={styles.counterText}>
|
</Text>
|
||||||
{approvedGroceries?.length} list{" "}
|
) : (
|
||||||
{approvedGroceries?.length === 1 ? (
|
<Text text70BL color="#46a80a" style={styles.counterText}>
|
||||||
<Text text70BL color="#46a80a" style={styles.counterText}>
|
items
|
||||||
item
|
</Text>
|
||||||
</Text>
|
)}
|
||||||
) : (
|
</Text>
|
||||||
<Text text70BL color="#46a80a" style={styles.counterText}>
|
</View>
|
||||||
items
|
<View
|
||||||
</Text>
|
backgroundColor="#faead2"
|
||||||
)}
|
padding-8
|
||||||
</Text>
|
paddingH-12
|
||||||
</View>
|
marginR-15
|
||||||
<View
|
style={{borderRadius: 50}}
|
||||||
backgroundColor="#faead2"
|
>
|
||||||
padding-8
|
<Text text70 style={styles.counterText} color="#e28800">
|
||||||
paddingH-12
|
{pendingGroceries?.length} pending
|
||||||
marginR-15
|
</Text>
|
||||||
style={{ borderRadius: 50 }}
|
</View>
|
||||||
>
|
<TouchableOpacity>
|
||||||
<Text text70 style={styles.counterText} color="#e28800">
|
<AddPersonIcon width={24}/>
|
||||||
{pendingGroceries?.length} pending
|
</TouchableOpacity>
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<TouchableOpacity>
|
|
||||||
<AddPersonIcon width={24}/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</HeaderTemplate>
|
|
||||||
|
|
||||||
{/* Pending Approval Section */}
|
|
||||||
<View row spread marginT-40 marginB-10 centerV>
|
|
||||||
<View row centerV>
|
|
||||||
<Text style={styles.subHeader}>Pending Approval</Text>
|
|
||||||
{pendingVisible && (
|
|
||||||
<AntDesign
|
|
||||||
name="down"
|
|
||||||
size={17}
|
|
||||||
style={styles.dropIcon}
|
|
||||||
color="#9f9f9f"
|
|
||||||
onPress={() => {
|
|
||||||
setPendingVisible(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!pendingVisible && (
|
|
||||||
<AntDesign
|
|
||||||
name="right"
|
|
||||||
size={15}
|
|
||||||
style={styles.dropIcon}
|
|
||||||
color="#9f9f9f"
|
|
||||||
onPress={() => {
|
|
||||||
setPendingVisible(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
centerV
|
|
||||||
style={{
|
|
||||||
aspectRatio: 1,
|
|
||||||
width: 35,
|
|
||||||
backgroundColor: "#faead2",
|
|
||||||
borderRadius: 50,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={styles.counterNr} center color="#e28800">
|
|
||||||
{pendingGroceries?.length.toString()}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{pendingGroceries?.length > 0
|
|
||||||
? pendingVisible && (
|
|
||||||
<FlatList
|
|
||||||
data={pendingGroceries}
|
|
||||||
renderItem={({ item }) => (
|
|
||||||
<GroceryItem
|
|
||||||
item={item}
|
|
||||||
handleItemApproved={(id, changes) =>
|
|
||||||
updateGroceryItem({ ...changes, id: id })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
keyExtractor={(item) => item.id.toString()}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: pendingVisible && (
|
|
||||||
<Text style={styles.noItemTxt}>No items pending approval.</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Approved Section */}
|
|
||||||
<View row spread marginT-40 marginB-0 centerV>
|
|
||||||
<View row centerV>
|
|
||||||
<Text style={styles.subHeader}>Shopping List</Text>
|
|
||||||
{approvedVisible && (
|
|
||||||
<AntDesign
|
|
||||||
name="down"
|
|
||||||
size={17}
|
|
||||||
style={styles.dropIcon}
|
|
||||||
color="#9f9f9f"
|
|
||||||
onPress={() => {
|
|
||||||
setApprovedVisible(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!approvedVisible && (
|
|
||||||
<AntDesign
|
|
||||||
name="right"
|
|
||||||
size={15}
|
|
||||||
style={styles.dropIcon}
|
|
||||||
color="#9f9f9f"
|
|
||||||
onPress={() => {
|
|
||||||
setApprovedVisible(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
centerV
|
|
||||||
style={{
|
|
||||||
aspectRatio: 1,
|
|
||||||
width: 35,
|
|
||||||
backgroundColor: "#e2eed8",
|
|
||||||
borderRadius: 50,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={styles.counterNr} center color="#46a80a">
|
|
||||||
{approvedGroceries?.length.toString()}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{isAddingGrocery && (
|
|
||||||
<EditGroceryItem
|
|
||||||
editGrocery={{
|
|
||||||
title: title,
|
|
||||||
setCategory: setCategory,
|
|
||||||
category: category,
|
|
||||||
setTitle: setTitle,
|
|
||||||
setSubmit: setSubmitted,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Render Approved Groceries Grouped by Category */}
|
|
||||||
{approvedGroceries?.length > 0
|
|
||||||
? approvedVisible && (
|
|
||||||
<FlatList
|
|
||||||
data={Object.keys(approvedGroceriesByCategory)}
|
|
||||||
renderItem={({ item: category }) => (
|
|
||||||
<View key={category}>
|
|
||||||
{/* Render Category Header */}
|
|
||||||
<Text text80M style={{ marginTop: 10 }} color="#666">
|
|
||||||
{category}
|
|
||||||
</Text>
|
|
||||||
{/* Render Grocery Items for this Category */}
|
|
||||||
{approvedGroceriesByCategory[category].map(
|
|
||||||
(grocery: IGrocery) => (
|
|
||||||
<GroceryItem
|
|
||||||
key={grocery.id}
|
|
||||||
item={grocery}
|
|
||||||
handleItemApproved={(id, changes) =>
|
|
||||||
updateGroceryItem({ ...changes, id: id })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
</HeaderTemplate>
|
||||||
keyExtractor={(category) => category}
|
|
||||||
/>
|
{/* Pending Approval Section */}
|
||||||
)
|
<View row spread marginT-40 marginB-10 centerV>
|
||||||
: approvedVisible && (
|
<View row centerV>
|
||||||
<Text style={styles.noItemTxt}>No approved items.</Text>
|
<Text style={styles.subHeader}>Pending Approval</Text>
|
||||||
)}
|
{pendingVisible && (
|
||||||
</View>
|
<AntDesign
|
||||||
);
|
name="down"
|
||||||
|
size={17}
|
||||||
|
style={styles.dropIcon}
|
||||||
|
color="#9f9f9f"
|
||||||
|
onPress={() => {
|
||||||
|
setPendingVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!pendingVisible && (
|
||||||
|
<AntDesign
|
||||||
|
name="right"
|
||||||
|
size={15}
|
||||||
|
style={styles.dropIcon}
|
||||||
|
color="#9f9f9f"
|
||||||
|
onPress={() => {
|
||||||
|
setPendingVisible(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
centerV
|
||||||
|
style={{
|
||||||
|
aspectRatio: 1,
|
||||||
|
width: 35,
|
||||||
|
backgroundColor: "#faead2",
|
||||||
|
borderRadius: 50,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.counterNr} center color="#e28800">
|
||||||
|
{pendingGroceries?.length.toString()}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{pendingGroceries?.length > 0
|
||||||
|
? pendingVisible && (
|
||||||
|
<FlatList
|
||||||
|
data={pendingGroceries}
|
||||||
|
renderItem={({item}) => (
|
||||||
|
<GroceryItem
|
||||||
|
item={item}
|
||||||
|
handleItemApproved={(id, changes) =>
|
||||||
|
updateGroceryItem({...changes, id: id})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
keyExtractor={(item) => item.id.toString()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: pendingVisible && (
|
||||||
|
<Text style={styles.noItemTxt}>No items pending approval.</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Approved Section */}
|
||||||
|
<View row spread marginT-40 marginB-0 centerV>
|
||||||
|
<View row centerV>
|
||||||
|
<Text style={styles.subHeader}>Shopping List</Text>
|
||||||
|
{approvedVisible && (
|
||||||
|
<AntDesign
|
||||||
|
name="down"
|
||||||
|
size={17}
|
||||||
|
style={styles.dropIcon}
|
||||||
|
color="#9f9f9f"
|
||||||
|
onPress={() => {
|
||||||
|
setApprovedVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!approvedVisible && (
|
||||||
|
<AntDesign
|
||||||
|
name="right"
|
||||||
|
size={15}
|
||||||
|
style={styles.dropIcon}
|
||||||
|
color="#9f9f9f"
|
||||||
|
onPress={() => {
|
||||||
|
setApprovedVisible(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
centerV
|
||||||
|
style={{
|
||||||
|
aspectRatio: 1,
|
||||||
|
width: 35,
|
||||||
|
backgroundColor: "#e2eed8",
|
||||||
|
borderRadius: 50,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.counterNr} center color="#46a80a">
|
||||||
|
{approvedGroceries?.length.toString()}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{isAddingGrocery && (
|
||||||
|
<View style={{marginTop: 8}}>
|
||||||
|
<EditGroceryItem
|
||||||
|
editGrocery={{
|
||||||
|
title: title,
|
||||||
|
setCategory: setCategory,
|
||||||
|
category: category,
|
||||||
|
setTitle: setTitle,
|
||||||
|
setSubmit: setSubmitted,
|
||||||
|
closeEdit: () => setIsAddingGrocery(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Render Approved Groceries Grouped by Category */}
|
||||||
|
{approvedGroceries?.length > 0
|
||||||
|
? approvedVisible && (
|
||||||
|
<FlatList
|
||||||
|
data={Object.keys(approvedGroceriesByCategory)}
|
||||||
|
renderItem={({item: category}) => (
|
||||||
|
<View key={category}>
|
||||||
|
{/* Render Category Header */}
|
||||||
|
<Text text80M style={{marginTop: 10}} color="#666">
|
||||||
|
{category}
|
||||||
|
</Text>
|
||||||
|
{/* Render Grocery Items for this Category */}
|
||||||
|
{approvedGroceriesByCategory[category].map(
|
||||||
|
(grocery: IGrocery) => (
|
||||||
|
<GroceryItem
|
||||||
|
key={grocery.id}
|
||||||
|
item={grocery}
|
||||||
|
handleItemApproved={(id, changes) =>
|
||||||
|
updateGroceryItem({...changes, id: id})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
keyExtractor={(category) => category}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: approvedVisible && (
|
||||||
|
<Text style={styles.noItemTxt}>No approved items.</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
dropIcon: {
|
dropIcon: {
|
||||||
marginHorizontal: 10,
|
marginHorizontal: 10,
|
||||||
},
|
},
|
||||||
noItemTxt: {
|
noItemTxt: {
|
||||||
fontFamily: "Manrope_400Regular",
|
fontFamily: "Manrope_400Regular",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
counterText: {
|
counterText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: "PlusJakartaSans_600SemiBold",
|
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||||
},
|
},
|
||||||
subHeader: {
|
subHeader: {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontFamily: "Manrope_700Bold",
|
fontFamily: "Manrope_700Bold",
|
||||||
},
|
},
|
||||||
counterNr: {
|
counterNr: {
|
||||||
fontFamily: "PlusJakartaSans_600SemiBold",
|
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||||
fontSize: 14
|
fontSize: 14
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default GroceryList;
|
export default GroceryList;
|
||||||
|
|||||||
@ -1,38 +1,38 @@
|
|||||||
import { Text, ScrollView } from "react-native";
|
import {ScrollView} from "react-native";
|
||||||
import { View } from "react-native-ui-lib";
|
import {View} from "react-native-ui-lib";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, {useEffect, useRef} from "react";
|
||||||
import AddGroceryItem from "./AddGroceryItem";
|
import AddGroceryItem from "./AddGroceryItem";
|
||||||
import GroceryList from "./GroceryList";
|
import GroceryList from "./GroceryList";
|
||||||
import { useGroceryContext } from "@/contexts/GroceryContext";
|
import {useGroceryContext} from "@/contexts/GroceryContext";
|
||||||
|
|
||||||
const GroceryWrapper = () => {
|
const GroceryWrapper = () => {
|
||||||
const { isAddingGrocery } = useGroceryContext();
|
const {isAddingGrocery} = useGroceryContext();
|
||||||
const scrollViewRef = useRef<ScrollView>(null); // Reference to the ScrollView
|
const scrollViewRef = useRef<ScrollView>(null); // Reference to the ScrollView
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAddingGrocery && scrollViewRef.current) {
|
if (isAddingGrocery && scrollViewRef.current) {
|
||||||
scrollViewRef.current.scrollTo({
|
scrollViewRef.current.scrollTo({
|
||||||
y: 400, // Adjust this value to scroll a bit down (100 is an example)
|
y: 400, // Adjust this value to scroll a bit down (100 is an example)
|
||||||
animated: true,
|
animated: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isAddingGrocery]);
|
}, [isAddingGrocery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View height={"100%"} paddingT-15 paddingH-15>
|
<View height={"100%"} paddingT-15 paddingH-15>
|
||||||
<View height={"100%"}>
|
<View height={"100%"}>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
ref={scrollViewRef} // Assign the ref to the ScrollView
|
ref={scrollViewRef}
|
||||||
automaticallyAdjustKeyboardInsets={true}
|
automaticallyAdjustKeyboardInsets={true}
|
||||||
>
|
>
|
||||||
<View marginB-70>
|
<View marginB-70>
|
||||||
<GroceryList />
|
<GroceryList/>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
{!isAddingGrocery && <AddGroceryItem />}
|
{!isAddingGrocery && <AddGroceryItem/>}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GroceryWrapper;
|
export default GroceryWrapper;
|
||||||
|
|||||||
@ -1,170 +1,218 @@
|
|||||||
import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib";
|
import {
|
||||||
import React, {useState} from "react";
|
Button,
|
||||||
import {useSignIn} from "@/hooks/firebase/useSignIn";
|
ButtonSize,
|
||||||
import {StyleSheet} from "react-native";
|
Dialog,
|
||||||
import Toast from 'react-native-toast-message';
|
Text,
|
||||||
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
|
TextField,
|
||||||
import {Camera, CameraView} from 'expo-camera';
|
TextFieldRef,
|
||||||
|
View,
|
||||||
|
} from "react-native-ui-lib";
|
||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import { useSignIn } from "@/hooks/firebase/useSignIn";
|
||||||
|
import { StyleSheet } from "react-native";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
import { useLoginWithQrCode } from "@/hooks/firebase/useLoginWithQrCode";
|
||||||
|
import { Camera, CameraView } from "expo-camera";
|
||||||
|
|
||||||
const SignInPage = ({setTab}: {
|
const SignInPage = ({
|
||||||
setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">>
|
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 [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
|
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
|
||||||
|
const passwordRef = useRef<TextFieldRef>(null);
|
||||||
|
|
||||||
const {mutateAsync: signIn, error, isError} = useSignIn();
|
const { mutateAsync: signIn, error, isError } = useSignIn();
|
||||||
const {mutateAsync: signInWithQrCode} = useLoginWithQrCode()
|
const { mutateAsync: signInWithQrCode } = useLoginWithQrCode();
|
||||||
|
|
||||||
const handleSignIn = async () => {
|
const handleSignIn = async () => {
|
||||||
await signIn({email, password});
|
await signIn({ email, password });
|
||||||
if (!isError) {
|
if (!isError) {
|
||||||
Toast.show({
|
Toast.show({
|
||||||
type: "success",
|
type: "success",
|
||||||
text1: "Login successful!"
|
text1: "Login successful!",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Toast.show({
|
Toast.show({
|
||||||
type: "error",
|
type: "error",
|
||||||
text1: "Error logging in",
|
text1: "Error logging in",
|
||||||
text2: `${error}`
|
text2: `${error}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQrCodeScanned = async ({data}: { data: string }) => {
|
const handleQrCodeScanned = async ({ data }: { data: string }) => {
|
||||||
setShowCameraDialog(false);
|
setShowCameraDialog(false);
|
||||||
try {
|
try {
|
||||||
await signInWithQrCode({userId: data});
|
await signInWithQrCode({ userId: data });
|
||||||
Toast.show({
|
Toast.show({
|
||||||
type: "success",
|
type: "success",
|
||||||
text1: "Login successful with QR code!"
|
text1: "Login successful with QR code!",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Toast.show({
|
Toast.show({
|
||||||
type: "error",
|
type: "error",
|
||||||
text1: "Error logging in with QR code",
|
text1: "Error logging in with QR code",
|
||||||
text2: `${err}`
|
text2: `${err}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCameraPermissions = async (callback: () => void) => {
|
const getCameraPermissions = async (callback: () => void) => {
|
||||||
const {status} = await Camera.requestCameraPermissionsAsync();
|
const { status } = await Camera.requestCameraPermissionsAsync();
|
||||||
setHasPermission(status === 'granted');
|
setHasPermission(status === "granted");
|
||||||
if (status === 'granted') {
|
if (status === "granted") {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View padding-10 centerV height={"100%"}>
|
<View padding-10 centerV height={"100%"}>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
value={email}
|
value={email}
|
||||||
onChangeText={setEmail}
|
|
||||||
style={styles.textfield}
|
onChangeText={setEmail}
|
||||||
/>
|
style={styles.textfield}
|
||||||
<TextField
|
onSubmitEditing={() => {
|
||||||
placeholder="Password"
|
// Move focus to the description field
|
||||||
value={password}
|
passwordRef.current?.focus();
|
||||||
onChangeText={setPassword}
|
}}
|
||||||
secureTextEntry
|
/>
|
||||||
style={styles.textfield}
|
<TextField
|
||||||
/>
|
ref={passwordRef}
|
||||||
<Button
|
placeholder="Password"
|
||||||
label="Login"
|
value={password}
|
||||||
onPress={handleSignIn}
|
onChangeText={setPassword}
|
||||||
style={{marginBottom: 20}}
|
secureTextEntry
|
||||||
backgroundColor="#fd1775"
|
style={styles.textfield}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
label="Login with a QR Code"
|
label="Log in"
|
||||||
onPress={() => {
|
marginT-50
|
||||||
getCameraPermissions(() => setShowCameraDialog(true));
|
labelStyle={{
|
||||||
}
|
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||||
}
|
fontSize: 16,
|
||||||
style={{marginBottom: 20}}
|
}}
|
||||||
backgroundColor="#fd1775"
|
onPress={handleSignIn}
|
||||||
/>
|
style={{ marginBottom: 20, height: 50 }}
|
||||||
{isError && <Text center style={{marginBottom: 20}}>{`${error?.toString()?.split("]")?.[1]}`}</Text>}
|
backgroundColor="#fd1775"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="Log in with a QR Code"
|
||||||
|
labelStyle={{
|
||||||
|
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
getCameraPermissions(() => setShowCameraDialog(true));
|
||||||
|
}}
|
||||||
|
style={{ marginBottom: 20, height: 50 }}
|
||||||
|
backgroundColor="#fd1775"
|
||||||
|
/>
|
||||||
|
{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 style={styles.jakartaLight}>Don't have an account?</Text>
|
||||||
Don't have an account?
|
<Button
|
||||||
</Text>
|
onPress={() => setTab("register")}
|
||||||
<Button
|
label="Sign Up"
|
||||||
onPress={() => setTab("register")}
|
labelStyle={[
|
||||||
label="Sign Up"
|
styles.jakartaMedium,
|
||||||
link
|
{ textDecorationLine: "none", color: "#fd1575" },
|
||||||
size={ButtonSize.xSmall}
|
]}
|
||||||
padding-0
|
link
|
||||||
margin-0
|
size={ButtonSize.xSmall}
|
||||||
text70
|
padding-0
|
||||||
left
|
margin-0
|
||||||
color="#fd1775"
|
text70
|
||||||
/>
|
left
|
||||||
</View>
|
color="#fd1775"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View row centerH marginB-5 gap-5>
|
<View row centerH marginB-5 gap-5>
|
||||||
<Text text70>
|
<Text text70>Forgot your password?</Text>
|
||||||
Forgot your password?
|
<Button
|
||||||
</Text>
|
onPress={() => setTab("reset-password")}
|
||||||
<Button
|
label="Reset password"
|
||||||
onPress={() => setTab("reset-password")}
|
labelStyle={[
|
||||||
label="Reset password"
|
styles.jakartaMedium,
|
||||||
link
|
{ textDecorationLine: "none", color: "#fd1575" },
|
||||||
size={ButtonSize.xSmall}
|
]}
|
||||||
padding-0
|
link
|
||||||
margin-0
|
size={ButtonSize.xSmall}
|
||||||
text70
|
padding-0
|
||||||
left
|
margin-0
|
||||||
color="#fd1775"
|
text70
|
||||||
/>
|
left
|
||||||
</View>
|
avoidInnerPadding
|
||||||
|
color="#fd1775"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Camera Dialog */}
|
{/* Camera Dialog */}
|
||||||
<Dialog
|
<Dialog
|
||||||
visible={showCameraDialog}
|
visible={showCameraDialog}
|
||||||
onDismiss={() => setShowCameraDialog(false)}
|
onDismiss={() => setShowCameraDialog(false)}
|
||||||
bottom
|
bottom
|
||||||
width="100%"
|
width="100%"
|
||||||
height="70%"
|
height="70%"
|
||||||
containerStyle={{padding: 0}}
|
containerStyle={{ padding: 0 }}
|
||||||
>
|
>
|
||||||
{hasPermission === null ? (
|
{hasPermission === null ? (
|
||||||
<Text>Requesting camera permissions...</Text>
|
<Text>Requesting camera permissions...</Text>
|
||||||
) : !hasPermission ? (
|
) : !hasPermission ? (
|
||||||
<Text>No access to camera</Text>
|
<Text>No access to camera</Text>
|
||||||
) : (
|
) : (
|
||||||
<CameraView
|
<CameraView
|
||||||
style={{flex: 1}}
|
style={{ flex: 1 }}
|
||||||
onBarcodeScanned={handleQrCodeScanned}
|
onBarcodeScanned={handleQrCodeScanned}
|
||||||
barcodeScannerSettings={{
|
barcodeScannerSettings={{
|
||||||
barcodeTypes: ["qr"],
|
barcodeTypes: ["qr"],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
label="Cancel"
|
label="Cancel"
|
||||||
onPress={() => setShowCameraDialog(false)}
|
onPress={() => setShowCameraDialog(false)}
|
||||||
backgroundColor="#fd1775"
|
backgroundColor="#fd1775"
|
||||||
style={{margin: 10}}
|
style={{ margin: 10 }}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
textfield: {
|
textfield: {
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
marginVertical: 10,
|
marginVertical: 10,
|
||||||
padding: 30,
|
padding: 30,
|
||||||
height: 45,
|
height: 45,
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
},
|
fontFamily: "PlusJakartaSans_300Light",
|
||||||
|
},
|
||||||
|
jakartaLight: {
|
||||||
|
fontFamily: "PlusJakartaSans_300Light",
|
||||||
|
fontSize: 16,
|
||||||
|
color: "#484848",
|
||||||
|
},
|
||||||
|
jakartaMedium: {
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
fontSize: 16,
|
||||||
|
color: "#919191",
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SignInPage;
|
export default SignInPage;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
} from "react-native-ui-lib";
|
} from "react-native-ui-lib";
|
||||||
import { useSignUp } from "@/hooks/firebase/useSignUp";
|
import { useSignUp } from "@/hooks/firebase/useSignUp";
|
||||||
import { ProfileType } from "@/contexts/AuthContext";
|
import { ProfileType } from "@/contexts/AuthContext";
|
||||||
import { StyleSheet } from "react-native";
|
import { Dimensions, StyleSheet } from "react-native";
|
||||||
import { AntDesign } from "@expo/vector-icons";
|
import { AntDesign } from "@expo/vector-icons";
|
||||||
|
|
||||||
const SignUpPage = ({
|
const SignUpPage = ({
|
||||||
@ -40,19 +40,21 @@ const SignUpPage = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View padding-10 height={"100%"} flexG>
|
<View padding-15 marginT-30 height={Dimensions.get("window").height} flexG>
|
||||||
<Text text30 center>
|
<Text style={styles.title}>Get started with Cally</Text>
|
||||||
Get started with Cally
|
<Text style={styles.subtitle} marginT-15 color="#919191">
|
||||||
|
Please enter your details.
|
||||||
</Text>
|
</Text>
|
||||||
<Text center>Please enter your details.</Text>
|
|
||||||
<TextField
|
<TextField
|
||||||
marginT-60
|
marginT-30
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder="First name"
|
placeholder="First name"
|
||||||
value={firstName}
|
value={firstName}
|
||||||
onChangeText={setFirstName}
|
onChangeText={setFirstName}
|
||||||
style={styles.textfield}
|
style={styles.textfield}
|
||||||
onSubmitEditing={() => {lnameRef.current?.focus()}}
|
onSubmitEditing={() => {
|
||||||
|
lnameRef.current?.focus();
|
||||||
|
}}
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@ -61,7 +63,9 @@ const SignUpPage = ({
|
|||||||
value={lastName}
|
value={lastName}
|
||||||
onChangeText={setLastName}
|
onChangeText={setLastName}
|
||||||
style={styles.textfield}
|
style={styles.textfield}
|
||||||
onSubmitEditing={() => {emailRef.current?.focus()}}
|
onSubmitEditing={() => {
|
||||||
|
emailRef.current?.focus();
|
||||||
|
}}
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
@ -70,58 +74,69 @@ const SignUpPage = ({
|
|||||||
value={email}
|
value={email}
|
||||||
onChangeText={setEmail}
|
onChangeText={setEmail}
|
||||||
style={styles.textfield}
|
style={styles.textfield}
|
||||||
onSubmitEditing={() => {passwordRef.current?.focus()}}
|
onSubmitEditing={() => {
|
||||||
|
passwordRef.current?.focus();
|
||||||
|
}}
|
||||||
blurOnSubmit={false}
|
blurOnSubmit={false}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<View
|
||||||
ref={passwordRef}
|
centerV
|
||||||
placeholder="Password"
|
style={[styles.textfield, { padding: 0, paddingHorizontal: 30 }]}
|
||||||
value={password}
|
>
|
||||||
onChangeText={setPassword}
|
<TextField
|
||||||
secureTextEntry={!isPasswordVisible}
|
ref={passwordRef}
|
||||||
style={styles.textfield}
|
placeholder="Password"
|
||||||
trailingAccessory={
|
style={styles.jakartaLight}
|
||||||
<TouchableOpacity
|
value={password}
|
||||||
onPress={() => setIsPasswordVisible(!isPasswordVisible)}
|
onChangeText={setPassword}
|
||||||
>
|
secureTextEntry={!isPasswordVisible}
|
||||||
<AntDesign
|
trailingAccessory={
|
||||||
name={isPasswordVisible ? "eye" : "eyeo"}
|
<TouchableOpacity
|
||||||
size={24}
|
onPress={() => setIsPasswordVisible(!isPasswordVisible)}
|
||||||
color="gray"
|
>
|
||||||
/>
|
<AntDesign
|
||||||
</TouchableOpacity>
|
name={isPasswordVisible ? "eye" : "eyeo"}
|
||||||
}
|
size={24}
|
||||||
/>
|
color="gray"
|
||||||
<View gap-10 marginH-10>
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View gap-5 marginT-15>
|
||||||
<View row centerV>
|
<View row centerV>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
style={[styles.check]}
|
||||||
|
color="#919191"
|
||||||
value={allowFaceID}
|
value={allowFaceID}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setAllowFaceID(value);
|
setAllowFaceID(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text text90R marginL-10>
|
<Text style={styles.jakartaLight} marginL-10>
|
||||||
Allow FaceID for login in future
|
Allow FaceID for login in future
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View row centerV>
|
<View row centerV>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
style={styles.check}
|
||||||
|
color="#919191"
|
||||||
value={acceptTerms}
|
value={acceptTerms}
|
||||||
onValueChange={(value) => setAcceptTerms(value)}
|
onValueChange={(value) => setAcceptTerms(value)}
|
||||||
/>
|
/>
|
||||||
<View row>
|
<View row>
|
||||||
<Text text90R marginL-10>
|
<Text style={styles.jakartaLight} marginL-10>
|
||||||
I accept the
|
I accept the
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity>
|
<TouchableOpacity>
|
||||||
<Text text90 style={{ textDecorationLine: "underline" }}>
|
<Text text90 style={styles.jakartaMedium}>
|
||||||
{" "}
|
{" "}
|
||||||
terms and conditions
|
terms and conditions
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text text90R> and </Text>
|
<Text style={styles.jakartaLight}> and </Text>
|
||||||
<TouchableOpacity>
|
<TouchableOpacity>
|
||||||
<Text text90 style={{ textDecorationLine: "underline" }}>
|
<Text text90 style={styles.jakartaMedium}>
|
||||||
{" "}
|
{" "}
|
||||||
privacy policy
|
privacy policy
|
||||||
</Text>
|
</Text>
|
||||||
@ -132,16 +147,24 @@ const SignUpPage = ({
|
|||||||
<View style={styles.bottomView}>
|
<View style={styles.bottomView}>
|
||||||
<Button
|
<Button
|
||||||
label="Register"
|
label="Register"
|
||||||
|
labelStyle={{
|
||||||
|
fontFamily: "PlusJakartaSans_600SemiBold",
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
onPress={handleSignUp}
|
onPress={handleSignUp}
|
||||||
style={{ marginBottom: 10, backgroundColor: "#fd1775" }}
|
style={{ marginBottom: 0, backgroundColor: "#fd1775", height: 50 }}
|
||||||
/>
|
/>
|
||||||
<View row centerH marginT-10 marginB-5 gap-5>
|
<View row centerH marginT-10 marginB-2 gap-5>
|
||||||
<Text text70 center>
|
<Text style={[styles.jakartaLight, { fontSize: 16, color: "#484848" }]} center>
|
||||||
Already have an account?
|
Already have an account?
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
label="Sign In"
|
label="Log in"
|
||||||
|
labelStyle={[
|
||||||
|
styles.jakartaMedium,
|
||||||
|
{ fontSize: 16, textDecorationLine: "none", color: "#fd1775" },
|
||||||
|
]}
|
||||||
flexS
|
flexS
|
||||||
margin-0
|
margin-0
|
||||||
link
|
link
|
||||||
@ -161,11 +184,35 @@ export default SignUpPage;
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
textfield: {
|
textfield: {
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
marginVertical: 10,
|
marginVertical: 8,
|
||||||
padding: 30,
|
padding: 30,
|
||||||
height: 45,
|
height: 44,
|
||||||
borderRadius: 50,
|
borderRadius: 50,
|
||||||
|
fontFamily: "PlusJakartaSans_300Light",
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#919191",
|
||||||
},
|
},
|
||||||
//mora da se izmeni kako treba
|
//mora da se izmeni kako treba
|
||||||
bottomView: { marginTop: 150 },
|
bottomView: { marginTop: "auto", marginBottom: 30 },
|
||||||
|
jakartaLight: {
|
||||||
|
fontFamily: "PlusJakartaSans_300Light",
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#919191",
|
||||||
|
},
|
||||||
|
jakartaMedium: {
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#919191",
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
},
|
||||||
|
title: { fontFamily: "Manrope_600SemiBold", fontSize: 34, marginTop: 50 },
|
||||||
|
subtitle: { fontFamily: "PlusJakartaSans_400Regular", fontSize: 16 },
|
||||||
|
check: {
|
||||||
|
borderRadius: 3,
|
||||||
|
aspectRatio: 1,
|
||||||
|
width: 18,
|
||||||
|
color: "#919191",
|
||||||
|
borderColor: "#919191",
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,338 +1,353 @@
|
|||||||
import { View, Text, Button, Switch, PickerModes } from "react-native-ui-lib";
|
|
||||||
import React, { useRef, useState } from "react";
|
|
||||||
import PointsSlider from "@/components/shared/PointsSlider";
|
|
||||||
import { repeatOptions, useToDosContext } from "@/contexts/ToDosContext";
|
|
||||||
import { Feather, AntDesign, Ionicons } from "@expo/vector-icons";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Button,
|
||||||
TextField,
|
ButtonSize,
|
||||||
DateTimePicker,
|
DateTimePicker,
|
||||||
Picker,
|
Dialog,
|
||||||
ButtonSize,
|
Picker,
|
||||||
|
PickerModes,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
TextFieldRef,
|
||||||
|
View
|
||||||
} from "react-native-ui-lib";
|
} from "react-native-ui-lib";
|
||||||
import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
import { Dimensions, StyleSheet } from "react-native";
|
import PointsSlider from "@/components/shared/PointsSlider";
|
||||||
|
import {repeatOptions, useToDosContext} from "@/contexts/ToDosContext";
|
||||||
|
import {Ionicons} from "@expo/vector-icons";
|
||||||
|
import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
|
||||||
|
import {Dimensions, KeyboardAvoidingView, StyleSheet} from "react-native";
|
||||||
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
import DropModalIcon from "@/assets/svgs/DropModalIcon";
|
||||||
import { IToDo } from "@/hooks/firebase/types/todoData";
|
import {IToDo} from "@/hooks/firebase/types/todoData";
|
||||||
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
|
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
|
||||||
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
|
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
|
||||||
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
import CalendarIcon from "@/assets/svgs/CalendarIcon";
|
||||||
import ClockIcon from "@/assets/svgs/ClockIcon";
|
|
||||||
import ClockOIcon from "@/assets/svgs/ClockOIcon";
|
import ClockOIcon from "@/assets/svgs/ClockOIcon";
|
||||||
import ProfileIcon from "@/assets/svgs/ProfileIcon";
|
import ProfileIcon from "@/assets/svgs/ProfileIcon";
|
||||||
import RepeatFreq from "./RepeatFreq";
|
import RepeatFreq from "./RepeatFreq";
|
||||||
|
|
||||||
interface IAddChoreDialog {
|
interface IAddChoreDialog {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
setIsVisible: (value: boolean) => void;
|
setIsVisible: (value: boolean) => void;
|
||||||
selectedTodo?: IToDo;
|
selectedTodo?: IToDo;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTodo = {
|
const defaultTodo = {
|
||||||
id: "",
|
id: "",
|
||||||
title: "",
|
title: "",
|
||||||
points: 10,
|
points: 10,
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
rotate: false,
|
rotate: false,
|
||||||
repeatType: "Every week",
|
repeatType: "Every week",
|
||||||
assignees: [],
|
assignees: [],
|
||||||
repeatDays: []
|
repeatDays: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
|
||||||
const { addToDo, updateToDo } = useToDosContext();
|
const {addToDo, updateToDo} = useToDosContext();
|
||||||
const [todo, setTodo] = useState<IToDo>(
|
const [todo, setTodo] = useState<IToDo>(
|
||||||
addChoreDialogProps.selectedTodo ?? defaultTodo
|
addChoreDialogProps.selectedTodo ?? defaultTodo
|
||||||
);
|
);
|
||||||
const [selectedAssignees, setSelectedAssignees] = useState<string[]>(
|
const [selectedAssignees, setSelectedAssignees] = useState<string[]>(
|
||||||
addChoreDialogProps?.selectedTodo?.assignees ?? []
|
addChoreDialogProps?.selectedTodo?.assignees ?? []
|
||||||
);
|
);
|
||||||
const { width, height } = Dimensions.get("screen");
|
const {width} = Dimensions.get("screen");
|
||||||
const [points, setPoints] = useState<number>(todo.points);
|
const [points, setPoints] = useState<number>(todo.points);
|
||||||
|
|
||||||
const { data: members } = useGetFamilyMembers();
|
const titleRef = useRef<TextFieldRef>(null)
|
||||||
|
|
||||||
const handleClose = () => {
|
const {data: members} = useGetFamilyMembers();
|
||||||
setTodo(defaultTodo);
|
|
||||||
setSelectedAssignees([]);
|
|
||||||
addChoreDialogProps.setIsVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (text: string) => {
|
const handleClose = () => {
|
||||||
const numericValue = parseInt(text, 10);
|
setTodo(defaultTodo);
|
||||||
|
setSelectedAssignees([]);
|
||||||
|
addChoreDialogProps.setIsVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
if (!isNaN(numericValue) && numericValue >= 0 && numericValue <= 100) {
|
const handleChange = (text: string) => {
|
||||||
setPoints(numericValue);
|
const numericValue = parseInt(text, 10);
|
||||||
} else if (text === "") {
|
|
||||||
setPoints(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRepeatDaysChange = (day: string, set: boolean) => {
|
if (!isNaN(numericValue) && numericValue >= 0 && numericValue <= 100) {
|
||||||
if (set) {
|
setPoints(numericValue);
|
||||||
const updatedTodo = {
|
} else if (text === "") {
|
||||||
...todo,
|
setPoints(0);
|
||||||
repeatDays: [...todo.repeatDays, day]
|
}
|
||||||
}
|
};
|
||||||
setTodo(updatedTodo);
|
|
||||||
} else {
|
|
||||||
const array = todo.repeatDays ?? [];
|
|
||||||
let index = array.indexOf(day);
|
|
||||||
array.splice(index, 1);
|
|
||||||
const updatedTodo = {
|
|
||||||
...todo,
|
|
||||||
repeatDays: array
|
|
||||||
}
|
|
||||||
setTodo(updatedTodo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const handleRepeatDaysChange = (day: string, set: boolean) => {
|
||||||
<Dialog
|
if (set) {
|
||||||
bottom={true}
|
const updatedTodo = {
|
||||||
height={"90%"}
|
...todo,
|
||||||
width={width}
|
repeatDays: [...todo.repeatDays, day]
|
||||||
panDirection={PanningDirectionsEnum.DOWN}
|
|
||||||
onDismiss={() => handleClose}
|
|
||||||
containerStyle={{
|
|
||||||
borderRadius: 10,
|
|
||||||
backgroundColor: "white",
|
|
||||||
alignSelf: "stretch",
|
|
||||||
padding: 0,
|
|
||||||
paddingTop: 4,
|
|
||||||
margin: 0,
|
|
||||||
}}
|
|
||||||
visible={addChoreDialogProps.isVisible}
|
|
||||||
>
|
|
||||||
<View row spread>
|
|
||||||
<Button
|
|
||||||
color="#05a8b6"
|
|
||||||
style={styles.topBtn}
|
|
||||||
label="Cancel"
|
|
||||||
onPress={() => {
|
|
||||||
handleClose();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<View marginT-12>
|
|
||||||
<DropModalIcon
|
|
||||||
onPress={() => {
|
|
||||||
handleClose();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Button
|
|
||||||
color="#05a8b6"
|
|
||||||
style={styles.topBtn}
|
|
||||||
label="Save"
|
|
||||||
onPress={() => {
|
|
||||||
try {
|
|
||||||
if (addChoreDialogProps.selectedTodo) {
|
|
||||||
updateToDo({
|
|
||||||
...todo,
|
|
||||||
points: points,
|
|
||||||
assignees: selectedAssignees
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
addToDo({
|
|
||||||
...todo,
|
|
||||||
done: false,
|
|
||||||
points: points,
|
|
||||||
assignees: selectedAssignees,
|
|
||||||
repeatDays: todo.repeatDays ?? []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
handleClose();
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
}}
|
setTodo(updatedTodo);
|
||||||
/>
|
} else {
|
||||||
</View>
|
const array = todo.repeatDays ?? [];
|
||||||
<TextField
|
let index = array.indexOf(day);
|
||||||
placeholder="Add a To Do"
|
array.splice(index, 1);
|
||||||
autoFocus
|
const updatedTodo = {
|
||||||
value={todo?.title}
|
...todo,
|
||||||
onChangeText={(text) => {
|
repeatDays: array
|
||||||
setTodo((oldValue: IToDo) => ({ ...oldValue, title: text }));
|
}
|
||||||
}}
|
setTodo(updatedTodo);
|
||||||
placeholderTextColor="#2d2d30"
|
}
|
||||||
text60R
|
}
|
||||||
marginT-15
|
|
||||||
marginL-30
|
|
||||||
/>
|
|
||||||
<View style={styles.divider} marginT-8 />
|
|
||||||
<View marginL-30 centerV>
|
|
||||||
<View row marginB-10>
|
|
||||||
{todo?.date && (
|
|
||||||
<View row centerV>
|
|
||||||
<CalendarIcon color="#919191" width={24} height={24} />
|
|
||||||
<DateTimePicker
|
|
||||||
value={todo.date}
|
|
||||||
text70
|
|
||||||
style={{
|
|
||||||
fontFamily: "PlusJakartaSans_500Medium",
|
|
||||||
fontSize: 16,
|
|
||||||
}}
|
|
||||||
marginL-12
|
|
||||||
onChange={(date) => {
|
|
||||||
setTodo((oldValue: IToDo) => ({ ...oldValue, date: date }));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View row centerV>
|
|
||||||
<ClockOIcon />
|
|
||||||
<Picker
|
|
||||||
marginL-12
|
|
||||||
placeholder="Select Repeat Type"
|
|
||||||
value={todo?.repeatType}
|
|
||||||
onChange={(value) => {
|
|
||||||
if (value) {
|
|
||||||
if (value.toString() == "None") {
|
|
||||||
setTodo((oldValue) => ({
|
|
||||||
...oldValue,
|
|
||||||
date: null,
|
|
||||||
repeatType: "None",
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
setTodo((oldValue) => ({
|
|
||||||
...oldValue,
|
|
||||||
date: new Date(),
|
|
||||||
repeatType: value.toString(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
topBarProps={{ title: "Repeat" }}
|
|
||||||
style={{
|
|
||||||
marginVertical: 5,
|
|
||||||
fontFamily: "PlusJakartaSans_500Medium",
|
|
||||||
fontSize: 16,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{repeatOptions.map((option) => (
|
|
||||||
<Picker.Item
|
|
||||||
key={option.value}
|
|
||||||
label={option.label}
|
|
||||||
value={option.value}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Picker>
|
|
||||||
</View>
|
|
||||||
{todo.repeatType == "Every week" && <RepeatFreq handleRepeatDaysChange={handleRepeatDaysChange} repeatDays={todo.repeatDays ?? []}/>}
|
|
||||||
</View>
|
|
||||||
<View style={styles.divider} />
|
|
||||||
|
|
||||||
<View marginH-30 marginB-10 row centerV>
|
useEffect(() => {
|
||||||
<ProfileIcon color="#919191" />
|
setTimeout(() => {
|
||||||
<Text style={styles.sub} marginL-10>
|
titleRef?.current?.focus()
|
||||||
Assignees
|
}, 500)
|
||||||
</Text>
|
}, []);
|
||||||
<View flex-1 />
|
|
||||||
<Picker
|
return (
|
||||||
marginL-8
|
<Dialog
|
||||||
value={selectedAssignees}
|
bottom={true}
|
||||||
onChange={(value) => {
|
height={"90%"}
|
||||||
setSelectedAssignees([...selectedAssignees, ...value]);
|
width={width}
|
||||||
}}
|
panDirection={PanningDirectionsEnum.DOWN}
|
||||||
style={{ marginVertical: 5 }}
|
onDismiss={() => handleClose}
|
||||||
mode={PickerModes.MULTI}
|
containerStyle={{
|
||||||
renderInput={() => (
|
borderRadius: 10,
|
||||||
<Button
|
backgroundColor: "white",
|
||||||
size={ButtonSize.small}
|
alignSelf: "stretch",
|
||||||
paddingH-8
|
padding: 0,
|
||||||
iconSource={() => (
|
paddingTop: 4,
|
||||||
<Ionicons name="add-outline" size={20} color="#ea156c" />
|
margin: 0,
|
||||||
)}
|
}}
|
||||||
style={{
|
visible={addChoreDialogProps.isVisible}
|
||||||
marginLeft: "auto",
|
|
||||||
borderRadius: 8,
|
|
||||||
backgroundColor: "#ffe8f1",
|
|
||||||
borderColor: "#ea156c",
|
|
||||||
borderWidth: 1,
|
|
||||||
}}
|
|
||||||
color="#ea156c"
|
|
||||||
label="Assign"
|
|
||||||
labelStyle={{ fontFamily: "Manrope_600SemiBold", fontSize: 14 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{members?.map((member) => (
|
<View row spread>
|
||||||
<Picker.Item
|
<Button
|
||||||
key={member.uid}
|
color="#05a8b6"
|
||||||
label={member?.firstName + " " + member?.lastName}
|
style={styles.topBtn}
|
||||||
value={member?.uid!}
|
label="Cancel"
|
||||||
/>
|
onPress={() => {
|
||||||
))}
|
handleClose();
|
||||||
</Picker>
|
}}
|
||||||
</View>
|
/>
|
||||||
<View row marginL-27 marginT-0>
|
<View marginT-12>
|
||||||
<AssigneesDisplay
|
<DropModalIcon
|
||||||
selectedAttendees={selectedAssignees}
|
onPress={() => {
|
||||||
setSelectedAttendees={setSelectedAssignees}
|
handleClose();
|
||||||
/>
|
}}
|
||||||
</View>
|
/>
|
||||||
<View row centerV style={styles.rotateSwitch}>
|
</View>
|
||||||
<Text style={{ fontFamily: "PlusJakartaSans_500Medium", fontSize: 16 }}>
|
<Button
|
||||||
Take Turns
|
color="#05a8b6"
|
||||||
</Text>
|
style={styles.topBtn}
|
||||||
<Switch
|
label="Save"
|
||||||
onColor={"#ea156c"}
|
onPress={() => {
|
||||||
value={todo.rotate}
|
try {
|
||||||
style={{ width: 43.06, height: 27.13 }}
|
if (addChoreDialogProps.selectedTodo) {
|
||||||
marginL-10
|
updateToDo({
|
||||||
onValueChange={(value) =>
|
...todo,
|
||||||
setTodo((oldValue) => ({ ...oldValue, rotate: value }))
|
points: points,
|
||||||
}
|
assignees: selectedAssignees
|
||||||
/>
|
});
|
||||||
</View>
|
} else {
|
||||||
<View style={styles.divider} />
|
addToDo({
|
||||||
<View marginH-30 marginB-15 row centerV>
|
...todo,
|
||||||
<Ionicons name="gift-outline" size={25} color="#919191" />
|
done: false,
|
||||||
<Text style={styles.sub} marginL-10>
|
points: points,
|
||||||
Reward Points
|
assignees: selectedAssignees,
|
||||||
</Text>
|
repeatDays: todo.repeatDays ?? []
|
||||||
</View>
|
});
|
||||||
<PointsSlider
|
}
|
||||||
points={points}
|
handleClose();
|
||||||
setPoints={setPoints}
|
} catch (error) {
|
||||||
handleChange={handleChange}
|
console.error(error);
|
||||||
/>
|
}
|
||||||
</Dialog>
|
}}
|
||||||
);
|
/>
|
||||||
|
</View>
|
||||||
|
<KeyboardAvoidingView>
|
||||||
|
<TextField
|
||||||
|
placeholder="Add a To Do"
|
||||||
|
value={todo?.title}
|
||||||
|
onChangeText={(text) => {
|
||||||
|
setTodo((oldValue: IToDo) => ({...oldValue, title: text}));
|
||||||
|
}}
|
||||||
|
placeholderTextColor="#2d2d30"
|
||||||
|
text60R
|
||||||
|
marginT-15
|
||||||
|
marginL-30
|
||||||
|
ref={titleRef}
|
||||||
|
/>
|
||||||
|
<View style={styles.divider} marginT-8/>
|
||||||
|
<View marginL-30 centerV>
|
||||||
|
<View row marginB-10>
|
||||||
|
{todo?.date && (
|
||||||
|
<View row centerV>
|
||||||
|
<CalendarIcon color="#919191" width={24} height={24}/>
|
||||||
|
<DateTimePicker
|
||||||
|
value={todo.date}
|
||||||
|
text70
|
||||||
|
style={{
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
marginL-12
|
||||||
|
onChange={(date) => {
|
||||||
|
setTodo((oldValue: IToDo) => ({...oldValue, date: date}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View row centerV>
|
||||||
|
<ClockOIcon/>
|
||||||
|
<Picker
|
||||||
|
marginL-12
|
||||||
|
placeholder="Select Repeat Type"
|
||||||
|
value={todo?.repeatType}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (value) {
|
||||||
|
if (value.toString() == "None") {
|
||||||
|
setTodo((oldValue) => ({
|
||||||
|
...oldValue,
|
||||||
|
date: null,
|
||||||
|
repeatType: "None",
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setTodo((oldValue) => ({
|
||||||
|
...oldValue,
|
||||||
|
date: new Date(),
|
||||||
|
repeatType: value.toString(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
topBarProps={{title: "Repeat"}}
|
||||||
|
style={{
|
||||||
|
marginVertical: 5,
|
||||||
|
fontFamily: "PlusJakartaSans_500Medium",
|
||||||
|
fontSize: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{repeatOptions.map((option) => (
|
||||||
|
<Picker.Item
|
||||||
|
key={option.value}
|
||||||
|
label={option.label}
|
||||||
|
value={option.value}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
{todo.repeatType == "Every week" && <RepeatFreq handleRepeatDaysChange={handleRepeatDaysChange}
|
||||||
|
repeatDays={todo.repeatDays ?? []}/>}
|
||||||
|
</View>
|
||||||
|
<View style={styles.divider}/>
|
||||||
|
|
||||||
|
<View marginH-30 marginB-10 row centerV>
|
||||||
|
<ProfileIcon color="#919191"/>
|
||||||
|
<Text style={styles.sub} marginL-10>
|
||||||
|
Assignees
|
||||||
|
</Text>
|
||||||
|
<View flex-1/>
|
||||||
|
<Picker
|
||||||
|
marginL-8
|
||||||
|
value={selectedAssignees}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSelectedAssignees([...selectedAssignees, ...value]);
|
||||||
|
}}
|
||||||
|
style={{marginVertical: 5}}
|
||||||
|
mode={PickerModes.MULTI}
|
||||||
|
renderInput={() => (
|
||||||
|
<Button
|
||||||
|
size={ButtonSize.small}
|
||||||
|
paddingH-8
|
||||||
|
iconSource={() => (
|
||||||
|
<Ionicons name="add-outline" size={20} color="#ea156c"/>
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
marginLeft: "auto",
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: "#ffe8f1",
|
||||||
|
borderColor: "#ea156c",
|
||||||
|
borderWidth: 1,
|
||||||
|
}}
|
||||||
|
color="#ea156c"
|
||||||
|
label="Assign"
|
||||||
|
labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{members?.map((member) => (
|
||||||
|
<Picker.Item
|
||||||
|
key={member.uid}
|
||||||
|
label={member?.firstName + " " + member?.lastName}
|
||||||
|
value={member?.uid!}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Picker>
|
||||||
|
</View>
|
||||||
|
<View row marginL-27 marginT-0>
|
||||||
|
<AssigneesDisplay
|
||||||
|
selectedAttendees={selectedAssignees}
|
||||||
|
setSelectedAttendees={setSelectedAssignees}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View row centerV style={styles.rotateSwitch}>
|
||||||
|
<Text style={{fontFamily: "PlusJakartaSans_500Medium", fontSize: 16}}>
|
||||||
|
Take Turns
|
||||||
|
</Text>
|
||||||
|
<Switch
|
||||||
|
onColor={"#ea156c"}
|
||||||
|
value={todo.rotate}
|
||||||
|
style={{width: 43.06, height: 27.13}}
|
||||||
|
marginL-10
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setTodo((oldValue) => ({...oldValue, rotate: value}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.divider}/>
|
||||||
|
<View marginH-30 marginB-15 row centerV>
|
||||||
|
<Ionicons name="gift-outline" size={25} color="#919191"/>
|
||||||
|
<Text style={styles.sub} marginL-10>
|
||||||
|
Reward Points
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<PointsSlider
|
||||||
|
points={points}
|
||||||
|
setPoints={setPoints}
|
||||||
|
handleChange={handleChange}
|
||||||
|
/>
|
||||||
|
</KeyboardAvoidingView>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddChoreDialog;
|
export default AddChoreDialog;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
divider: { height: 1, backgroundColor: "#e4e4e4", marginVertical: 15 },
|
divider: {height: 1, backgroundColor: "#e4e4e4", marginVertical: 15},
|
||||||
gradient: {
|
gradient: {
|
||||||
height: "25%",
|
height: "25%",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: 25,
|
bottom: 25,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: "rgb(253, 23, 117)",
|
backgroundColor: "rgb(253, 23, 117)",
|
||||||
paddingVertical: 20,
|
paddingVertical: 20,
|
||||||
},
|
},
|
||||||
topBtn: {
|
topBtn: {
|
||||||
backgroundColor: "white",
|
backgroundColor: "white",
|
||||||
color: "#05a8b6",
|
color: "#05a8b6",
|
||||||
},
|
},
|
||||||
rotateSwitch: {
|
rotateSwitch: {
|
||||||
marginLeft: 35,
|
marginLeft: 35,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
marginTop: 25,
|
marginTop: 25,
|
||||||
},
|
},
|
||||||
sub: {
|
sub: {
|
||||||
fontFamily: "Manrope_600SemiBold",
|
fontFamily: "Manrope_600SemiBold",
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,19 +1,27 @@
|
|||||||
import { useQuery } from "react-query";
|
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 { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||||
import {IToDo} from "@/hooks/firebase/types/todoData";
|
import { IToDo } from "@/hooks/firebase/types/todoData";
|
||||||
|
|
||||||
export const useGetTodos = () => {
|
export const useGetTodos = () => {
|
||||||
const { user, profileData } = useAuthContext();
|
const { user, profileData } = useAuthContext();
|
||||||
//TODO: Add role based filtering for todos
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["todos", user?.uid],
|
queryKey: ["todos", user?.uid],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const snapshot = await firestore()
|
|
||||||
.collection("Todos")
|
let snapshot;
|
||||||
.where("familyId", "==", profileData?.familyId)
|
if (profileData?.userType === ProfileType.PARENT) {
|
||||||
.get();
|
snapshot = await firestore()
|
||||||
|
.collection("Todos")
|
||||||
|
.where("familyId", "==", profileData?.familyId)
|
||||||
|
.get();
|
||||||
|
} else {
|
||||||
|
snapshot = await firestore()
|
||||||
|
.collection("Todos")
|
||||||
|
.where("assignees", "array-contains", user?.uid)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
return snapshot.docs.map((doc) => {
|
return snapshot.docs.map((doc) => {
|
||||||
const data = doc.data();
|
const data = doc.data();
|
||||||
|
|||||||
@ -127,6 +127,7 @@
|
|||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>SplashScreen</string>
|
<string>SplashScreen</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user