This commit is contained in:
ivic00
2024-10-30 23:44:37 +01:00
parent 01338e7c70
commit 1417ec8f9b
12 changed files with 585 additions and 351 deletions

19
assets/svgs/PlusIcon.tsx Normal file
View File

@ -0,0 +1,19 @@
import * as React from "react";
import Svg, { SvgProps, Path } from "react-native-svg";
const PlusIcon = (props: SvgProps) => (
<Svg
width={props.width || 14}
height={props.height || 15}
fill="none"
{...props}
>
<Path
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M1 7.632h12m-6-6v12"
/>
</Svg>
);
export default PlusIcon;

View File

@ -6,6 +6,7 @@ import HeaderTemplate from "@/components/shared/HeaderTemplate";
import {Feather, MaterialIcons} from "@expo/vector-icons"; import {Feather, MaterialIcons} from "@expo/vector-icons";
import AddBrainDump from "./AddBrainDump"; import AddBrainDump from "./AddBrainDump";
import LinearGradient from "react-native-linear-gradient"; import LinearGradient from "react-native-linear-gradient";
import PlusIcon from "@/assets/svgs/PlusIcon";
const BrainDumpPage = () => { const BrainDumpPage = () => {
const [searchText, setSearchText] = useState<string>(""); const [searchText, setSearchText] = useState<string>("");
@ -85,10 +86,10 @@ const BrainDumpPage = () => {
}} }}
> >
<View row centerV centerH> <View row centerV centerH>
<MaterialIcons name="add" size={22} color={"white"}/> <PlusIcon />
<Text <Text
white white
style={{fontSize: 16, fontFamily: "Manrope_600SemiBold"}} style={{fontSize: 16, fontFamily: "Manrope_600SemiBold", marginLeft: 5}}
> >
New New
</Text> </Text>

View File

@ -10,6 +10,7 @@ import CalendarIcon from "@/assets/svgs/CalendarIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon"; import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import {useSetAtom} from "jotai"; import {useSetAtom} from "jotai";
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms"; import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
import PlusIcon from "@/assets/svgs/PlusIcon";
export const AddEventDialog = () => { export const AddEventDialog = () => {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
@ -50,8 +51,8 @@ export const AddEventDialog = () => {
onPress={() => setShow(true)} onPress={() => setShow(true)}
> >
<View row centerV centerH> <View row centerV centerH>
<MaterialIcons name="add" size={22} color={"white"}/> <PlusIcon />
<Text white style={{fontSize: 16, fontFamily: 'Manrope_600SemiBold'}}> <Text white style={{fontSize: 16, fontFamily: 'Manrope_600SemiBold', marginLeft: 5}}>
New New
</Text> </Text>
</View> </View>

View File

@ -3,6 +3,7 @@ import React from "react";
import {Button, View,} from "react-native-ui-lib"; import {Button, View,} from "react-native-ui-lib";
import {useGroceryContext} from "@/contexts/GroceryContext"; import {useGroceryContext} from "@/contexts/GroceryContext";
import {FontAwesome6} from "@expo/vector-icons"; import {FontAwesome6} from "@expo/vector-icons";
import PlusIcon from "@/assets/svgs/PlusIcon";
const AddGroceryItem = () => { const AddGroceryItem = () => {
const {setIsAddingGrocery} = useGroceryContext(); const {setIsAddingGrocery} = useGroceryContext();
@ -11,12 +12,12 @@ const AddGroceryItem = () => {
<View <View
row row
spread spread
paddingH-25 paddingH-20
style={{ style={{
position: "absolute", position: "absolute",
bottom: 65, bottom: 15,
width: "100%", width: "100%",
height: 60, height: 53.26,
}} }}
> >
<View style={styles.btnContainer} row> <View style={styles.btnContainer} row>
@ -25,7 +26,7 @@ const AddGroceryItem = () => {
backgroundColor="#fd1775" backgroundColor="#fd1775"
label="Add item" label="Add item"
text70L text70L
iconSource={() => <FontAwesome6 name="add" size={18} color="white"/>} iconSource={() => <PlusIcon />}
style={styles.finishShopBtn} style={styles.finishShopBtn}
labelStyle={styles.addBtnLbl} labelStyle={styles.addBtnLbl}
enableShadow enableShadow
@ -69,6 +70,7 @@ const styles = StyleSheet.create({
}, },
finishShopBtn: { finishShopBtn: {
width: "100%", width: "100%",
height: 53.26,
}, },
shoppingBtn: { shoppingBtn: {
flex: 1, flex: 1,

View File

@ -1,167 +1,204 @@
import {Text, TextField, TextFieldRef, View} from "react-native-ui-lib"; import { Text, TextField, TextFieldRef, View } from "react-native-ui-lib";
import React, {useEffect, useRef} from "react"; import React, { useEffect, useRef } from "react";
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 { findNodeHandle, StyleSheet, UIManager } from "react-native";
import DropdownIcon from "@/assets/svgs/DropdownIcon"; import DropdownIcon from "@/assets/svgs/DropdownIcon";
import {AntDesign} from "@expo/vector-icons"; 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?: () => void; closeEdit?: () => void;
handleEditSubmit?: Function; handleEditSubmit?: Function;
} }
const EditGroceryItem = ({
editGrocery,
onInputFocus,
}: {
editGrocery: IEditGrocery;
onInputFocus: (y: number) => void;
}) => {
const { fuzzyMatchGroceryCategory } = useGroceryContext();
const inputRef = useRef<TextFieldRef>(null);
const containerRef = useRef(null);
const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => { const handleFocus = () => {
const {fuzzyMatchGroceryCategory} = useGroceryContext(); if (containerRef.current) {
const inputRef = useRef<TextFieldRef>(null); const handle = findNodeHandle(containerRef.current);
if (handle) {
UIManager.measure(
handle,
(
x: number,
y: number,
width: number,
height: number,
pageX: number,
pageY: number
) => {
onInputFocus(pageY);
}
);
}
}
};
const groceryCategoryOptions = Object.values(GroceryCategory).map( const groceryCategoryOptions = Object.values(GroceryCategory).map(
(category) => ({ (category) => ({
label: category, label: category,
value: category, value: category,
}) })
); );
const handleSubmit = () => { const handleSubmit = () => {
inputRef?.current?.blur() inputRef?.current?.blur();
console.log("CALLLLLL") console.log("CALLLLLL");
if (editGrocery.setSubmit) { if (editGrocery.setSubmit) {
editGrocery.setSubmit(true); editGrocery.setSubmit(true);
} }
if (editGrocery.handleEditSubmit) { if (editGrocery.handleEditSubmit) {
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) { if (editGrocery.closeEdit) {
editGrocery.closeEdit(); editGrocery.closeEdit();
} }
};
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
} }
useEffect(() => { console.log(editGrocery.category);
if (inputRef.current) { }, []);
inputRef.current.focus();
}
console.log(editGrocery.category); return (
}, []); <View
ref={containerRef}
return ( style={{
<View backgroundColor: "white",
width: "100%",
borderRadius: 25,
paddingHorizontal: 13,
paddingVertical: 10,
marginTop: 0,
}}
>
<View row spread centerV>
<TextField
text70T
ref={inputRef}
onFocus={handleFocus}
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={{ style={{
backgroundColor: "white", color: "green",
width: "100%", marginRight: 15,
borderRadius: 25,
paddingHorizontal: 13,
paddingVertical: 10,
marginTop: 0,
}} }}
> onPress={handleSubmit}
<View row spread centerV> />
<TextField <CloseXIcon
text70T onPress={() => {
ref={inputRef} if (editGrocery.closeEdit) {
placeholder="Grocery" editGrocery.closeEdit();
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> </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;

View File

@ -1,21 +1,23 @@
import {Checkbox, Text, TouchableOpacity, View} from "react-native-ui-lib"; import { Checkbox, Text, TouchableOpacity, View } from "react-native-ui-lib";
import React, {useEffect, useState} from "react"; import React, { useEffect, useState } from "react";
import {AntDesign} from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import {GroceryCategory, useGroceryContext} from "@/contexts/GroceryContext"; import { GroceryCategory, useGroceryContext } from "@/contexts/GroceryContext";
import EditGroceryFrequency from "./EditGroceryFrequency"; import EditGroceryFrequency from "./EditGroceryFrequency";
import EditGroceryItem from "./EditGroceryItem"; import EditGroceryItem from "./EditGroceryItem";
import {ImageBackground, StyleSheet} from "react-native"; import { ImageBackground, StyleSheet } from "react-native";
import {IGrocery} from "@/hooks/firebase/types/groceryData"; import { IGrocery } from "@/hooks/firebase/types/groceryData";
import firestore from "@react-native-firebase/firestore"; import firestore from "@react-native-firebase/firestore";
import {UserProfile} from "@/hooks/firebase/types/profileTypes"; import { UserProfile } from "@/hooks/firebase/types/profileTypes";
import {ProfileType, useAuthContext} from "@/contexts/AuthContext"; import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
const GroceryItem = ({ const GroceryItem = ({
item, item,
handleItemApproved, handleItemApproved,
onInputFocus,
}: { }: {
item: IGrocery; item: IGrocery;
handleItemApproved: (id: string, changes: Partial<IGrocery>) => void; handleItemApproved: (id: string, changes: Partial<IGrocery>) => void;
onInputFocus: (y: number) => void;
}) => { }) => {
const { updateGroceryItem } = useGroceryContext(); const { updateGroceryItem } = useGroceryContext();
const { profileData } = useAuthContext(); const { profileData } = useAuthContext();
@ -24,12 +26,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>(item.title ?? ""); const [newTitle, setNewTitle] = useState<string>(item.title ?? "");
const [category, setCategory] = useState<GroceryCategory>(item.category ?? GroceryCategory.None); const [category, setCategory] = useState<GroceryCategory>(
item.category ?? GroceryCategory.None
);
const [itemCreator, setItemCreator] = useState<UserProfile>(null); const [itemCreator, setItemCreator] = useState<UserProfile>(null);
const closeEdit = () => { const closeEdit = () => {
setIsEditingTitle(false); setIsEditingTitle(false);
} };
const handleTitleChange = (newTitle: string) => { const handleTitleChange = (newTitle: string) => {
updateGroceryItem({ id: item?.id, title: newTitle }); updateGroceryItem({ id: item?.id, title: newTitle });
@ -82,57 +86,70 @@ const GroceryItem = ({
setOpenFreqEdit(false); setOpenFreqEdit(false);
}} }}
/> />
{isEditingTitle ? {isEditingTitle ? (
<EditGroceryItem <EditGroceryItem
editGrocery={{ editGrocery={{
id: item.id, id: item.id,
title: newTitle, title: newTitle,
category: category, category: category,
setTitle: setNewTitle, setTitle: setNewTitle,
setCategory: setCategory, setCategory: setCategory,
closeEdit: closeEdit, closeEdit: closeEdit,
handleEditSubmit: updateGroceryItem, handleEditSubmit: updateGroceryItem,
}} }}
/> : onInputFocus={onInputFocus}
<View> />
{isParent ? ) : (
<TouchableOpacity onPress={() => setIsEditingTitle(true)}> <View>
<Text text70T black style={styles.title}> {isParent ? (
{item.title} <TouchableOpacity onPress={() => setIsEditingTitle(true)}>
</Text>
</TouchableOpacity> :
<Text text70T black style={styles.title}> <Text text70T black style={styles.title}>
{item.title} {item.title}
</Text> </Text>
} </TouchableOpacity>
</View> ) : (
} <Text text70T black style={styles.title}>
{item.title}
</Text>
)}
</View>
)}
{!item.approved ? ( {!item.approved ? (
<View row centerV marginB-10> <View row centerV marginB-10>
{isParent && {isParent && (
<> <>
<AntDesign <AntDesign
name="check" name="check"
size={24} size={24}
style={{ style={{
color: "green", color: "green",
marginRight: 15, marginRight: 15,
}} }}
onPress={isParent ? () => handleItemApproved(item.id, { approved: true }) : null} onPress={
/> isParent
<AntDesign ? () => handleItemApproved(item.id, { approved: true })
name="close" : null
size={24} }
style={{ color: "red" }} />
onPress={isParent ? () => handleItemApproved(item.id, { approved: false }) : null} <AntDesign
/> name="close"
</>} size={24}
style={{ color: "red" }}
onPress={
isParent
? () => handleItemApproved(item.id, { approved: false })
: null
}
/>
</>
)}
</View> </View>
) : ( ) : (
!isEditingTitle && isParent && ( !isEditingTitle &&
isParent && (
<Checkbox <Checkbox
value={item.bought} value={item.bought}
containerStyle={[styles.checkbox, {borderRadius: 50}]} containerStyle={[styles.checkbox, { borderRadius: 50 }]}
style={styles.checked} style={styles.checked}
borderRadius={50} borderRadius={50}
color="#fd1575" color="#fd1575"
@ -150,7 +167,8 @@ const GroceryItem = ({
<View height={0.7} backgroundColor="#e7e7e7" width={"98%"} /> <View height={0.7} backgroundColor="#e7e7e7" width={"98%"} />
</View> </View>
<View paddingL-0 paddingT-12 flexS row centerV> <View paddingL-0 paddingT-12 flexS row centerV>
{profileData?.pfp ? <ImageBackground {profileData?.pfp ? (
<ImageBackground
source={require("../../../assets/images/child-picture.png")} source={require("../../../assets/images/child-picture.png")}
style={{ style={{
height: 24.64, height: 24.64,
@ -158,16 +176,17 @@ 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",
justifyContent: "center", justifyContent: "center",
@ -176,18 +195,21 @@ const GroceryItem = ({
width: "100%", width: "100%",
height: "100%", height: "100%",
}} }}
>
<Text
style={{
color: "#fff",
fontSize: 12,
fontWeight: "bold",
}}
> >
{itemCreator ? getInitials(itemCreator.firstName, itemCreator.lastName) : ""} <Text
</Text> style={{
color: "#fff",
fontSize: 12,
fontWeight: "bold",
}}
>
{itemCreator
? getInitials(itemCreator.firstName, itemCreator.lastName)
: ""}
</Text>
</View>
</View> </View>
</View>} )}
<Text color="#858585" style={styles.authorTxt}> <Text color="#858585" style={styles.authorTxt}>
Requested by {itemCreator?.firstName} Requested by {itemCreator?.firstName}
</Text> </Text>

View File

@ -10,7 +10,7 @@ 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 = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
const { const {
groceries, groceries,
updateGroceryItem, updateGroceryItem,
@ -169,6 +169,7 @@ const GroceryList = () => {
handleItemApproved={(id, changes) => handleItemApproved={(id, changes) =>
updateGroceryItem({...changes, id: id}) updateGroceryItem({...changes, id: id})
} }
onInputFocus={onInputFocus}
/> />
)} )}
keyExtractor={(item) => item.id.toString()} keyExtractor={(item) => item.id.toString()}
@ -230,6 +231,7 @@ const GroceryList = () => {
setSubmit: setSubmitted, setSubmit: setSubmitted,
closeEdit: () => setIsAddingGrocery(false) closeEdit: () => setIsAddingGrocery(false)
}} }}
onInputFocus={onInputFocus}
/> />
</View> </View>
)} )}
@ -254,6 +256,7 @@ const GroceryList = () => {
handleItemApproved={(id, changes) => handleItemApproved={(id, changes) =>
updateGroceryItem({...changes, id: id}) updateGroceryItem({...changes, id: id})
} }
onInputFocus={onInputFocus}
/> />
) )
)} )}

View File

@ -1,42 +1,79 @@
import { Dimensions, ScrollView } from "react-native"; import { Dimensions, ScrollView, Keyboard, Platform } 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, useState } 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);
const [keyboardHeight, setKeyboardHeight] = useState(0);
useEffect(() => {
const keyboardWillShowListener = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
(e) => {
setKeyboardHeight(e.endCoordinates.height);
}
);
const keyboardWillHideListener = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
() => {
setKeyboardHeight(0);
}
);
return () => {
keyboardWillShowListener.remove();
keyboardWillHideListener.remove();
};
}, []);
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,
animated: true, animated: true,
}); });
} }
}, [isAddingGrocery]); }, [isAddingGrocery]);
const handleInputFocus = (y: number) => {
if (scrollViewRef.current) {
// Get the window height
const windowHeight = Dimensions.get('window').height;
// Calculate the space we want to leave at the top
const topSpacing = 20;
// Calculate the target scroll position:
// y (position of input) - topSpacing (space we want at top)
// if keyboard is shown, we need to account for its height
const scrollPosition = Math.max(0, y - topSpacing);
scrollViewRef.current.scrollTo({
y: scrollPosition,
animated: true,
});
}
};
return ( return (
<View height={Dimensions.get("window").height}> <>
<View> <ScrollView
<ScrollView ref={scrollViewRef}
ref={scrollViewRef} automaticallyAdjustKeyboardInsets={true}
automaticallyAdjustKeyboardInsets={true} showsHorizontalScrollIndicator={false}
showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false}
showsVerticalScrollIndicator={false} >
> <View marginB-60>
<View height={"100%"}> <GroceryList onInputFocus={handleInputFocus} />
<View marginB-115> </View>
<GroceryList /> </ScrollView>
</View>
</View>
</ScrollView>
</View>
{!isAddingGrocery && <AddGroceryItem />} {!isAddingGrocery && <AddGroceryItem />}
</View> </>
); );
}; };
export default GroceryWrapper; export default GroceryWrapper;

View File

@ -1,38 +1,46 @@
import { StyleSheet } from "react-native"; import { Dimensions, StyleSheet } from "react-native";
import React, { useState } from "react"; import React, { useState } from "react";
import { Button, ButtonSize, Text, View } from "react-native-ui-lib"; import { Button, ButtonSize, Text, View } from "react-native-ui-lib";
import { AntDesign } from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import LinearGradient from "react-native-linear-gradient"; import LinearGradient from "react-native-linear-gradient";
import AddChoreDialog from "./AddChoreDialog"; import AddChoreDialog from "./AddChoreDialog";
import PlusIcon from "@/assets/svgs/PlusIcon";
const AddChore = () => { const AddChore = () => {
const [isVisible, setIsVisible] = useState<boolean>(false); const [isVisible, setIsVisible] = useState<boolean>(false);
return ( return (
<LinearGradient <View
colors={["#f9f8f700", "#f9f8f7", "#f9f8f700"]} row
locations={[0, 0.5, 1]} spread
style={styles.gradient} paddingH-20
style={{
position: "absolute",
bottom: 15,
width: "100%",
}}
> >
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<Button <Button
marginH-25 marginH-20
size={ButtonSize.large} size={ButtonSize.large}
style={styles.button} style={styles.button}
onPress={() => setIsVisible(!isVisible)} onPress={() => setIsVisible(!isVisible)}
> >
<AntDesign name="plus" size={24} color="white" /> <PlusIcon />
<Text <Text
white white
style={{ fontFamily: "Manrope_600SemiBold", fontSize: 15 }} style={{ fontFamily: "Manrope_600SemiBold", fontSize: 15 }}
marginL-10 marginL-5
> >
Create new to do Create new to do
</Text> </Text>
</Button> </Button>
</View> </View>
{isVisible && <AddChoreDialog isVisible={isVisible} setIsVisible={setIsVisible} />} {isVisible && (
</LinearGradient> <AddChoreDialog isVisible={isVisible} setIsVisible={setIsVisible} />
)}
</View>
); );
}; };
@ -53,8 +61,7 @@ const styles = StyleSheet.create({
}, },
button: { button: {
backgroundColor: "rgb(253, 23, 117)", backgroundColor: "rgb(253, 23, 117)",
paddingVertical: 15, height: 53.26,
paddingHorizontal: 30,
borderRadius: 30, borderRadius: 30,
width: 335, width: 335,
}, },

View File

@ -17,8 +17,13 @@ import AddChoreDialog from "@/components/pages/todos/AddChoreDialog";
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers"; import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
import RepeatIcon from "@/assets/svgs/RepeatIcon"; import RepeatIcon from "@/assets/svgs/RepeatIcon";
import { ProfileType, useAuthContext } from "@/contexts/AuthContext"; import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
import { format } from "date-fns";
const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => { const ToDoItem = (props: {
item: IToDo;
isSettings?: boolean;
is7Days?: boolean;
}) => {
const { updateToDo } = useToDosContext(); const { updateToDo } = useToDosContext();
const { data: members } = useGetFamilyMembers(); const { data: members } = useGetFamilyMembers();
const { profileData } = useAuthContext(); const { profileData } = useAuthContext();
@ -55,6 +60,7 @@ const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => {
return ( return (
<View <View
key={props.item.id}
centerV centerV
paddingV-10 paddingV-10
paddingH-13 paddingH-13
@ -154,6 +160,9 @@ const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => {
return props.item.repeatType; return props.item.repeatType;
} }
})()} })()}
{props.item.date &&
props.is7Days &&
" / " + format(props.item.date, "EEEE")}
</Text> </Text>
</View> </View>
)} )}

View File

@ -14,40 +14,64 @@ import { IToDo } from "@/hooks/firebase/types/todoData";
const groupToDosByDate = (toDos: IToDo[]) => { const groupToDosByDate = (toDos: IToDo[]) => {
let sortedTodos = toDos.sort((a, b) => a.date - b.date); let sortedTodos = toDos.sort((a, b) => a.date - b.date);
return sortedTodos.reduce((groups, toDo) => { return sortedTodos.reduce(
let dateKey; (groups, toDo) => {
let dateKey;
let subDateKey;
const isNext7Days = (date: Date) => { const isNext7Days = (date: Date) => {
const today = new Date(); const today = new Date();
return isWithinInterval(date, { start: today, end: addDays(today, 7) }); return isWithinInterval(date, { start: today, end: addDays(today, 7) });
}; };
const isNext30Days = (date: Date) => { const isNext30Days = (date: Date) => {
const today = new Date(); const today = new Date();
return isWithinInterval(date, { start: today, end: addDays(today, 30) }); return isWithinInterval(date, {
}; start: today,
end: addDays(today, 30),
});
};
if (toDo.date === null) { if (toDo.date === null) {
dateKey = "No Date"; dateKey = "No Date";
} else if (isToday(toDo.date)) { } else if (isToday(toDo.date)) {
dateKey = "Today"; dateKey = "Today";
} else if (isTomorrow(toDo.date)) { } else if (isTomorrow(toDo.date)) {
dateKey = "Tomorrow"; dateKey = "Tomorrow";
} else if (isNext7Days(toDo.date)) { } else if (isNext7Days(toDo.date)) {
dateKey = "Next 7 Days"; dateKey = "Next 7 Days";
} else if (isNext30Days(toDo.date)) { } else if (isNext30Days(toDo.date)) {
dateKey = "Next 30 Days"; dateKey = "Next 30 Days";
} else { subDateKey = format(toDo.date, "MMM d");
dateKey = format(toDo.date, "EEE MMM dd"); } else {
return groups;
}
if (!groups[dateKey]) {
groups[dateKey] = {
items: [],
subgroups: {},
};
}
if (dateKey === "Next 30 Days" && subDateKey) {
if (!groups[dateKey].subgroups[subDateKey]) {
groups[dateKey].subgroups[subDateKey] = [];
}
groups[dateKey].subgroups[subDateKey].push(toDo);
} else {
groups[dateKey].items.push(toDo);
}
return groups;
},
{} as {
[key: string]: {
items: IToDo[];
subgroups: { [key: string]: IToDo[] };
};
} }
);
if (!groups[dateKey]) {
groups[dateKey] = [];
}
groups[dateKey].push(toDo);
return groups;
}, {} as { [key: string]: IToDo[] });
}; };
const ToDosList = ({ isSettings }: { isSettings?: boolean }) => { const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
@ -67,11 +91,139 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
})); }));
}; };
const noDateToDos = groupedToDos["No Date"] || []; const noDateToDos = groupedToDos["No Date"]?.items || [];
const datedToDos = Object.keys(groupedToDos).filter( const datedToDos = Object.keys(groupedToDos).filter(
(key) => key !== "No Date" (key) => key !== "No Date"
); );
const renderTodoGroup = (dateKey: string) => {
const isExpanded = expandedGroups[dateKey] || false;
if (dateKey === "Next 30 Days") {
const subgroups = Object.entries(groupedToDos[dateKey].subgroups).sort(
([dateA], [dateB]) => {
const dateAObj = new Date(dateA);
const dateBObj = new Date(dateB);
return dateAObj.getTime() - dateBObj.getTime();
}
);
return (
<View key={dateKey}>
<TouchableOpacity
onPress={() => toggleExpand(dateKey)}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 0,
marginBottom: 4,
marginTop: 15,
}}
>
<Text
style={{
fontFamily: "Manrope_700Bold",
fontSize: 15,
color: "#484848",
}}
>
{dateKey}
</Text>
<AntDesign
name={isExpanded ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
/>
</TouchableOpacity>
{isExpanded &&
subgroups.map(([subDate, items]) => {
const sortedItems = [
...items.filter((toDo) => !toDo.done),
...items.filter((toDo) => toDo.done),
];
return (
<View key={subDate} marginT-15>
<View
style={{
marginBottom: 8,
}}
>
<Text
style={{
fontFamily: "Manrope_600SemiBold",
fontSize: 14,
color: "#919191",
}}
>
{subDate}
</Text>
</View>
{sortedItems.map((item) => (
<ToDoItem
isSettings={isSettings}
key={item.id}
item={item}
is7Days={false}
/>
))}
</View>
);
})}
</View>
);
}
const sortedToDos = [
...groupedToDos[dateKey].items.filter((toDo) => !toDo.done),
...groupedToDos[dateKey].items.filter((toDo) => toDo.done),
];
return (
<View key={dateKey}>
<TouchableOpacity
onPress={() => toggleExpand(dateKey)}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 0,
marginBottom: 4,
marginTop: 15,
}}
>
<Text
style={{
fontFamily: "Manrope_700Bold",
fontSize: 15,
color: "#484848",
}}
>
{dateKey}
</Text>
<AntDesign
name={isExpanded ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
/>
</TouchableOpacity>
{isExpanded &&
sortedToDos.map((item) => (
<ToDoItem
isSettings={isSettings}
key={item.id}
item={item}
is7Days={dateKey === "Next 7 Days"}
/>
))}
</View>
);
};
return ( return (
<View marginB-402> <View marginB-402>
{noDateToDos.length > 0 && ( {noDateToDos.length > 0 && (
@ -85,26 +237,14 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
> >
Unscheduled Unscheduled
</Text> </Text>
{!expandNoDate && ( <AntDesign
<AntDesign name={expandNoDate ? "caretdown" : "caretright"}
name="caretright" size={24}
size={24} color="#fd1575"
color="#fd1575" onPress={() => {
onPress={() => { setExpandNoDate(!expandNoDate);
setExpandNoDate(!expandNoDate); }}
}} />
/>
)}
{expandNoDate && (
<AntDesign
name="caretdown"
size={24}
color="#fd1575"
onPress={() => {
setExpandNoDate(!expandNoDate);
}}
/>
)}
</View> </View>
{expandNoDate && {expandNoDate &&
noDateToDos noDateToDos
@ -115,50 +255,7 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
</View> </View>
)} )}
{datedToDos.map((dateKey) => { {datedToDos.map(renderTodoGroup)}
const isExpanded = expandedGroups[dateKey] || false;
const sortedToDos = [
...groupedToDos[dateKey].filter((toDo) => !toDo.done),
...groupedToDos[dateKey].filter((toDo) => toDo.done),
];
return (
<View key={dateKey}>
<TouchableOpacity
onPress={() => toggleExpand(dateKey)}
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 0,
marginBottom: 4,
marginTop: 15,
}}
>
<Text
style={{
fontFamily: "Manrope_700Bold",
fontSize: 15,
color: "#484848",
}}
>
{dateKey}
</Text>
{!isExpanded && (
<AntDesign name="caretright" size={24} color="#fd1575" />
)}
{isExpanded && (
<AntDesign name="caretdown" size={24} color="#fd1575" />
)}
</TouchableOpacity>
{isExpanded &&
sortedToDos.map((item) => (
<ToDoItem isSettings={isSettings} key={item.id} item={item} />
))}
</View>
);
})}
</View> </View>
); );
}; };

View File

@ -32,7 +32,6 @@ const ToDosPage = () => {
> >
<View <View
paddingH-25 paddingH-25
backgroundColor="#f9f8f7"
height={"100%"} height={"100%"}
width={width} width={width}
> >