mirror of
https://github.com/urosran/cally.git
synced 2025-08-25 21:59:39 +00:00
Merge branch 'dev'
# Conflicts: # app/(auth)/calendar/index.tsx # components/pages/calendar/ManuallyAddEventModal.tsx # components/pages/main/SignUpPage.tsx # package.json
This commit is contained in:
@ -51,7 +51,7 @@ export const AddEventDialog = () => {
|
||||
}}
|
||||
onPress={handleOpenManualInputModal}
|
||||
>
|
||||
<Text style={{color: "white"}}>Manually Input</Text>
|
||||
<Text style={{color: "white"}}>Create New</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -62,7 +62,7 @@ export const AddEventDialog = () => {
|
||||
}}
|
||||
disabled
|
||||
>
|
||||
<Text style={{color: "white"}}>Scan an Image</Text>
|
||||
<Text style={{color: "white"}}>Event</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -73,7 +73,18 @@ export const AddEventDialog = () => {
|
||||
}}
|
||||
disabled
|
||||
>
|
||||
<Text style={{color: "white"}}>Paste in text</Text>
|
||||
<Text style={{color: "white"}}>To Do</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#007bff",
|
||||
opacity: 0.5
|
||||
}}
|
||||
disabled
|
||||
>
|
||||
<Text style={{color: "white"}}>Upload Image</Text>
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
|
@ -1,260 +1,320 @@
|
||||
import {
|
||||
Avatar,
|
||||
Colors,
|
||||
DateTimePicker,
|
||||
LoaderScreen,
|
||||
Modal,
|
||||
Picker,
|
||||
Switch,
|
||||
Text,
|
||||
TextField,
|
||||
View
|
||||
Avatar,
|
||||
Colors,
|
||||
DateTimePicker,
|
||||
LoaderScreen,
|
||||
Modal,
|
||||
Picker,
|
||||
Switch,
|
||||
Text,
|
||||
TextField,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import {ScrollView, TouchableOpacity} from "react-native-gesture-handler";
|
||||
import {useSafeAreaInsets} from "react-native-safe-area-context";
|
||||
import {useState} from "react";
|
||||
import {MaterialIcons} from "@expo/vector-icons";
|
||||
import {PickerMultiValue} from "react-native-ui-lib/src/components/picker/types";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useCreateEvent} from "@/hooks/firebase/useCreateEvent";
|
||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||
import { ScrollView, TouchableOpacity } from "react-native-gesture-handler";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { useState } from "react";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import { PickerMultiValue } from "react-native-ui-lib/src/components/picker/types";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
import { useCreateEvent } from "@/hooks/firebase/useCreateEvent";
|
||||
import { EventData } from "@/hooks/firebase/types/eventData";
|
||||
|
||||
const daysOfWeek = [
|
||||
{label: "Monday", value: "monday"},
|
||||
{label: "Tuesday", value: "tuesday"},
|
||||
{label: "Wednesday", value: "wednesday"},
|
||||
{label: "Thursday", value: "thursday"},
|
||||
{label: "Friday", value: "friday"},
|
||||
{label: "Saturday", value: "saturday"},
|
||||
{label: "Sunday", value: "sunday"},
|
||||
{ label: "Monday", value: "monday" },
|
||||
{ label: "Tuesday", value: "tuesday" },
|
||||
{ label: "Wednesday", value: "wednesday" },
|
||||
{ label: "Thursday", value: "thursday" },
|
||||
{ label: "Friday", value: "friday" },
|
||||
{ label: "Saturday", value: "saturday" },
|
||||
{ label: "Sunday", value: "sunday" },
|
||||
];
|
||||
|
||||
export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolean, close: () => void, initialDate?: Date }) => {
|
||||
const {user} = useAuthContext()
|
||||
const insets = useSafeAreaInsets();
|
||||
export const ManuallyAddEventModal = ({
|
||||
show,
|
||||
close,
|
||||
}: {
|
||||
show: boolean;
|
||||
close: () => void;
|
||||
}) => {
|
||||
const { user } = useAuthContext();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [title, setTitle] = useState<string>("");
|
||||
|
||||
const [isAllDay, setIsAllDay] = useState(false);
|
||||
const [startTime, setStartTime] = useState(initialDate ?? new Date());
|
||||
const [endTime, setEndTime] = useState(new Date())
|
||||
const [isAllDay, setIsAllDay] = useState(false);
|
||||
const [startTime, setStartTime] = useState(initialDate ?? new Date());
|
||||
const [endTime, setEndTime] = useState(new Date());
|
||||
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
const [endDate, setEndDate] = useState(new Date())
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
const [endDate, setEndDate] = useState(new Date());
|
||||
|
||||
const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
|
||||
const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
|
||||
|
||||
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent()
|
||||
const { mutateAsync: createEvent, isLoading, isError } = useCreateEvent();
|
||||
|
||||
const formatDateTime = (date: Date) => {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
const formatDateTime = (date: Date) => {
|
||||
return date.toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const combineDateAndTime = (date: Date, time: Date): Date => {
|
||||
const combined = new Date(date);
|
||||
combined.setHours(time.getHours());
|
||||
combined.setMinutes(time.getMinutes());
|
||||
combined.setSeconds(0);
|
||||
combined.setMilliseconds(0);
|
||||
return combined;
|
||||
};
|
||||
const combineDateAndTime = (date: Date, time: Date): Date => {
|
||||
const combined = new Date(date);
|
||||
combined.setHours(time.getHours());
|
||||
combined.setMinutes(time.getMinutes());
|
||||
combined.setSeconds(0);
|
||||
combined.setMilliseconds(0);
|
||||
return combined;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
let finalStartDate: Date;
|
||||
let finalEndDate: Date;
|
||||
const handleSave = async () => {
|
||||
let finalStartDate: Date;
|
||||
let finalEndDate: Date;
|
||||
|
||||
if (isAllDay) {
|
||||
finalStartDate = new Date(startDate);
|
||||
finalStartDate.setHours(0, 0, 0, 0);
|
||||
if (isAllDay) {
|
||||
finalStartDate = new Date(startDate);
|
||||
finalStartDate.setHours(0, 0, 0, 0);
|
||||
|
||||
finalEndDate = new Date(startDate);
|
||||
finalEndDate.setHours(23, 59, 59, 999);
|
||||
} else {
|
||||
finalStartDate = combineDateAndTime(startDate, startTime);
|
||||
finalEndDate = combineDateAndTime(endDate, endTime);
|
||||
}
|
||||
|
||||
const eventData: Partial<EventData> = {
|
||||
title,
|
||||
startDate: finalStartDate,
|
||||
endDate: finalEndDate,
|
||||
repeatDays: repeatInterval.map(x => x.toString()),
|
||||
allDay: isAllDay
|
||||
};
|
||||
|
||||
await createEvent(eventData)
|
||||
|
||||
close();
|
||||
};
|
||||
|
||||
const getRepeatLabel = () => {
|
||||
const selectedDays = repeatInterval
|
||||
const allDays = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
||||
const workDays = ["monday", "tuesday", "wednesday", "thursday", "friday"];
|
||||
|
||||
const isEveryWorkDay = workDays.every(day => selectedDays.includes(day));
|
||||
|
||||
const isEveryDay = allDays.every(day => selectedDays.includes(day));
|
||||
|
||||
if (isEveryDay) {
|
||||
return "Every day";
|
||||
} else if (isEveryWorkDay && !selectedDays.includes("saturday") && !selectedDays.includes("sunday")) {
|
||||
return "Every work day";
|
||||
} else {
|
||||
return selectedDays
|
||||
.map(item => daysOfWeek.find(day => day.value === item)?.label)
|
||||
.join(", ");
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading && !isError) {
|
||||
return (
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<LoaderScreen message={'Saving event...'} color={Colors.grey40}/>
|
||||
</Modal>
|
||||
)
|
||||
finalEndDate = new Date(startDate);
|
||||
finalEndDate.setHours(23, 59, 59, 999);
|
||||
} else {
|
||||
finalStartDate = combineDateAndTime(startDate, startTime);
|
||||
finalEndDate = combineDateAndTime(endDate, endTime);
|
||||
}
|
||||
|
||||
const eventData: Partial<EventData> = {
|
||||
title,
|
||||
startDate: finalStartDate,
|
||||
endDate: finalEndDate,
|
||||
repeatDays: repeatInterval.map((x) => x.toString()),
|
||||
allDay: isAllDay,
|
||||
};
|
||||
|
||||
await createEvent(eventData);
|
||||
|
||||
close();
|
||||
};
|
||||
|
||||
const getRepeatLabel = () => {
|
||||
const selectedDays = repeatInterval;
|
||||
const allDays = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
];
|
||||
const workDays = ["monday", "tuesday", "wednesday", "thursday", "friday"];
|
||||
|
||||
const isEveryWorkDay = workDays.every((day) => selectedDays.includes(day));
|
||||
|
||||
const isEveryDay = allDays.every((day) => selectedDays.includes(day));
|
||||
|
||||
if (isEveryDay) {
|
||||
return "Every day";
|
||||
} else if (
|
||||
isEveryWorkDay &&
|
||||
!selectedDays.includes("saturday") &&
|
||||
!selectedDays.includes("sunday")
|
||||
) {
|
||||
return "Every work day";
|
||||
} else {
|
||||
return selectedDays
|
||||
.map((item) => daysOfWeek.find((day) => day.value === item)?.label)
|
||||
.join(", ");
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading && !isError) {
|
||||
return (
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<View style={{
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
paddingTop: insets.top, // Safe area inset for top
|
||||
paddingBottom: insets.bottom, // Safe area inset for bottom
|
||||
paddingLeft: insets.left, // Safe area inset for left
|
||||
paddingRight: insets.right, // Safe area inset for right
|
||||
}}>
|
||||
<View style={{flexDirection: "row", justifyContent: "space-between", padding: 16}}>
|
||||
<TouchableOpacity onPress={close}>
|
||||
<Text style={{color: "#007bff"}}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={{fontWeight: "bold", fontSize: 16}}>Add event</Text>
|
||||
<TouchableOpacity onPress={handleSave}>
|
||||
<Text style={{color: "#007bff"}}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={{paddingHorizontal: 16, paddingTop: 10}}>
|
||||
<View style={{marginVertical: 10}}>
|
||||
<TextField
|
||||
placeholder={'Title'}
|
||||
floatingPlaceholder
|
||||
value={title}
|
||||
onChangeText={setTitle}
|
||||
showCharCounter
|
||||
maxLength={200}
|
||||
fieldStyle={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: 'black',
|
||||
borderStyle: 'solid',
|
||||
}}
|
||||
/>
|
||||
|
||||
</View>
|
||||
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20
|
||||
}}>
|
||||
<View style={{flexDirection: "row", alignItems: "center"}}>
|
||||
<MaterialIcons name="schedule" size={24} color="gray"/>
|
||||
<Text style={{marginLeft: 10}}>All-day</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={isAllDay}
|
||||
onValueChange={(value) => setIsAllDay(value)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20
|
||||
}}>
|
||||
<DateTimePicker
|
||||
mode="date"
|
||||
dateFormatter={formatDateTime}
|
||||
value={startDate}
|
||||
onChange={setStartDate}
|
||||
/>
|
||||
{!isAllDay && (
|
||||
<DateTimePicker
|
||||
mode="time"
|
||||
value={startTime}
|
||||
onChange={setStartTime}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{!isAllDay && (
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20
|
||||
}}>
|
||||
<DateTimePicker
|
||||
mode="date"
|
||||
dateFormatter={formatDateTime}
|
||||
value={endDate}
|
||||
onChange={setEndDate}
|
||||
/>
|
||||
<DateTimePicker
|
||||
mode="time"
|
||||
value={endTime}
|
||||
onChange={setEndTime}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={{flexDirection: "row", alignItems: "center", marginBottom: 20}}>
|
||||
<MaterialIcons name="repeat" size={24} color="gray"/>
|
||||
<Picker
|
||||
value={repeatInterval}
|
||||
//@ts-ignore
|
||||
onChange={(items: PickerMultiValue) => setRepeatInterval(items)}
|
||||
placeholder="Doest not repeat"
|
||||
style={{marginLeft: 10, flex: 1}}
|
||||
mode={Picker.modes.MULTI}
|
||||
getLabel={getRepeatLabel}
|
||||
|
||||
>
|
||||
{daysOfWeek.map((option) => (
|
||||
<Picker.Item key={option.value} label={option.label} value={option.value}/>
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View style={{flexDirection: "row", alignItems: "center", marginBottom: 20}}>
|
||||
<MaterialIcons name="person-add" size={24} color="gray"/>
|
||||
<TouchableOpacity style={{marginLeft: 10, flexDirection: "row", alignItems: "center", flex: 1}}>
|
||||
<Avatar size={40} backgroundColor={Colors.yellow10}/>
|
||||
<View style={{marginLeft: 10}}>
|
||||
<Text>Other</Text>
|
||||
<Text style={{color: "gray"}}>{user?.email}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Modal>
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<LoaderScreen message={"Saving event..."} color={Colors.grey40} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={show}
|
||||
animationType="slide"
|
||||
onRequestClose={close}
|
||||
transparent={false}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
paddingTop: insets.top, // Safe area inset for top
|
||||
paddingBottom: insets.bottom, // Safe area inset for bottom
|
||||
paddingLeft: insets.left, // Safe area inset for left
|
||||
paddingRight: insets.right, // Safe area inset for right
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity onPress={close}>
|
||||
<Text style={{ color: "#007bff" }}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<Text style={{ fontWeight: "bold", fontSize: 16 }}>Add event</Text>
|
||||
<TouchableOpacity onPress={handleSave}>
|
||||
<Text style={{ color: "#007bff" }}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={{ paddingHorizontal: 16, paddingTop: 10 }}
|
||||
>
|
||||
<View style={{ marginVertical: 10 }}>
|
||||
<TextField
|
||||
placeholder={"Title"}
|
||||
floatingPlaceholder
|
||||
value={title}
|
||||
onChangeText={setTitle}
|
||||
showCharCounter
|
||||
maxLength={200}
|
||||
fieldStyle={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "black",
|
||||
borderStyle: "solid",
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<MaterialIcons name="schedule" size={24} color="gray" />
|
||||
<Text style={{ marginLeft: 10 }}>All-day</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={isAllDay}
|
||||
onValueChange={(value) => setIsAllDay(value)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<DateTimePicker
|
||||
mode="date"
|
||||
dateFormatter={formatDateTime}
|
||||
value={startDate}
|
||||
onChange={setStartDate}
|
||||
display="default"
|
||||
/>
|
||||
{!isAllDay && (
|
||||
<DateTimePicker
|
||||
mode="time"
|
||||
value={startTime}
|
||||
onChange={setStartTime}
|
||||
display="spinner"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{!isAllDay && (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<DateTimePicker
|
||||
mode="date"
|
||||
dateFormatter={formatDateTime}
|
||||
value={endDate}
|
||||
onChange={setEndDate}
|
||||
display="default"
|
||||
/>
|
||||
<DateTimePicker
|
||||
mode="time"
|
||||
value={endTime}
|
||||
onChange={setEndTime}
|
||||
minuteInterval={1}
|
||||
display="spinner"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="repeat" size={24} color="gray" />
|
||||
<Picker
|
||||
value={repeatInterval}
|
||||
//@ts-ignore
|
||||
onChange={(items: PickerMultiValue) => setRepeatInterval(items)}
|
||||
placeholder="Doest not repeat"
|
||||
style={{ marginLeft: 10, flex: 1 }}
|
||||
mode={Picker.modes.MULTI}
|
||||
getLabel={getRepeatLabel}
|
||||
>
|
||||
{daysOfWeek.map((option) => (
|
||||
<Picker.Item
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
value={option.value}
|
||||
/>
|
||||
))}
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 20,
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="person-add" size={24} color="gray" />
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
marginLeft: 10,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<Avatar size={40} backgroundColor={Colors.yellow10} />
|
||||
<View style={{ marginLeft: 10 }}>
|
||||
<Text>Other</Text>
|
||||
<Text style={{ color: "gray" }}>{user?.email}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -1,28 +1,60 @@
|
||||
import { View, Text } from 'react-native'
|
||||
import React from 'react'
|
||||
import { TextField } from 'react-native-ui-lib'
|
||||
import CategoryDropdown from './CategoryDropdown'
|
||||
import { GroceryCategory } from '@/contexts/GroceryContext'
|
||||
import { View, Text } from "react-native";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { TextField } from "react-native-ui-lib";
|
||||
import {
|
||||
GroceryCategory,
|
||||
IGrocery,
|
||||
useGroceryContext,
|
||||
} from "@/contexts/GroceryContext";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
||||
const EditGroceryItem = (props: {title: string, setTitle: (value: string) => void, setCategory: (category: GroceryCategory) => void}) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 25,
|
||||
padding: 15,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
placeholder="Grocery"
|
||||
value={props.title}
|
||||
onChangeText={(value) => props.setTitle(value)}
|
||||
maxLength={25}
|
||||
/>
|
||||
<CategoryDropdown setSelectedCategory={props.setCategory} />
|
||||
</View>
|
||||
)
|
||||
interface IEditGrocery {
|
||||
id?: number;
|
||||
title: string;
|
||||
setTitle: (value: string) => void;
|
||||
setCategory?: (category: GroceryCategory) => void;
|
||||
category: GroceryCategory;
|
||||
setSubmit?: (value: boolean) => void;
|
||||
updateCategory?: (id: number, changes: Partial<IGrocery>) => void;
|
||||
closeEdit?: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export default EditGroceryItem
|
||||
const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
|
||||
const { fuzzyMatchGroceryCategory } = useGroceryContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (editGrocery.setCategory)
|
||||
editGrocery.setCategory(fuzzyMatchGroceryCategory(editGrocery.title));
|
||||
}, [editGrocery.title]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
width: "100%",
|
||||
borderRadius: 25,
|
||||
padding: 15,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
placeholder="Grocery"
|
||||
value={editGrocery.title}
|
||||
onChangeText={(value) => {
|
||||
editGrocery.setTitle(value);
|
||||
}}
|
||||
onSubmitEditing={() => {
|
||||
if (editGrocery.setSubmit) editGrocery.setSubmit(true);
|
||||
if (editGrocery.closeEdit) editGrocery.closeEdit(false);
|
||||
if (editGrocery.updateCategory && editGrocery.id)
|
||||
editGrocery.updateCategory(editGrocery.id, {
|
||||
category: fuzzyMatchGroceryCategory(editGrocery.title), title: editGrocery.title
|
||||
});
|
||||
}}
|
||||
maxLength={25}
|
||||
/>
|
||||
<Text>{editGrocery.category}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditGroceryItem;
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import EditGroceryFrequency from "./EditGroceryFrequency";
|
||||
import { TextInput } from "react-native";
|
||||
import EditGroceryItem from "./EditGroceryItem";
|
||||
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
|
||||
|
||||
const GroceryItem = ({
|
||||
item,
|
||||
@ -31,6 +32,7 @@ const GroceryItem = ({
|
||||
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
|
||||
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
|
||||
const [newTitle, setNewTitle] = useState<string>("");
|
||||
const [category, setCategory] = useState<GroceryCategory>(GroceryCategory.None);
|
||||
|
||||
const handleTitleChange = (newTitle: string) => {
|
||||
updateGroceryItem(item.id, { title: newTitle });
|
||||
@ -46,6 +48,7 @@ const GroceryItem = ({
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={item.id}
|
||||
style={{ borderRadius: 50, marginVertical: 5, height: 55 }}
|
||||
backgroundColor="white"
|
||||
centerV
|
||||
@ -63,49 +66,24 @@ const GroceryItem = ({
|
||||
}}
|
||||
/>
|
||||
<ListItem.Part left containerStyle={{ flex: 1, paddingStart: 20 }}>
|
||||
{/* <View
|
||||
height={50}
|
||||
width={50}
|
||||
style={{ borderRadius: 15 }}
|
||||
backgroundColor="#e6f1ed"
|
||||
marginR-10
|
||||
children={
|
||||
<MaterialCommunityIcons
|
||||
name={iconMapping[item.category]}
|
||||
size={50}
|
||||
color="orange"
|
||||
/>
|
||||
}
|
||||
/>*/}
|
||||
{!isEditingTitle ? (
|
||||
<View>
|
||||
<TouchableOpacity onPress={() => setIsEditingTitle(true)}>
|
||||
<Text text70BL>{item.title}</Text>
|
||||
</TouchableOpacity>
|
||||
{ /*
|
||||
<TextInput
|
||||
value={item.title}
|
||||
onChangeText={handleTitleChange}
|
||||
onBlur={() => {
|
||||
setIsEditingTitle(false);
|
||||
console.log(groceries);
|
||||
}}
|
||||
autoFocus
|
||||
style={{
|
||||
fontSize: 16,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
/>
|
||||
*/}
|
||||
</View>
|
||||
) : (
|
||||
<EditGroceryItem
|
||||
title={item.title}
|
||||
setTitle={handleTitleChange}
|
||||
setCategory={handleCategoryChange}
|
||||
/>
|
||||
<EditGroceryItem
|
||||
editGrocery={{
|
||||
id: item.id,
|
||||
title: newTitle,
|
||||
setTitle: setNewTitle,
|
||||
category:category,
|
||||
updateCategory: updateGroceryItem,
|
||||
closeEdit: setIsEditingTitle,
|
||||
setCategory: setCategory,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ListItem.Part>
|
||||
<ListItem.Part right containerStyle={{ paddingEnd: 20 }}>
|
||||
|
@ -12,6 +12,7 @@ import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import CategoryDropdown from "./CategoryDropdown";
|
||||
import { MaterialIcons } from "@expo/vector-icons";
|
||||
import EditGroceryItem from "./EditGroceryItem";
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
|
||||
const GroceryList = () => {
|
||||
const {
|
||||
@ -21,6 +22,7 @@ const GroceryList = () => {
|
||||
setIsAddingGrocery,
|
||||
addGrocery,
|
||||
} = useGroceryContext();
|
||||
const { profileData } = useAuthContext();
|
||||
const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>(
|
||||
groceries.filter((item) => item.approved === true)
|
||||
);
|
||||
@ -31,6 +33,7 @@ const GroceryList = () => {
|
||||
GroceryCategory.Bakery
|
||||
);
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [submit, setSubmitted] = useState<boolean>(false);
|
||||
|
||||
// Group approved groceries by category
|
||||
const approvedGroceriesByCategory = approvedGroceries.reduce(
|
||||
@ -46,19 +49,27 @@ const GroceryList = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (title?.length > 2 && title?.length <= 25) {
|
||||
addGrocery({
|
||||
id: 0,
|
||||
title: title,
|
||||
category: category,
|
||||
approved: false,
|
||||
recurring: false,
|
||||
frequency: GroceryFrequency.Never,
|
||||
bought: false,
|
||||
});
|
||||
if (submit) {
|
||||
if (title?.length > 2 && title?.length <= 25) {
|
||||
addGrocery({
|
||||
id: 0,
|
||||
title: title,
|
||||
category: category,
|
||||
approved: profileData?.userType === ProfileType.PARENT ? true : false,
|
||||
recurring: false,
|
||||
frequency: GroceryFrequency.Never,
|
||||
bought: false,
|
||||
});
|
||||
|
||||
setIsAddingGrocery(false);
|
||||
setIsAddingGrocery(false);
|
||||
setSubmitted(false);
|
||||
setTitle("");
|
||||
}
|
||||
}
|
||||
}, [submit]);
|
||||
|
||||
useEffect(() => {
|
||||
/**/
|
||||
}, [category]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -67,7 +78,7 @@ const GroceryList = () => {
|
||||
}, [groceries]);
|
||||
|
||||
return (
|
||||
<View marginH-20>
|
||||
<View marginH-20 marginB-20>
|
||||
<HeaderTemplate
|
||||
message={"Welcome to your grocery list"}
|
||||
isWelcome={false}
|
||||
@ -101,8 +112,8 @@ const GroceryList = () => {
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
backgroundColor='transparent'
|
||||
paddingH-10
|
||||
backgroundColor="transparent"
|
||||
paddingH-10
|
||||
iconSource={() => (
|
||||
<MaterialIcons name="person-add-alt" size={24} color="gray" />
|
||||
)}
|
||||
@ -157,7 +168,15 @@ const GroceryList = () => {
|
||||
</View>
|
||||
</View>
|
||||
{isAddingGrocery && (
|
||||
<EditGroceryItem title={title} setTitle={setTitle} setCategory={setCategory} />
|
||||
<EditGroceryItem
|
||||
editGrocery={{
|
||||
title: title,
|
||||
setCategory: setCategory,
|
||||
category: category,
|
||||
setTitle: setTitle,
|
||||
setSubmit: setSubmitted,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Render Approved Groceries Grouped by Category */}
|
||||
|
@ -1,145 +1,150 @@
|
||||
import React, {useState} from "react";
|
||||
import {Button, ButtonSize, Text, TextField, View,} from "react-native-ui-lib";
|
||||
import {useSignUp} from "@/hooks/firebase/useSignUp";
|
||||
import {ProfileType} from "@/contexts/AuthContext";
|
||||
import {StyleSheet} from "react-native";
|
||||
import {AntDesign} from "@expo/vector-icons";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonSize,
|
||||
Checkbox,
|
||||
Text,
|
||||
TextField,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native-ui-lib";
|
||||
import { useSignUp } from "@/hooks/firebase/useSignUp";
|
||||
import { ProfileType } from "@/contexts/AuthContext";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
|
||||
const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [firstName, setFirstName] = useState<string>("");
|
||||
const [lastName, setLastName] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [isParent, setIsParent] = useState<boolean>(true);
|
||||
const [isChild, setIsChild] = useState<boolean>(false);
|
||||
const [isCaregiver, setIsCaregiver] = useState<boolean>(false);
|
||||
const [profileType, setProfileType] = useState<ProfileType>(
|
||||
ProfileType.PARENT
|
||||
);
|
||||
const {mutateAsync: signUp} = useSignUp();
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [firstName, setFirstName] = useState<string>("");
|
||||
const [lastName, setLastName] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await signUp({email, password, firstName, lastName});
|
||||
};
|
||||
const [isPasswordVisible, setIsPasswordVisible] = useState<boolean>(false);
|
||||
const [allowFaceID, setAllowFaceID] = useState<boolean>(false);
|
||||
const [acceptTerms, setAcceptTerms] = useState<boolean>(false);
|
||||
const { mutateAsync: signUp } = useSignUp();
|
||||
|
||||
return (
|
||||
<View padding-10>
|
||||
<Text text30 center>
|
||||
Get started with Kali
|
||||
</Text>
|
||||
<Text center>Please enter your details.</Text>
|
||||
<TextField
|
||||
marginT-60
|
||||
placeholder="First name"
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
style={styles.textfield}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
style={styles.textfield}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
style={styles.textfield}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
style={styles.textfield}
|
||||
/>
|
||||
<Button
|
||||
label="Register"
|
||||
onPress={handleSignUp}
|
||||
style={{marginBottom: 10, backgroundColor: "#fd1775"}}
|
||||
/>
|
||||
<Button
|
||||
label="Sign up with Google"
|
||||
backgroundColor="white"
|
||||
color="black"
|
||||
iconSource={() => (
|
||||
<AntDesign
|
||||
name="google"
|
||||
size={24}
|
||||
color="black"
|
||||
style={{marginRight: 15}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{/*<Text style={{ marginBottom: 10 }}>Choose Profile Type:</Text>
|
||||
<Checkbox
|
||||
label="Parent"
|
||||
value={isParent}
|
||||
onValueChange={(value) => {
|
||||
setIsParent(value);
|
||||
setProfileType(ProfileType.PARENT);
|
||||
if (value) {
|
||||
setIsChild(false);
|
||||
setIsCaregiver(false);
|
||||
}
|
||||
}}
|
||||
style={{ marginBottom: 10 }}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Child"
|
||||
value={isChild}
|
||||
onValueChange={(value) => {
|
||||
setIsChild(value);
|
||||
setProfileType(ProfileType.CHILD);
|
||||
if (value) {
|
||||
setIsParent(false);
|
||||
setIsCaregiver(false);
|
||||
}
|
||||
}}
|
||||
style={{ marginBottom: 10 }}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Caregiver"
|
||||
value={isCaregiver}
|
||||
onValueChange={(value) => {
|
||||
setIsCaregiver(value);
|
||||
setProfileType(ProfileType.CAREGIVER);
|
||||
if (value) {
|
||||
setIsParent(false);
|
||||
setIsChild(false);
|
||||
}
|
||||
}}
|
||||
/>*/}
|
||||
<View row centerH marginT-10 marginB-5 gap-5>
|
||||
<Text text70 center>
|
||||
Already have an account?
|
||||
</Text>
|
||||
const handleSignUp = async () => {
|
||||
await signUp({ email, password, firstName, lastName });
|
||||
};
|
||||
|
||||
|
||||
<Button
|
||||
label="Sign In"
|
||||
flexS
|
||||
margin-0
|
||||
link
|
||||
color="#fd1775"
|
||||
size={ButtonSize.small}
|
||||
text70
|
||||
onPress={() => setTab("login")}
|
||||
/>
|
||||
</View>
|
||||
return (
|
||||
<View padding-10 height={"100%"} flexG>
|
||||
<Text text30 center>
|
||||
Get started with Kali
|
||||
</Text>
|
||||
<Text center>Please enter your details.</Text>
|
||||
<TextField
|
||||
marginT-60
|
||||
placeholder="First name"
|
||||
value={firstName}
|
||||
onChangeText={setFirstName}
|
||||
style={styles.textfield}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Last name"
|
||||
value={lastName}
|
||||
onChangeText={setLastName}
|
||||
style={styles.textfield}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
style={styles.textfield}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry={!isPasswordVisible}
|
||||
style={styles.textfield}
|
||||
trailingAccessory={
|
||||
<TouchableOpacity
|
||||
onPress={() => setIsPasswordVisible(!isPasswordVisible)}
|
||||
>
|
||||
<AntDesign
|
||||
name={isPasswordVisible ? "eye" : "eyeo"}
|
||||
size={24}
|
||||
color="gray"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
/>
|
||||
<View gap-10 marginH-10>
|
||||
<View row centerV>
|
||||
<Checkbox
|
||||
value={allowFaceID}
|
||||
onValueChange={(value) => {
|
||||
setAllowFaceID(value);
|
||||
}}
|
||||
/>
|
||||
<Text text90R marginL-10>
|
||||
Allow FaceID for login in future
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
<View row centerV>
|
||||
<Checkbox
|
||||
value={acceptTerms}
|
||||
onValueChange={(value) => setAcceptTerms(value)}
|
||||
/>
|
||||
<View row>
|
||||
<Text text90R marginL-10>
|
||||
I accept the
|
||||
</Text>
|
||||
<TouchableOpacity>
|
||||
<Text text90 style={{ textDecorationLine: "underline" }}>
|
||||
{" "}
|
||||
terms and conditions
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Text text90R> and </Text>
|
||||
<TouchableOpacity>
|
||||
<Text text90 style={{ textDecorationLine: "underline" }}>
|
||||
{" "}
|
||||
privacy policy
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.bottomView}>
|
||||
<Button
|
||||
label="Register"
|
||||
onPress={handleSignUp}
|
||||
style={{ marginBottom: 10, backgroundColor: "#fd1775" }}
|
||||
/>
|
||||
<View row centerH marginT-10 marginB-5 gap-5>
|
||||
<Text text70 center>
|
||||
Already have an account?
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
label="Sign In"
|
||||
flexS
|
||||
margin-0
|
||||
link
|
||||
color="#fd1775"
|
||||
size={ButtonSize.small}
|
||||
text70
|
||||
onPress={() => setTab("login")}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpPage;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
textfield: {
|
||||
backgroundColor: "white",
|
||||
marginVertical: 10,
|
||||
padding: 30,
|
||||
height: 45,
|
||||
borderRadius: 50,
|
||||
},
|
||||
textfield: {
|
||||
backgroundColor: "white",
|
||||
marginVertical: 10,
|
||||
padding: 30,
|
||||
height: 45,
|
||||
borderRadius: 50,
|
||||
},
|
||||
//mora da se izmeni kako treba
|
||||
bottomView: { marginTop: 150 },
|
||||
});
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { View, Text, TouchableOpacity } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { ScrollView, StyleSheet } from "react-native";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
} from "react-native";
|
||||
import MyProfile from "./user_settings_views/MyProfile";
|
||||
import MyGroup from "./user_settings_views/MyGroup";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
@ -9,51 +14,57 @@ import { useAuthContext } from "@/contexts/AuthContext";
|
||||
const UserSettings = (props: { setSelectedPage: (page: number) => void }) => {
|
||||
const [selectedView, setSelectedView] = useState<boolean>(true);
|
||||
return (
|
||||
<View>
|
||||
<ScrollView>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginL-20 marginB-35 centerV>
|
||||
<Ionicons name="chevron-back" size={22} color="#979797" />
|
||||
<Text text70 color="#979797">
|
||||
Return to main settings
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1 }}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginL-20 marginB-35 centerV>
|
||||
<Ionicons name="chevron-back" size={22} color="#979797" />
|
||||
<Text text70 color="#979797">
|
||||
Return to main settings
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View marginH-20>
|
||||
<Text text60R marginB-25>
|
||||
User Management
|
||||
</Text>
|
||||
<View style={styles.buttonSwitch} spread row>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(true)}
|
||||
centerV
|
||||
centerH
|
||||
style={
|
||||
selectedView == true ? styles.btnSelected : styles.btnNot
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text text70 color={selectedView ? "white" : "black"}>
|
||||
My Profile
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(false)}
|
||||
centerV
|
||||
centerH
|
||||
style={
|
||||
selectedView == false ? styles.btnSelected : styles.btnNot
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text text70 color={!selectedView ? "white" : "black"}>
|
||||
My Group
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{selectedView && <MyProfile />}
|
||||
{!selectedView && <MyGroup />}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View marginH-20>
|
||||
<Text text60R marginB-25>
|
||||
User Management
|
||||
</Text>
|
||||
<View style={styles.buttonSwitch} spread row>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(true)}
|
||||
centerV
|
||||
centerH
|
||||
style={selectedView == true ? styles.btnSelected : styles.btnNot}
|
||||
>
|
||||
<View>
|
||||
<Text text70 color={selectedView ? "white" : "black"}>
|
||||
My Profile
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => setSelectedView(false)}
|
||||
centerV
|
||||
centerH
|
||||
style={selectedView == false ? styles.btnSelected : styles.btnNot}
|
||||
>
|
||||
<View>
|
||||
<Text text70 color={!selectedView ? "white" : "black"}>
|
||||
My Group
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{selectedView && <MyProfile />}
|
||||
{!selectedView && <MyGroup />}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -2,13 +2,14 @@ import { View, Text, Button } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { Fontisto } from "@expo/vector-icons";
|
||||
import { ProgressBar } from "react-native-ui-lib/src/components/progressBar";
|
||||
import { useToDosContext } from "@/contexts/ToDosContext";
|
||||
|
||||
const ProgressCard = () => {
|
||||
const ProgressCard = ({children}: {children?: React.ReactNode}) => {
|
||||
const { maxPoints } = useToDosContext();
|
||||
return (
|
||||
<View
|
||||
backgroundColor="white"
|
||||
marginH-25
|
||||
marginB-30
|
||||
marginB-5
|
||||
padding-15
|
||||
style={{ borderRadius: 22 }}
|
||||
>
|
||||
@ -30,16 +31,10 @@ const ProgressCard = () => {
|
||||
/>
|
||||
<View row spread>
|
||||
<Text color={"#868686"}>0</Text>
|
||||
<Text color={"#868686"}>1000</Text>
|
||||
<Text color={"#868686"}>{maxPoints}</Text>
|
||||
</View>
|
||||
<View centerV centerH>
|
||||
<Button
|
||||
backgroundColor="transparent"
|
||||
>
|
||||
<Text style={{ textDecorationLine: "underline", color: "#05a8b6" }}>
|
||||
View your full progress report here
|
||||
</Text>
|
||||
</Button>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
@ -10,7 +10,6 @@ const ToDoItem = (props: { item: IToDo }) => {
|
||||
centerV
|
||||
paddingV-10
|
||||
paddingH-10
|
||||
marginH-25
|
||||
marginV-10
|
||||
style={{
|
||||
borderRadius: 22,
|
||||
|
76
components/pages/todos/ToDosPage.tsx
Normal file
76
components/pages/todos/ToDosPage.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { View, Text, Button, ButtonSize } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import AddChore from "./AddChore";
|
||||
import ProgressCard from "./ProgressCard";
|
||||
import ToDosList from "./ToDosList";
|
||||
import { ScrollView } from "react-native";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
import FamilyChoresProgress from "./family-chores/FamilyChoresProgress";
|
||||
import UserChoresProgress from "./user-chores/UserChoresProgress";
|
||||
|
||||
const ToDosPage = () => {
|
||||
const [pageIndex, setPageIndex] = useState<number>(0);
|
||||
const { profileData } = useAuthContext();
|
||||
const pageLink = (
|
||||
<TouchableOpacity onPress={() => setPageIndex(1)}>
|
||||
<Text color="#ea156d">View family progress</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
return (
|
||||
<View paddingH-25 backgroundColor="#f9f8f7" height={"100%"}>
|
||||
{pageIndex == 0 && (
|
||||
<View>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
<View>
|
||||
<HeaderTemplate
|
||||
message="Here are your To Do's"
|
||||
isWelcome={true}
|
||||
link={profileData?.userType == ProfileType.PARENT && pageLink}
|
||||
/>
|
||||
{profileData?.userType == ProfileType.CHILD && (
|
||||
<View marginB-25>
|
||||
<ProgressCard
|
||||
children={
|
||||
<Button
|
||||
backgroundColor="transparent"
|
||||
onPress={() => setPageIndex(2)}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
textDecorationLine: "underline",
|
||||
color: "#05a8b6",
|
||||
}}
|
||||
>
|
||||
View your full progress report here
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<ToDosList />
|
||||
</View>
|
||||
</ScrollView>
|
||||
{profileData?.userType == ProfileType.PARENT && <AddChore />}
|
||||
</View>
|
||||
)}
|
||||
{pageIndex == 1 && <FamilyChoresProgress setPageIndex={setPageIndex} />}
|
||||
{pageIndex == 2 && <UserChoresProgress setPageIndex={setPageIndex} />}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
linkBtn: {
|
||||
backgroundColor: "transparent",
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
|
||||
export default ToDosPage;
|
87
components/pages/todos/family-chores/FamilyChart.tsx
Normal file
87
components/pages/todos/family-chores/FamilyChart.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { BarChart } from "react-native-gifted-charts";
|
||||
|
||||
const FamilyChart = () => {
|
||||
// Define the data for the bars
|
||||
const data = [
|
||||
{
|
||||
value: 600, // Total value of the bar
|
||||
stacks: [
|
||||
{ value: 290, color: "#e7d526" }, // First part of the bar
|
||||
{ value: 310, color: "#00a8b6" }, // Second part of the bar
|
||||
],
|
||||
label: "M",
|
||||
},
|
||||
{
|
||||
value: 400,
|
||||
stacks: [
|
||||
{ value: 190, color: "#e7d526" },
|
||||
{ value: 210, color: "#00a8b6" },
|
||||
],
|
||||
label: "Tu",
|
||||
},
|
||||
{
|
||||
value: 400,
|
||||
stacks: [
|
||||
{ value: 210, color: "#e7d526" },
|
||||
{ value: 190, color: "#00a8b6" },
|
||||
],
|
||||
label: "W",
|
||||
},
|
||||
{
|
||||
value: 800,
|
||||
stacks: [
|
||||
{ value: 410, color: "#e7d526" },
|
||||
{ value: 390, color: "#00a8b6" },
|
||||
],
|
||||
label: "Th",
|
||||
},
|
||||
{
|
||||
value: 600,
|
||||
stacks: [
|
||||
{ value: 220, color: "#e7d526" },
|
||||
{ value: 380, color: "#00a8b6" },
|
||||
],
|
||||
label: "F",
|
||||
},
|
||||
{
|
||||
value: 200,
|
||||
stacks: [
|
||||
{ value: 160, color: "#e7d526" },
|
||||
{ value: 40, color: "#00a8b6" },
|
||||
],
|
||||
label: "Sa",
|
||||
},
|
||||
{
|
||||
value: 200,
|
||||
stacks: [
|
||||
{ value: 160, color: "#e7d526" },
|
||||
{ value: 40, color: "#00a8b6" },
|
||||
],
|
||||
label: "Su",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
stackData={data}
|
||||
width={250}
|
||||
height={150} // Height of the chart
|
||||
barWidth={20} // Width of each bar
|
||||
noOfSections={5} // Number of horizontal sections (for 0 to 1000 in steps of 200)
|
||||
maxValue={1000} // Max value on the chart
|
||||
stepValue={200} // Step size for horizontal lines
|
||||
yAxisThickness={0} // Hide the Y-axis line
|
||||
yAxisLabelTexts={["0", "200", "400", "600", "800", "1000"]} // Custom Y-axis labels
|
||||
hideRules={false} // Show the horizontal lines
|
||||
rulesColor="#dadada" // Color for the horizontal lines
|
||||
stackBorderTopLeftRadius={5} // Round the bars
|
||||
stackBorderTopRightRadius={5} // Round the bars
|
||||
spacing={16}
|
||||
disableScroll
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FamilyChart;
|
@ -0,0 +1,67 @@
|
||||
import { View, Text } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import FamilyChart from "./FamilyChart";
|
||||
import { TouchableOpacity } from "react-native-ui-lib/src/incubator";
|
||||
|
||||
const FamilyChoresProgress = ({
|
||||
setPageIndex,
|
||||
}: {
|
||||
setPageIndex: (value: number) => void;
|
||||
}) => {
|
||||
return (
|
||||
<View marginT-20 marginH-5>
|
||||
<TouchableOpacity onPress={() => setPageIndex(0)}>
|
||||
<Text>Back to ToDos</Text>
|
||||
</TouchableOpacity>
|
||||
<View centerH>
|
||||
<Text text50R>Family Chores Progress Report</Text>
|
||||
</View>
|
||||
<View row spread marginT-25 marginB-20>
|
||||
<Text text70>Points earned this week</Text>
|
||||
<View row>
|
||||
<View style={styles.pfpSmall} backgroundColor="#05a8b6" />
|
||||
<View style={styles.pfpSmall} backgroundColor="#ebd825" />
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.card} paddingL-10>
|
||||
<FamilyChart />
|
||||
</View>
|
||||
<Text text70 marginV-20>
|
||||
Chore Tracker
|
||||
</Text>
|
||||
<View style={styles.card} marginB-20 row spread>
|
||||
<View style={styles.pfpBig} backgroundColor="#05a8b6" />
|
||||
<View width={"100%"} centerV centerH>
|
||||
<Text> x/y chores completed</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.card} row spread>
|
||||
<View style={styles.pfpBig} backgroundColor="#ebd825" />
|
||||
<View width={"100%"} centerV centerH>
|
||||
<Text> x/y chores completed</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const styles = StyleSheet.create({
|
||||
pfpSmall: {
|
||||
width: 30,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 2,
|
||||
},
|
||||
pfpBig: {
|
||||
width: 50,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 50,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default FamilyChoresProgress;
|
66
components/pages/todos/user-chores/UserChart.tsx
Normal file
66
components/pages/todos/user-chores/UserChart.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { BarChart } from "react-native-gifted-charts";
|
||||
|
||||
const UserChart = () => {
|
||||
const barColor = "#05a8b6"
|
||||
const data = [
|
||||
{
|
||||
value: 290, // Direct value of the bar
|
||||
frontColor: barColor, // Color of the bar
|
||||
label: "M",
|
||||
},
|
||||
{
|
||||
value: 190,
|
||||
frontColor: barColor,
|
||||
label: "Tu",
|
||||
},
|
||||
{
|
||||
value: 210,
|
||||
frontColor: barColor,
|
||||
label: "W",
|
||||
},
|
||||
{
|
||||
value: 410,
|
||||
frontColor: barColor,
|
||||
label: "Th",
|
||||
},
|
||||
{
|
||||
value: 220,
|
||||
frontColor: barColor,
|
||||
label: "F",
|
||||
},
|
||||
{
|
||||
value: 160,
|
||||
frontColor: barColor,
|
||||
label: "Sa",
|
||||
},
|
||||
{
|
||||
value: 160,
|
||||
frontColor: barColor,
|
||||
label: "Su",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
data={data}
|
||||
width={255}
|
||||
height={150} // Height of the chart
|
||||
barWidth={20} // Width of each bar
|
||||
noOfSections={5} // Number of horizontal sections (for 0 to 1000 in steps of 200)
|
||||
maxValue={1000} // Max value on the chart
|
||||
stepValue={200} // Step size for horizontal lines
|
||||
yAxisThickness={0} // Hide the Y-axis line
|
||||
yAxisLabelTexts={["0", "200", "400", "600", "800", "1000"]} // Custom Y-axis labels
|
||||
hideRules={false} // Show the horizontal lines
|
||||
rulesColor="#dadada" // Color for the horizontal lines
|
||||
barBorderTopLeftRadius={5} // Round the bars
|
||||
barBorderTopRightRadius={5} // Round the bars
|
||||
spacing={16}
|
||||
disableScroll
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserChart;
|
152
components/pages/todos/user-chores/UserChoresProgress.tsx
Normal file
152
components/pages/todos/user-chores/UserChoresProgress.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ProgressBar,
|
||||
Button,
|
||||
ButtonSize,
|
||||
Modal,
|
||||
Dialog,
|
||||
} from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { TouchableOpacity } from "react-native-ui-lib/src/incubator";
|
||||
import UserChart from "./UserChart";
|
||||
import ProgressCard from "../ProgressCard";
|
||||
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { PanViewDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
|
||||
|
||||
const UserChoresProgress = ({
|
||||
setPageIndex,
|
||||
}: {
|
||||
setPageIndex: (value: number) => void;
|
||||
}) => {
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
return (
|
||||
<View marginT-20 paddingB-20>
|
||||
<ScrollView
|
||||
showsVerticalScrollIndicator={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
<TouchableOpacity onPress={() => setPageIndex(0)}>
|
||||
<Text>Back to ToDos</Text>
|
||||
</TouchableOpacity>
|
||||
<View centerH>
|
||||
<Text text50R>Your To Dos Progress Report</Text>
|
||||
</View>
|
||||
<View row spread marginT-25 marginB-5>
|
||||
<Text text70>Daily Goal</Text>
|
||||
</View>
|
||||
<ProgressCard />
|
||||
<View row spread marginT-10 marginB-5>
|
||||
<Text text70>Points Earned This Week</Text>
|
||||
</View>
|
||||
<View style={styles.card} paddingL-10>
|
||||
<UserChart />
|
||||
</View>
|
||||
<View row spread marginT-20 marginB-8 centerV>
|
||||
<Text text70>Total Reward Points</Text>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
label="Spend my points"
|
||||
color="#50be0c"
|
||||
backgroundColor="#ebf2e4"
|
||||
onPress={() => setModalVisible(true)}
|
||||
iconSource={() => (
|
||||
<AntDesign
|
||||
name="gift"
|
||||
size={20}
|
||||
style={{ marginRight: 5 }}
|
||||
color="#50be0c"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<View row centerV>
|
||||
<Ionicons name="flower-outline" size={30} color="#8005eb" />
|
||||
<Text text70 marginL-5>
|
||||
You have 1200 points saved!
|
||||
</Text>
|
||||
</View>
|
||||
<ProgressBar
|
||||
progress={80}
|
||||
progressColor="#ff9900"
|
||||
style={{
|
||||
height: 21,
|
||||
backgroundColor: "#faeedb",
|
||||
marginTop: 15,
|
||||
marginBottom: 5,
|
||||
}}
|
||||
/>
|
||||
<View row spread>
|
||||
<Text>0</Text>
|
||||
<Text>5000</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<Dialog
|
||||
visible={modalVisible}
|
||||
onDismiss={() => setModalVisible(false)}
|
||||
children={
|
||||
<View style={styles.card} paddingH-35 paddingT-35>
|
||||
<Text text60 center marginB-35>
|
||||
How would you like to spend your points?
|
||||
</Text>
|
||||
<Button
|
||||
label="Skip a Chore Cor a Day - 150 pts"
|
||||
text70
|
||||
marginB-15
|
||||
backgroundColor="#05a8b6"
|
||||
size={ButtonSize.large}
|
||||
/>
|
||||
<Button
|
||||
label="Extra Screen Time - 100 pts"
|
||||
text70
|
||||
marginB-15
|
||||
backgroundColor="#ea156c"
|
||||
size={ButtonSize.large}
|
||||
/>
|
||||
<Button
|
||||
label="Movie Night - 50 pts"
|
||||
text70
|
||||
marginB-15
|
||||
backgroundColor="#7305d4"
|
||||
size={ButtonSize.large}
|
||||
/>
|
||||
<Button
|
||||
label="Ice Cream Treat - 25 pts"
|
||||
text70
|
||||
marginB-15
|
||||
backgroundColor="#e28800"
|
||||
size={ButtonSize.large}
|
||||
/>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)}>
|
||||
<Text text70 center color="#999999">Go back to my to dos</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const styles = StyleSheet.create({
|
||||
pfpSmall: {
|
||||
width: 30,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 2,
|
||||
},
|
||||
pfpBig: {
|
||||
width: 50,
|
||||
aspectRatio: 1,
|
||||
borderRadius: 50,
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 20,
|
||||
padding: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default UserChoresProgress;
|
@ -2,7 +2,12 @@ import { View, Text } from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import { useAuthContext } from "@/contexts/AuthContext";
|
||||
|
||||
const HeaderTemplate = (props: { message: string; isWelcome: boolean; children?: React.ReactNode }) => {
|
||||
const HeaderTemplate = (props: {
|
||||
message: string;
|
||||
isWelcome: boolean;
|
||||
children?: React.ReactNode;
|
||||
link?: React.ReactNode;
|
||||
}) => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
return (
|
||||
<View row centerV padding-25>
|
||||
@ -14,9 +19,12 @@ const HeaderTemplate = (props: { message: string; isWelcome: boolean; children?:
|
||||
marginR-20
|
||||
/>
|
||||
<View>
|
||||
{props.isWelcome && <Text text70L>Welcome, {user?.email}!</Text>}
|
||||
{props.isWelcome && (
|
||||
<Text text70L>Welcome, {profileData?.firstName}!</Text>
|
||||
)}
|
||||
<Text text70BL>{props.message}</Text>
|
||||
{props.children && <View>{props.children}</View>}
|
||||
{props.link && <View>{props.link}</View>}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
Reference in New Issue
Block a user