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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import React, {useEffect, useRef} from "react";
import { GroceryCategory, useGroceryContext } from "@/contexts/GroceryContext";
import { Dropdown } from "react-native-element-dropdown";
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 { AntDesign } from "@expo/vector-icons";
@ -18,10 +18,37 @@ interface IEditGrocery {
handleEditSubmit?: Function;
}
const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
const EditGroceryItem = ({
editGrocery,
onInputFocus,
}: {
editGrocery: IEditGrocery;
onInputFocus: (y: number) => void;
}) => {
const { fuzzyMatchGroceryCategory } = useGroceryContext();
const inputRef = useRef<TextFieldRef>(null);
const containerRef = useRef(null);
const handleFocus = () => {
if (containerRef.current) {
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(
(category) => ({
@ -31,8 +58,8 @@ const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
);
const handleSubmit = () => {
inputRef?.current?.blur()
console.log("CALLLLLL")
inputRef?.current?.blur();
console.log("CALLLLLL");
if (editGrocery.setSubmit) {
editGrocery.setSubmit(true);
}
@ -46,7 +73,7 @@ const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
if (editGrocery.closeEdit) {
editGrocery.closeEdit();
}
}
};
useEffect(() => {
if (inputRef.current) {
@ -58,6 +85,7 @@ const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
return (
<View
ref={containerRef}
style={{
backgroundColor: "white",
width: "100%",
@ -71,6 +99,7 @@ const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
<TextField
text70T
ref={inputRef}
onFocus={handleFocus}
placeholder="Grocery"
value={editGrocery.title}
onSubmitEditing={handleSubmit}
@ -108,12 +137,14 @@ const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
style={{ marginTop: 15 }}
data={groceryCategoryOptions}
placeholder="Select grocery category"
placeholderStyle={{color: "#a2a2a2", fontFamily: "Manrope_500Medium", fontSize: 13.2}}
placeholderStyle={{
color: "#a2a2a2",
fontFamily: "Manrope_500Medium",
fontSize: 13.2,
}}
labelField="label"
valueField="value"
value={
editGrocery.category
}
value={editGrocery.category}
iconColor="white"
activeColor={"#fd1775"}
containerStyle={styles.dropdownStyle}
@ -121,8 +152,14 @@ const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
itemContainerStyle={styles.itemStyle}
selectedTextStyle={styles.selectedText}
renderLeftIcon={() => (
<DropdownIcon style={{marginRight: 8}}
color={editGrocery.category == GroceryCategory.None ? "#7b7b7b" : "#fd1775"}/>
<DropdownIcon
style={{ marginRight: 8 }}
color={
editGrocery.category == GroceryCategory.None
? "#7b7b7b"
: "#fd1775"
}
/>
)}
renderItem={(item) => {
return (

View File

@ -13,9 +13,11 @@ import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
const GroceryItem = ({
item,
handleItemApproved,
onInputFocus,
}: {
item: IGrocery;
handleItemApproved: (id: string, changes: Partial<IGrocery>) => void;
onInputFocus: (y: number) => void;
}) => {
const { updateGroceryItem } = useGroceryContext();
const { profileData } = useAuthContext();
@ -24,12 +26,14 @@ const GroceryItem = ({
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
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 closeEdit = () => {
setIsEditingTitle(false);
}
};
const handleTitleChange = (newTitle: string) => {
updateGroceryItem({ id: item?.id, title: newTitle });
@ -82,7 +86,7 @@ const GroceryItem = ({
setOpenFreqEdit(false);
}}
/>
{isEditingTitle ?
{isEditingTitle ? (
<EditGroceryItem
editGrocery={{
id: item.id,
@ -93,23 +97,26 @@ const GroceryItem = ({
closeEdit: closeEdit,
handleEditSubmit: updateGroceryItem,
}}
/> :
onInputFocus={onInputFocus}
/>
) : (
<View>
{isParent ?
{isParent ? (
<TouchableOpacity onPress={() => setIsEditingTitle(true)}>
<Text text70T black style={styles.title}>
{item.title}
</Text>
</TouchableOpacity> :
</TouchableOpacity>
) : (
<Text text70T black style={styles.title}>
{item.title}
</Text>
}
)}
</View>
}
)}
{!item.approved ? (
<View row centerV marginB-10>
{isParent &&
{isParent && (
<>
<AntDesign
name="check"
@ -118,18 +125,28 @@ const GroceryItem = ({
color: "green",
marginRight: 15,
}}
onPress={isParent ? () => handleItemApproved(item.id, { approved: true }) : null}
onPress={
isParent
? () => handleItemApproved(item.id, { approved: true })
: null
}
/>
<AntDesign
name="close"
size={24}
style={{ color: "red" }}
onPress={isParent ? () => handleItemApproved(item.id, { approved: false }) : null}
onPress={
isParent
? () => handleItemApproved(item.id, { approved: false })
: null
}
/>
</>}
</>
)}
</View>
) : (
!isEditingTitle && isParent && (
!isEditingTitle &&
isParent && (
<Checkbox
value={item.bought}
containerStyle={[styles.checkbox, { borderRadius: 50 }]}
@ -150,7 +167,8 @@ const GroceryItem = ({
<View height={0.7} backgroundColor="#e7e7e7" width={"98%"} />
</View>
<View paddingL-0 paddingT-12 flexS row centerV>
{profileData?.pfp ? <ImageBackground
{profileData?.pfp ? (
<ImageBackground
source={require("../../../assets/images/child-picture.png")}
style={{
height: 24.64,
@ -158,13 +176,14 @@ const GroceryItem = ({
borderRadius: 22,
overflow: "hidden",
}}
/> :
/>
) : (
<View
style={{
position: "relative",
width: 24.64,
aspectRatio: 1,
marginRight: 4
marginRight: 4,
}}
>
<View
@ -184,10 +203,13 @@ const GroceryItem = ({
fontWeight: "bold",
}}
>
{itemCreator ? getInitials(itemCreator.firstName, itemCreator.lastName) : ""}
{itemCreator
? getInitials(itemCreator.firstName, itemCreator.lastName)
: ""}
</Text>
</View>
</View>}
</View>
)}
<Text color="#858585" style={styles.authorTxt}>
Requested by {itemCreator?.firstName}
</Text>

View File

@ -10,7 +10,7 @@ import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
import {IGrocery} from "@/hooks/firebase/types/groceryData";
import AddPersonIcon from "@/assets/svgs/AddPersonIcon";
const GroceryList = () => {
const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
const {
groceries,
updateGroceryItem,
@ -169,6 +169,7 @@ const GroceryList = () => {
handleItemApproved={(id, changes) =>
updateGroceryItem({...changes, id: id})
}
onInputFocus={onInputFocus}
/>
)}
keyExtractor={(item) => item.id.toString()}
@ -230,6 +231,7 @@ const GroceryList = () => {
setSubmit: setSubmitted,
closeEdit: () => setIsAddingGrocery(false)
}}
onInputFocus={onInputFocus}
/>
</View>
)}
@ -254,6 +256,7 @@ const GroceryList = () => {
handleItemApproved={(id, changes) =>
updateGroceryItem({...changes, id: id})
}
onInputFocus={onInputFocus}
/>
)
)}

View File

@ -1,41 +1,78 @@
import { Dimensions, ScrollView } from "react-native";
import { Dimensions, ScrollView, Keyboard, Platform } from "react-native";
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 GroceryList from "./GroceryList";
import { useGroceryContext } from "@/contexts/GroceryContext";
const GroceryWrapper = () => {
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(() => {
if (isAddingGrocery && scrollViewRef.current) {
scrollViewRef.current.scrollTo({
y: 400, // Adjust this value to scroll a bit down (100 is an example)
y: 400,
animated: true,
});
}
}, [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 (
<View height={Dimensions.get("window").height}>
<View>
<>
<ScrollView
ref={scrollViewRef}
automaticallyAdjustKeyboardInsets={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
>
<View height={"100%"}>
<View marginB-115>
<GroceryList />
</View>
<View marginB-60>
<GroceryList onInputFocus={handleInputFocus} />
</View>
</ScrollView>
</View>
{!isAddingGrocery && <AddGroceryItem />}
</View>
</>
);
};

View File

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

View File

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

View File

@ -14,8 +14,10 @@ import { IToDo } from "@/hooks/firebase/types/todoData";
const groupToDosByDate = (toDos: IToDo[]) => {
let sortedTodos = toDos.sort((a, b) => a.date - b.date);
return sortedTodos.reduce((groups, toDo) => {
return sortedTodos.reduce(
(groups, toDo) => {
let dateKey;
let subDateKey;
const isNext7Days = (date: Date) => {
const today = new Date();
@ -24,7 +26,10 @@ const groupToDosByDate = (toDos: IToDo[]) => {
const isNext30Days = (date: 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) {
@ -37,17 +42,36 @@ const groupToDosByDate = (toDos: IToDo[]) => {
dateKey = "Next 7 Days";
} else if (isNext30Days(toDo.date)) {
dateKey = "Next 30 Days";
subDateKey = format(toDo.date, "MMM d");
} else {
dateKey = format(toDo.date, "EEE MMM dd");
return groups;
}
if (!groups[dateKey]) {
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);
}
groups[dateKey].push(toDo);
return groups;
}, {} as { [key: string]: IToDo[] });
},
{} as {
[key: string]: {
items: IToDo[];
subgroups: { [key: string]: IToDo[] };
};
}
);
};
const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
@ -67,59 +91,95 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
}));
};
const noDateToDos = groupedToDos["No Date"] || [];
const noDateToDos = groupedToDos["No Date"]?.items || [];
const datedToDos = Object.keys(groupedToDos).filter(
(key) => key !== "No Date"
);
const renderTodoGroup = (dateKey: string) => {
const isExpanded = expandedGroups[dateKey] || false;
if (dateKey === "Next 30 Days") {
const subgroups = Object.entries(groupedToDos[dateKey].subgroups).sort(
([dateA], [dateB]) => {
const dateAObj = new Date(dateA);
const dateBObj = new Date(dateB);
return dateAObj.getTime() - dateBObj.getTime();
}
);
return (
<View marginB-402>
{noDateToDos.length > 0 && (
<View key="No Date">
<View row spread paddingH-19 marginB-12>
<Text
text70
<View key={dateKey}>
<TouchableOpacity
onPress={() => toggleExpand(dateKey)}
style={{
fontWeight: "bold",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 0,
marginBottom: 4,
marginTop: 15,
}}
>
Unscheduled
<Text
style={{
fontFamily: "Manrope_700Bold",
fontSize: 15,
color: "#484848",
}}
>
{dateKey}
</Text>
{!expandNoDate && (
<AntDesign
name="caretright"
name={isExpanded ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
onPress={() => {
setExpandNoDate(!expandNoDate);
}}
/>
)}
{expandNoDate && (
<AntDesign
name="caretdown"
size={24}
color="#fd1575"
onPress={() => {
setExpandNoDate(!expandNoDate);
</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>
{expandNoDate &&
noDateToDos
.sort((a, b) => Number(a.done) - Number(b.done))
.map((item) => (
<ToDoItem isSettings={isSettings} key={item.id} item={item} />
{sortedItems.map((item) => (
<ToDoItem
isSettings={isSettings}
key={item.id}
item={item}
is7Days={false}
/>
))}
</View>
)}
);
})}
</View>
);
}
{datedToDos.map((dateKey) => {
const isExpanded = expandedGroups[dateKey] || false;
const sortedToDos = [
...groupedToDos[dateKey].filter((toDo) => !toDo.done),
...groupedToDos[dateKey].filter((toDo) => toDo.done),
...groupedToDos[dateKey].items.filter((toDo) => !toDo.done),
...groupedToDos[dateKey].items.filter((toDo) => toDo.done),
];
return (
@ -144,21 +204,58 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
>
{dateKey}
</Text>
{!isExpanded && (
<AntDesign name="caretright" size={24} color="#fd1575" />
)}
{isExpanded && (
<AntDesign name="caretdown" size={24} color="#fd1575" />
)}
<AntDesign
name={isExpanded ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
/>
</TouchableOpacity>
{isExpanded &&
sortedToDos.map((item) => (
<ToDoItem isSettings={isSettings} key={item.id} item={item} />
<ToDoItem
isSettings={isSettings}
key={item.id}
item={item}
is7Days={dateKey === "Next 7 Days"}
/>
))}
</View>
);
})}
};
return (
<View marginB-402>
{noDateToDos.length > 0 && (
<View key="No Date">
<View row spread paddingH-19 marginB-12>
<Text
text70
style={{
fontWeight: "bold",
}}
>
Unscheduled
</Text>
<AntDesign
name={expandNoDate ? "caretdown" : "caretright"}
size={24}
color="#fd1575"
onPress={() => {
setExpandNoDate(!expandNoDate);
}}
/>
</View>
{expandNoDate &&
noDateToDos
.sort((a, b) => Number(a.done) - Number(b.done))
.map((item) => (
<ToDoItem isSettings={isSettings} key={item.id} item={item} />
))}
</View>
)}
{datedToDos.map(renderTodoGroup)}
</View>
);
};

View File

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