mirror of
https://github.com/urosran/cally.git
synced 2025-07-17 02:25:10 +00:00
ui fixes
This commit is contained in:
19
assets/svgs/PlusIcon.tsx
Normal file
19
assets/svgs/PlusIcon.tsx
Normal 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;
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -32,7 +32,6 @@ const ToDosPage = () => {
|
||||
>
|
||||
<View
|
||||
paddingH-25
|
||||
backgroundColor="#f9f8f7"
|
||||
height={"100%"}
|
||||
width={width}
|
||||
>
|
||||
|
Reference in New Issue
Block a user