feat: update habit progress numeric
This commit is contained in:
parent
dbc19d7056
commit
c455326f8e
26
app/application/habits/[habitId]/progress/[selectedDate].tsx
Normal file
26
app/application/habits/[habitId]/progress/[selectedDate].tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { Redirect, useLocalSearchParams } from "expo-router"
|
||||
|
||||
import { HabitProgress } from "@/presentation/react-native/components/HabitProgress"
|
||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||
|
||||
const HabitProgressPage: React.FC = () => {
|
||||
const { habitId, selectedDate } = useLocalSearchParams()
|
||||
const { habitsTracker } = useHabitsTracker()
|
||||
|
||||
const habitHistory = habitsTracker.getHabitHistoryById(habitId as string)
|
||||
const selectedDateParsed = new Date(selectedDate as string)
|
||||
|
||||
if (habitHistory == null) {
|
||||
return <Redirect href="/application/habits/" />
|
||||
}
|
||||
|
||||
return (
|
||||
<HabitProgress
|
||||
habitHistory={habitHistory}
|
||||
key={habitHistory.habit.id}
|
||||
selectedDate={selectedDateParsed}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default HabitProgressPage
|
@ -51,6 +51,16 @@ export class HabitGoalProgressUpdateUseCase
|
||||
})
|
||||
}
|
||||
|
||||
if (goalProgress.isNumeric()) {
|
||||
return await this.habitProgressCreateRepository.execute({
|
||||
habitProgressData: {
|
||||
date,
|
||||
goalProgress,
|
||||
habitId: habitHistory.habit.id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
}
|
||||
|
@ -185,19 +185,6 @@ export const HabitCreateForm: React.FC<HabitCreateFormProps> = ({ user }) => {
|
||||
]}
|
||||
>
|
||||
Habit Type
|
||||
{/* <Tooltip
|
||||
title="Routine habits are activities performed regularly, while Target habits involve setting specific objectives to be achieved through repeated actions."
|
||||
enterTouchDelay={50}
|
||||
leaveTouchDelay={25}
|
||||
>
|
||||
<IconButton
|
||||
icon="chat-question-outline"
|
||||
selected
|
||||
size={24}
|
||||
onPress={() => {}}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
</Tooltip> */}
|
||||
</Text>
|
||||
<SegmentedButtons
|
||||
style={[{ width: "96%" }]}
|
||||
|
194
presentation/react-native/components/HabitProgress.tsx
Normal file
194
presentation/react-native/components/HabitProgress.tsx
Normal file
@ -0,0 +1,194 @@
|
||||
import { useState } from "react"
|
||||
import { ScrollView, StyleSheet, View } from "react-native"
|
||||
import { Button, Snackbar, Text, TextInput } from "react-native-paper"
|
||||
import { SafeAreaView } from "react-native-safe-area-context"
|
||||
|
||||
import type { GoalNumeric } from "@/domain/entities/Goal"
|
||||
import { GoalNumericProgress } from "@/domain/entities/Goal"
|
||||
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||
import { LOCALE, capitalize } from "@/utils/strings"
|
||||
|
||||
export interface HabitProgressProps {
|
||||
habitHistory: HabitHistory
|
||||
selectedDate: Date
|
||||
}
|
||||
|
||||
export const HabitProgress: React.FC<HabitProgressProps> = ({
|
||||
habitHistory,
|
||||
selectedDate,
|
||||
}) => {
|
||||
const { habitsTrackerPresenter, habitGoalProgressUpdate } = useHabitsTracker()
|
||||
|
||||
const [isVisibleSnackbar, setIsVisibleSnackbar] = useState(false)
|
||||
|
||||
const onDismissSnackbar = (): void => {
|
||||
setIsVisibleSnackbar(false)
|
||||
}
|
||||
|
||||
const goalProgress = habitHistory.getGoalProgressByDate(selectedDate)
|
||||
|
||||
const values = {
|
||||
progress: 0,
|
||||
min: 0,
|
||||
max: 0,
|
||||
}
|
||||
if (goalProgress.isNumeric()) {
|
||||
values.max = goalProgress.goal.target.value
|
||||
}
|
||||
const [progressValue, setProgressValue] = useState(values.progress)
|
||||
|
||||
if (!goalProgress.isNumeric()) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const progressTotal = goalProgress.progress + progressValue
|
||||
|
||||
const handleSave = async (): Promise<void> => {
|
||||
setIsVisibleSnackbar(true)
|
||||
await habitsTrackerPresenter.habitUpdateProgress({
|
||||
date: selectedDate,
|
||||
habitHistory,
|
||||
goalProgress: new GoalNumericProgress({
|
||||
goal: habitHistory.habit.goal as GoalNumeric,
|
||||
progress: progressValue,
|
||||
}),
|
||||
})
|
||||
setProgressValue(0)
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, justifyContent: "space-between" }}>
|
||||
<ScrollView
|
||||
contentContainerStyle={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 20,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
fontSize: 28,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{habitHistory.habit.name}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 10,
|
||||
fontWeight: "bold",
|
||||
fontSize: 18,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{capitalize(habitHistory.habit.goal.frequency)} Progress
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
textAlign: "center",
|
||||
marginBottom: 15,
|
||||
}}
|
||||
>
|
||||
{selectedDate.toLocaleDateString(LOCALE, {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
borderBottomWidth: 1,
|
||||
borderColor: "#f57c00",
|
||||
marginVertical: 10,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text style={{ marginVertical: 10, fontWeight: "bold", fontSize: 18 }}>
|
||||
{goalProgress.progress.toLocaleString()} /{" "}
|
||||
{goalProgress.goal.target.value.toLocaleString()}{" "}
|
||||
{goalProgress.goal.target.unit}
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
placeholder="Progress to add (e.g: 5 000)"
|
||||
value={progressValue === 0 ? "" : progressValue.toString()}
|
||||
onChangeText={(text) => {
|
||||
const hasDigits = /\d+$/.test(text)
|
||||
if (text.length <= 0 || !hasDigits) {
|
||||
setProgressValue(0)
|
||||
return
|
||||
}
|
||||
setProgressValue(Number.parseInt(text, 10))
|
||||
}}
|
||||
style={[
|
||||
styles.spacing,
|
||||
{
|
||||
width: "80%",
|
||||
},
|
||||
]}
|
||||
mode="outlined"
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
|
||||
{goalProgress.progress > 0 && progressValue > 0 ? (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
textAlign: "center",
|
||||
marginBottom: 15,
|
||||
}}
|
||||
>
|
||||
{goalProgress.progress.toLocaleString()} +{" "}
|
||||
{progressValue.toLocaleString()} = {progressTotal.toLocaleString()}{" "}
|
||||
{goalProgress.goal.target.unit}
|
||||
</Text>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={handleSave}
|
||||
loading={habitGoalProgressUpdate.state === "loading"}
|
||||
disabled={
|
||||
habitGoalProgressUpdate.state === "loading" || progressValue === 0
|
||||
}
|
||||
style={[styles.spacing, { width: "80%" }]}
|
||||
>
|
||||
Save Progress ✨
|
||||
</Button>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
borderBottomWidth: 1,
|
||||
borderColor: "#f57c00",
|
||||
marginVertical: 10,
|
||||
}}
|
||||
/>
|
||||
</ScrollView>
|
||||
|
||||
<Snackbar
|
||||
visible={isVisibleSnackbar}
|
||||
onDismiss={onDismissSnackbar}
|
||||
duration={2_000}
|
||||
>
|
||||
✅ Habit Saved successfully!
|
||||
</Snackbar>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
spacing: {
|
||||
marginVertical: 16,
|
||||
},
|
||||
})
|
@ -1,16 +1,17 @@
|
||||
import type { IconName } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-native-fontawesome"
|
||||
import { useRouter } from "expo-router"
|
||||
import { Link, useRouter } from "expo-router"
|
||||
import type LottieView from "lottie-react-native"
|
||||
import { useState } from "react"
|
||||
import { View } from "react-native"
|
||||
import { Checkbox, List, Text } from "react-native-paper"
|
||||
import { Button, Checkbox, List, Text } from "react-native-paper"
|
||||
|
||||
import type { GoalBoolean } from "@/domain/entities/Goal"
|
||||
import { GoalBooleanProgress } from "@/domain/entities/Goal"
|
||||
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
||||
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
|
||||
import { getColorRGBAFromHex } from "@/utils/colors"
|
||||
import { getISODate } from "@/utils/dates"
|
||||
|
||||
export interface HabitCardProps {
|
||||
habitHistory: HabitHistory
|
||||
@ -80,14 +81,32 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
||||
}}
|
||||
right={() => {
|
||||
if (goalProgress.isNumeric()) {
|
||||
const href = {
|
||||
pathname: "/application/habits/[habitId]/progress/[selectedDate]/",
|
||||
params: {
|
||||
habitId: habit.id,
|
||||
selectedDate: getISODate(selectedDate),
|
||||
},
|
||||
}
|
||||
return (
|
||||
<Link href={href}>
|
||||
<View>
|
||||
<Text>
|
||||
{goalProgress.progress.toLocaleString()} /{" "}
|
||||
{goalProgress.goal.target.value.toLocaleString()}{" "}
|
||||
{goalProgress.goal.target.unit}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
mode="elevated"
|
||||
onPress={() => {
|
||||
router.push(href)
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</View>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user