feat: update habit progress boolean
This commit is contained in:
parent
d75a8ab2cd
commit
867667f4c7
@ -1,6 +1,7 @@
|
|||||||
import type { GoalFrequency } from "./Goal"
|
import type { GoalFrequency } from "./Goal"
|
||||||
import type { Habit } from "./Habit"
|
import type { Habit } from "./Habit"
|
||||||
import { HabitHistory } from "./HabitHistory"
|
import { HabitHistory } from "./HabitHistory"
|
||||||
|
import type { HabitProgress } from "./HabitProgress"
|
||||||
|
|
||||||
export interface HabitsTrackerData {
|
export interface HabitsTrackerData {
|
||||||
habitsHistory: {
|
habitsHistory: {
|
||||||
@ -35,24 +36,6 @@ export class HabitsTracker implements HabitsTrackerData {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// public setHabitProgress(options: SetHabitProgressOptions): void {
|
|
||||||
// const { date, goalProgress, habitHistory } = options
|
|
||||||
// if (goalProgress.isBoolean()) {
|
|
||||||
// const currentHabitProgress = habitHistory.getProgressesByDate(date)[0]
|
|
||||||
// if (currentHabitProgress == null) {
|
|
||||||
// habitHistory.progressHistory = [
|
|
||||||
// ...habitHistory.progressHistory,
|
|
||||||
// new HabitProgress({
|
|
||||||
// date,
|
|
||||||
// goalProgress,
|
|
||||||
// habitId,
|
|
||||||
// id,
|
|
||||||
// }),
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
public editHabit(habit: Habit): void {
|
public editHabit(habit: Habit): void {
|
||||||
const habitHistory = this.getHabitHistoryById(habit.id)
|
const habitHistory = this.getHabitHistoryById(habit.id)
|
||||||
if (habitHistory == null) {
|
if (habitHistory == null) {
|
||||||
@ -61,6 +44,25 @@ export class HabitsTracker implements HabitsTrackerData {
|
|||||||
habitHistory.habit = habit
|
habitHistory.habit = habit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateHabitProgress(habitProgress: HabitProgress): void {
|
||||||
|
const habitHistory = this.getHabitHistoryById(habitProgress.habitId)
|
||||||
|
if (habitHistory == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const habitProgressSaved = habitHistory.progressHistory.find((progress) => {
|
||||||
|
return progress.id === habitProgress.id
|
||||||
|
})
|
||||||
|
if (habitProgressSaved == null) {
|
||||||
|
habitHistory.progressHistory = [
|
||||||
|
...habitHistory.progressHistory,
|
||||||
|
habitProgress,
|
||||||
|
]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
habitProgressSaved.goalProgress = habitProgress.goalProgress
|
||||||
|
habitProgressSaved.date = habitProgress.date
|
||||||
|
}
|
||||||
|
|
||||||
public getAllHabitsHistory(): HabitHistory[] {
|
public getAllHabitsHistory(): HabitHistory[] {
|
||||||
return [
|
return [
|
||||||
...this.habitsHistory.daily,
|
...this.habitsHistory.daily,
|
||||||
|
@ -4,7 +4,7 @@ import type {
|
|||||||
} from "../entities/HabitProgress"
|
} from "../entities/HabitProgress"
|
||||||
|
|
||||||
export interface HabitProgressUpdateOptions {
|
export interface HabitProgressUpdateOptions {
|
||||||
habitProgressData: HabitProgressData
|
habitProgressData: Omit<HabitProgressData, "habitId">
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HabitProgressUpdateRepository {
|
export interface HabitProgressUpdateRepository {
|
||||||
|
@ -1,28 +1,56 @@
|
|||||||
import type { GoalProgress } from "../entities/Goal"
|
import type { GoalProgress } from "../entities/Goal"
|
||||||
import type { HabitHistory } from "../entities/HabitHistory"
|
import type { HabitHistory } from "../entities/HabitHistory"
|
||||||
|
import type { HabitProgress } from "../entities/HabitProgress"
|
||||||
import type { HabitProgressCreateRepository } from "../repositories/HabitProgressCreate"
|
import type { HabitProgressCreateRepository } from "../repositories/HabitProgressCreate"
|
||||||
import type { HabitProgressUpdateRepository } from "../repositories/HabitProgressUpdate"
|
import type { HabitProgressUpdateRepository } from "../repositories/HabitProgressUpdate"
|
||||||
|
|
||||||
export interface HabitGoalProgressUpdateOptions {
|
export interface HabitGoalProgressUpdateUseCaseOptions {
|
||||||
date: Date
|
date: Date
|
||||||
goalProgress: GoalProgress
|
goalProgress: GoalProgress
|
||||||
habitHistory: HabitHistory
|
habitHistory: HabitHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HabitGoalProgressUpdateUseCase
|
export interface HabitGoalProgressUpdateUseCaseDependencyOptions {
|
||||||
implements HabitGoalProgressUpdateOptions
|
habitProgressCreateRepository: HabitProgressCreateRepository
|
||||||
{
|
habitProgressUpdateRepository: HabitProgressUpdateRepository
|
||||||
public date: Date
|
}
|
||||||
public goalProgress: GoalProgress
|
|
||||||
public habitHistory: HabitHistory
|
|
||||||
|
|
||||||
public constructor(option: HabitGoalProgressUpdateOptions) {
|
export class HabitGoalProgressUpdateUseCase
|
||||||
this.date = option.date
|
implements HabitGoalProgressUpdateUseCaseDependencyOptions
|
||||||
this.goalProgress = option.goalProgress
|
{
|
||||||
this.habitHistory = option.habitHistory
|
public habitProgressCreateRepository: HabitProgressCreateRepository
|
||||||
|
public habitProgressUpdateRepository: HabitProgressUpdateRepository
|
||||||
|
|
||||||
|
public constructor(options: HabitGoalProgressUpdateUseCaseDependencyOptions) {
|
||||||
|
this.habitProgressCreateRepository = options.habitProgressCreateRepository
|
||||||
|
this.habitProgressUpdateRepository = options.habitProgressUpdateRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
public async execute(data: unknown): Promise<HabitHistory> {
|
public async execute(
|
||||||
//
|
options: HabitGoalProgressUpdateUseCaseOptions,
|
||||||
|
): Promise<HabitProgress> {
|
||||||
|
const { date, goalProgress, habitHistory } = options
|
||||||
|
|
||||||
|
if (goalProgress.isBoolean()) {
|
||||||
|
const currentHabitProgress = habitHistory.getProgressesByDate(date)[0]
|
||||||
|
if (currentHabitProgress == null) {
|
||||||
|
return await this.habitProgressCreateRepository.execute({
|
||||||
|
habitProgressData: {
|
||||||
|
date,
|
||||||
|
goalProgress,
|
||||||
|
habitId: habitHistory.habit.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await this.habitProgressUpdateRepository.execute({
|
||||||
|
habitProgressData: {
|
||||||
|
date,
|
||||||
|
goalProgress,
|
||||||
|
id: currentHabitProgress.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Not implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,9 @@ import { HabitCreateSupabaseRepository } from "./supabase/repositories/HabitCrea
|
|||||||
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
import { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
||||||
import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
|
import { HabitEditSupabaseRepository } from "./supabase/repositories/HabitEdit"
|
||||||
import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
import { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
||||||
|
import { HabitProgressCreateSupabaseRepository } from "./supabase/repositories/HabitProgressCreate"
|
||||||
|
import { HabitProgressUpdateSupabaseRepository } from "./supabase/repositories/HabitProgressUpdate"
|
||||||
|
import { HabitGoalProgressUpdateUseCase } from "@/domain/use-cases/HabitGoalProgressUpdate"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repositories
|
* Repositories
|
||||||
@ -30,6 +33,16 @@ const habitCreateRepository = new HabitCreateSupabaseRepository({
|
|||||||
const habitEditRepository = new HabitEditSupabaseRepository({
|
const habitEditRepository = new HabitEditSupabaseRepository({
|
||||||
supabaseClient,
|
supabaseClient,
|
||||||
})
|
})
|
||||||
|
const habitProgressCreateRepository = new HabitProgressCreateSupabaseRepository(
|
||||||
|
{
|
||||||
|
supabaseClient,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const habitProgressUpdateRepository = new HabitProgressUpdateSupabaseRepository(
|
||||||
|
{
|
||||||
|
supabaseClient,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use Cases
|
* Use Cases
|
||||||
@ -47,6 +60,10 @@ const retrieveHabitsTrackerUseCase = new RetrieveHabitsTrackerUseCase({
|
|||||||
const habitEditUseCase = new HabitEditUseCase({
|
const habitEditUseCase = new HabitEditUseCase({
|
||||||
habitEditRepository,
|
habitEditRepository,
|
||||||
})
|
})
|
||||||
|
const habitGoalProgressUpdateUseCase = new HabitGoalProgressUpdateUseCase({
|
||||||
|
habitProgressCreateRepository,
|
||||||
|
habitProgressUpdateRepository,
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenters
|
* Presenters
|
||||||
@ -58,4 +75,5 @@ export const habitsTrackerPresenter = new HabitsTrackerPresenter({
|
|||||||
retrieveHabitsTrackerUseCase,
|
retrieveHabitsTrackerUseCase,
|
||||||
habitCreateUseCase,
|
habitCreateUseCase,
|
||||||
habitEditUseCase,
|
habitEditUseCase,
|
||||||
|
habitGoalProgressUpdateUseCase,
|
||||||
})
|
})
|
||||||
|
38
infrastructure/supabase/repositories/HabitProgressCreate.ts
Normal file
38
infrastructure/supabase/repositories/HabitProgressCreate.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { HabitProgressCreateRepository } from "@/domain/repositories/HabitProgressCreate"
|
||||||
|
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||||
|
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||||
|
|
||||||
|
export class HabitProgressCreateSupabaseRepository
|
||||||
|
extends SupabaseRepository
|
||||||
|
implements HabitProgressCreateRepository
|
||||||
|
{
|
||||||
|
public execute: HabitProgressCreateRepository["execute"] = async (
|
||||||
|
options,
|
||||||
|
) => {
|
||||||
|
const { habitProgressData } = options
|
||||||
|
const { goalProgress, date, habitId } = habitProgressData
|
||||||
|
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
||||||
|
if (goalProgress.isNumeric()) {
|
||||||
|
goalProgressValue = goalProgress.progress
|
||||||
|
}
|
||||||
|
const { data, error } = await this.supabaseClient
|
||||||
|
.from("habits_progresses")
|
||||||
|
.insert({
|
||||||
|
habit_id: Number(habitId),
|
||||||
|
date: date.toISOString(),
|
||||||
|
goal_progress: goalProgressValue,
|
||||||
|
})
|
||||||
|
.select("*")
|
||||||
|
const insertedProgress = data?.[0]
|
||||||
|
if (error != null || insertedProgress == null) {
|
||||||
|
throw new Error(error?.message ?? "Failed to create habit progress.")
|
||||||
|
}
|
||||||
|
const habitProgress = new HabitProgress({
|
||||||
|
id: insertedProgress.id.toString(),
|
||||||
|
habitId: insertedProgress.habit_id.toString(),
|
||||||
|
date: new Date(insertedProgress.date),
|
||||||
|
goalProgress,
|
||||||
|
})
|
||||||
|
return habitProgress
|
||||||
|
}
|
||||||
|
}
|
38
infrastructure/supabase/repositories/HabitProgressUpdate.ts
Normal file
38
infrastructure/supabase/repositories/HabitProgressUpdate.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { HabitProgressUpdateRepository } from "@/domain/repositories/HabitProgressUpdate"
|
||||||
|
import { SupabaseRepository } from "./_SupabaseRepository"
|
||||||
|
import { HabitProgress } from "@/domain/entities/HabitProgress"
|
||||||
|
|
||||||
|
export class HabitProgressUpdateSupabaseRepository
|
||||||
|
extends SupabaseRepository
|
||||||
|
implements HabitProgressUpdateRepository
|
||||||
|
{
|
||||||
|
public execute: HabitProgressUpdateRepository["execute"] = async (
|
||||||
|
options,
|
||||||
|
) => {
|
||||||
|
const { habitProgressData } = options
|
||||||
|
const { id, goalProgress, date } = habitProgressData
|
||||||
|
let goalProgressValue = goalProgress.isCompleted() ? 1 : 0
|
||||||
|
if (goalProgress.isNumeric()) {
|
||||||
|
goalProgressValue = goalProgress.progress
|
||||||
|
}
|
||||||
|
const { data, error } = await this.supabaseClient
|
||||||
|
.from("habits_progresses")
|
||||||
|
.update({
|
||||||
|
date: date.toISOString(),
|
||||||
|
goal_progress: goalProgressValue,
|
||||||
|
})
|
||||||
|
.eq("id", id)
|
||||||
|
.select("*")
|
||||||
|
const insertedProgress = data?.[0]
|
||||||
|
if (error != null || insertedProgress == null) {
|
||||||
|
throw new Error(error?.message ?? "Failed to update habit progress.")
|
||||||
|
}
|
||||||
|
const habitProgress = new HabitProgress({
|
||||||
|
id: insertedProgress.id.toString(),
|
||||||
|
habitId: insertedProgress.habit_id.toString(),
|
||||||
|
date: new Date(insertedProgress.date),
|
||||||
|
goalProgress,
|
||||||
|
})
|
||||||
|
return habitProgress
|
||||||
|
}
|
||||||
|
}
|
24
package-lock.json
generated
24
package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"expo-system-ui": "2.9.3",
|
"expo-system-ui": "2.9.3",
|
||||||
"expo-web-browser": "12.8.2",
|
"expo-web-browser": "12.8.2",
|
||||||
"immer": "10.0.4",
|
"immer": "10.0.4",
|
||||||
|
"lottie-react-native": "6.7.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "7.51.2",
|
"react-hook-form": "7.51.2",
|
||||||
@ -17868,6 +17869,29 @@
|
|||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lottie-react-native": {
|
||||||
|
"version": "6.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-6.7.2.tgz",
|
||||||
|
"integrity": "sha512-MZVx6N1EeO/EaSx8T44mJ0aHc5Mqee+xIfWwszni0oz8U2wlHdaWGjES44dHxaxgAp/0dRaFt3PkpZ6egTzcBg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@dotlottie/react-player": "^1.6.1",
|
||||||
|
"@lottiefiles/react-lottie-player": "^3.5.3",
|
||||||
|
"react": "*",
|
||||||
|
"react-native": ">=0.46",
|
||||||
|
"react-native-windows": ">=0.63.x"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@dotlottie/react-player": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@lottiefiles/react-lottie-player": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native-windows": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
"expo-system-ui": "2.9.3",
|
"expo-system-ui": "2.9.3",
|
||||||
"expo-web-browser": "12.8.2",
|
"expo-web-browser": "12.8.2",
|
||||||
"immer": "10.0.4",
|
"immer": "10.0.4",
|
||||||
|
"lottie-react-native": "6.7.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "7.51.2",
|
"react-hook-form": "7.51.2",
|
||||||
|
5382
presentation/assets/confetti.json
Normal file
5382
presentation/assets/confetti.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,10 @@ import type { HabitCreateData, HabitEditData } from "@/domain/entities/Habit"
|
|||||||
import { getErrorsFieldsFromZodError } from "../../utils/zod"
|
import { getErrorsFieldsFromZodError } from "../../utils/zod"
|
||||||
import type { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
import type { HabitCreateUseCase } from "@/domain/use-cases/HabitCreate"
|
||||||
import type { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
import type { HabitEditUseCase } from "@/domain/use-cases/HabitEdit"
|
||||||
|
import type {
|
||||||
|
HabitGoalProgressUpdateUseCase,
|
||||||
|
HabitGoalProgressUpdateUseCaseOptions,
|
||||||
|
} from "@/domain/use-cases/HabitGoalProgressUpdate"
|
||||||
|
|
||||||
export interface HabitsTrackerPresenterState {
|
export interface HabitsTrackerPresenterState {
|
||||||
habitsTracker: HabitsTracker
|
habitsTracker: HabitsTracker
|
||||||
@ -34,12 +38,17 @@ export interface HabitsTrackerPresenterState {
|
|||||||
global: ErrorGlobal
|
global: ErrorGlobal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
habitGoalProgressUpdate: {
|
||||||
|
state: FetchState
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HabitsTrackerPresenterOptions {
|
export interface HabitsTrackerPresenterOptions {
|
||||||
retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
|
retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
|
||||||
habitCreateUseCase: HabitCreateUseCase
|
habitCreateUseCase: HabitCreateUseCase
|
||||||
habitEditUseCase: HabitEditUseCase
|
habitEditUseCase: HabitEditUseCase
|
||||||
|
habitGoalProgressUpdateUseCase: HabitGoalProgressUpdateUseCase
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HabitsTrackerPresenter
|
export class HabitsTrackerPresenter
|
||||||
@ -49,12 +58,14 @@ export class HabitsTrackerPresenter
|
|||||||
public retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
|
public retrieveHabitsTrackerUseCase: RetrieveHabitsTrackerUseCase
|
||||||
public habitCreateUseCase: HabitCreateUseCase
|
public habitCreateUseCase: HabitCreateUseCase
|
||||||
public habitEditUseCase: HabitEditUseCase
|
public habitEditUseCase: HabitEditUseCase
|
||||||
|
public habitGoalProgressUpdateUseCase: HabitGoalProgressUpdateUseCase
|
||||||
|
|
||||||
public constructor(options: HabitsTrackerPresenterOptions) {
|
public constructor(options: HabitsTrackerPresenterOptions) {
|
||||||
const {
|
const {
|
||||||
retrieveHabitsTrackerUseCase,
|
retrieveHabitsTrackerUseCase,
|
||||||
habitCreateUseCase,
|
habitCreateUseCase,
|
||||||
habitEditUseCase,
|
habitEditUseCase,
|
||||||
|
habitGoalProgressUpdateUseCase,
|
||||||
} = options
|
} = options
|
||||||
const habitsTracker = HabitsTracker.default()
|
const habitsTracker = HabitsTracker.default()
|
||||||
super({
|
super({
|
||||||
@ -74,10 +85,14 @@ export class HabitsTrackerPresenter
|
|||||||
global: null,
|
global: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
habitGoalProgressUpdate: {
|
||||||
|
state: "idle",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
this.retrieveHabitsTrackerUseCase = retrieveHabitsTrackerUseCase
|
this.retrieveHabitsTrackerUseCase = retrieveHabitsTrackerUseCase
|
||||||
this.habitCreateUseCase = habitCreateUseCase
|
this.habitCreateUseCase = habitCreateUseCase
|
||||||
this.habitEditUseCase = habitEditUseCase
|
this.habitEditUseCase = habitEditUseCase
|
||||||
|
this.habitGoalProgressUpdateUseCase = habitGoalProgressUpdateUseCase
|
||||||
}
|
}
|
||||||
|
|
||||||
public async habitCreate(data: unknown): Promise<FetchState> {
|
public async habitCreate(data: unknown): Promise<FetchState> {
|
||||||
@ -158,4 +173,26 @@ export class HabitsTrackerPresenter
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async habitUpdateProgress(
|
||||||
|
options: HabitGoalProgressUpdateUseCaseOptions,
|
||||||
|
): Promise<FetchState> {
|
||||||
|
try {
|
||||||
|
this.setState((state) => {
|
||||||
|
state.habitGoalProgressUpdate.state = "loading"
|
||||||
|
})
|
||||||
|
const habitProgress =
|
||||||
|
await this.habitGoalProgressUpdateUseCase.execute(options)
|
||||||
|
this.setState((state) => {
|
||||||
|
state.habitsTracker.updateHabitProgress(habitProgress)
|
||||||
|
state.habitGoalProgressUpdate.state = "success"
|
||||||
|
})
|
||||||
|
return "success"
|
||||||
|
} catch (error) {
|
||||||
|
this.setState((state) => {
|
||||||
|
state.habitGoalProgressUpdate.state = "error"
|
||||||
|
})
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
import { useState } from "react"
|
|
||||||
import { Calendar } from "react-native-calendars"
|
|
||||||
import { Text } from "react-native-paper"
|
|
||||||
import { SafeAreaView } from "react-native-safe-area-context"
|
|
||||||
|
|
||||||
export const HabitHistory: React.FC = () => {
|
|
||||||
const [selected, setSelected] = useState("")
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeAreaView
|
|
||||||
style={[
|
|
||||||
{
|
|
||||||
flex: 1,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Calendar
|
|
||||||
onDayPress={(day) => {
|
|
||||||
setSelected(day.dateString)
|
|
||||||
}}
|
|
||||||
markedDates={{
|
|
||||||
"2023-03-01": { selected: true, marked: true, selectedColor: "blue" },
|
|
||||||
"2023-03-02": { marked: true },
|
|
||||||
"2023-03-03": { selected: true, marked: true, selectedColor: "blue" },
|
|
||||||
[selected]: {
|
|
||||||
selected: true,
|
|
||||||
disableTouchEvent: true,
|
|
||||||
selectedColor: "orange",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
theme={{
|
|
||||||
backgroundColor: "#000000",
|
|
||||||
calendarBackground: "#000000",
|
|
||||||
textSectionTitleColor: "#b6c1cd",
|
|
||||||
selectedDayBackgroundColor: "#00adf5",
|
|
||||||
selectedDayTextColor: "#ffffff",
|
|
||||||
todayTextColor: "#00adf5",
|
|
||||||
dayTextColor: "#2d4150",
|
|
||||||
textDisabledColor: "#d9efff",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text>{selected}</Text>
|
|
||||||
</SafeAreaView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
<Agenda
|
|
||||||
// The list of items that have to be displayed in agenda. If you want to render item as empty date
|
|
||||||
// the value of date key has to be an empty array []. If there exists no value for date key it is
|
|
||||||
// considered that the date in question is not yet loaded
|
|
||||||
items={{
|
|
||||||
'2012-05-22': [{name: 'item 1 - any js object'}],
|
|
||||||
'2012-05-23': [{name: 'item 2 - any js object', height: 80}],
|
|
||||||
'2012-05-24': [],
|
|
||||||
'2012-05-25': [{name: 'item 3 - any js object'}, {name: 'any js object'}]
|
|
||||||
}}
|
|
||||||
// Callback that gets called when items for a certain month should be loaded (month became visible)
|
|
||||||
loadItemsForMonth={month => {
|
|
||||||
console.log('trigger items loading');
|
|
||||||
}}
|
|
||||||
// Callback that fires when the calendar is opened or closed
|
|
||||||
onCalendarToggled={calendarOpened => {
|
|
||||||
console.log(calendarOpened);
|
|
||||||
}}
|
|
||||||
// Callback that gets called on day press
|
|
||||||
onDayPress={day => {
|
|
||||||
console.log('day pressed');
|
|
||||||
}}
|
|
||||||
// Callback that gets called when day changes while scrolling agenda list
|
|
||||||
onDayChange={day => {
|
|
||||||
console.log('day changed');
|
|
||||||
}}
|
|
||||||
// Initially selected day
|
|
||||||
selected={'2012-05-16'}
|
|
||||||
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
|
|
||||||
minDate={'2012-05-10'}
|
|
||||||
// Maximum date that can be selected, dates after maxDate will be grayed out. Default = undefined
|
|
||||||
maxDate={'2012-05-30'}
|
|
||||||
// Max amount of months allowed to scroll to the past. Default = 50
|
|
||||||
pastScrollRange={50}
|
|
||||||
// Max amount of months allowed to scroll to the future. Default = 50
|
|
||||||
futureScrollRange={50}
|
|
||||||
// Specify how each item should be rendered in agenda
|
|
||||||
renderItem={(item, firstItemInDay) => {
|
|
||||||
return <View />;
|
|
||||||
}}
|
|
||||||
// Specify how each date should be rendered. day can be undefined if the item is not first in that day
|
|
||||||
renderDay={(day, item) => {
|
|
||||||
return <View />;
|
|
||||||
}}
|
|
||||||
// Specify how empty date content with no items should be rendered
|
|
||||||
renderEmptyDate={() => {
|
|
||||||
return <View />;
|
|
||||||
}}
|
|
||||||
// Specify how agenda knob should look like
|
|
||||||
renderKnob={() => {
|
|
||||||
return <View />;
|
|
||||||
}}
|
|
||||||
// Override inner list with a custom implemented component
|
|
||||||
renderList={listProps => {
|
|
||||||
return <MyCustomList {...listProps} />;
|
|
||||||
}}
|
|
||||||
// Specify what should be rendered instead of ActivityIndicator
|
|
||||||
renderEmptyData={() => {
|
|
||||||
return <View />;
|
|
||||||
}}
|
|
||||||
// Specify your item comparison function for increased performance
|
|
||||||
rowHasChanged={(r1, r2) => {
|
|
||||||
return r1.text !== r2.text;
|
|
||||||
}}
|
|
||||||
// Hide knob button. Default = false
|
|
||||||
hideKnob={true}
|
|
||||||
// When `true` and `hideKnob` prop is `false`, the knob will always be visible and the user will be able to drag the knob up and close the calendar. Default = false
|
|
||||||
showClosingKnob={false}
|
|
||||||
// By default, agenda dates are marked if they have at least one item, but you can override this if needed
|
|
||||||
markedDates={{
|
|
||||||
'2012-05-16': {selected: true, marked: true},
|
|
||||||
'2012-05-17': {marked: true},
|
|
||||||
'2012-05-18': {disabled: true}
|
|
||||||
}}
|
|
||||||
// If disabledByDefault={true} dates flagged as not disabled will be enabled. Default = false
|
|
||||||
disabledByDefault={true}
|
|
||||||
// If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly
|
|
||||||
onRefresh={() => console.log('refreshing...')}
|
|
||||||
// Set this true while waiting for new data from a refresh
|
|
||||||
refreshing={false}
|
|
||||||
// Add a custom RefreshControl component, used to provide pull-to-refresh functionality for the ScrollView
|
|
||||||
refreshControl={null}
|
|
||||||
// Agenda theme
|
|
||||||
theme={{
|
|
||||||
...calendarTheme,
|
|
||||||
agendaDayTextColor: 'yellow',
|
|
||||||
agendaDayNumColor: 'green',
|
|
||||||
agendaTodayColor: 'red',
|
|
||||||
agendaKnobColor: 'blue'
|
|
||||||
}}
|
|
||||||
// Agenda container style
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
*/
|
|
@ -1,30 +1,37 @@
|
|||||||
import FontAwesome6 from "@expo/vector-icons/FontAwesome6"
|
import FontAwesome6 from "@expo/vector-icons/FontAwesome6"
|
||||||
import { useRouter } from "expo-router"
|
import { useRouter } from "expo-router"
|
||||||
import { View } from "react-native"
|
|
||||||
import { List, Text, Checkbox } from "react-native-paper"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
import { View } from "react-native"
|
||||||
|
import { Checkbox, List, Text } from "react-native-paper"
|
||||||
|
import type LottieView from "lottie-react-native"
|
||||||
|
|
||||||
import type { GoalProgress } from "@/domain/entities/Goal"
|
import type { GoalBoolean } from "@/domain/entities/Goal"
|
||||||
import type { Habit } from "@/domain/entities/Habit"
|
import { GoalBooleanProgress } from "@/domain/entities/Goal"
|
||||||
|
import type { HabitHistory } from "@/domain/entities/HabitHistory"
|
||||||
import { getColorRGBAFromHex } from "@/utils/colors"
|
import { getColorRGBAFromHex } from "@/utils/colors"
|
||||||
|
import { useHabitsTracker } from "../../contexts/HabitsTracker"
|
||||||
|
|
||||||
export interface HabitCardProps {
|
export interface HabitCardProps {
|
||||||
habit: Habit
|
habitHistory: HabitHistory
|
||||||
goalProgress: GoalProgress
|
selectedDate: Date
|
||||||
|
confettiRef: React.MutableRefObject<LottieView | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
||||||
const { habit, goalProgress } = props
|
const { habitHistory, selectedDate, confettiRef } = props
|
||||||
|
const { habit } = habitHistory
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { habitsTrackerPresenter } = useHabitsTracker()
|
||||||
|
|
||||||
|
const goalProgress = habitHistory.getGoalProgressByDate(selectedDate)
|
||||||
|
const [checked, setChecked] = useState(goalProgress.isCompleted())
|
||||||
|
|
||||||
const habitColor = getColorRGBAFromHex({
|
const habitColor = getColorRGBAFromHex({
|
||||||
hexColor: habit.color,
|
hexColor: habit.color,
|
||||||
opacity: 0.4,
|
opacity: 0.4,
|
||||||
})
|
})
|
||||||
|
|
||||||
const [checked, setChecked] = useState(goalProgress.isCompleted())
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@ -57,6 +64,7 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
|||||||
]}
|
]}
|
||||||
left={() => {
|
left={() => {
|
||||||
return (
|
return (
|
||||||
|
<View style={{ justifyContent: "center", alignItems: "center" }}>
|
||||||
<FontAwesome6
|
<FontAwesome6
|
||||||
size={24}
|
size={24}
|
||||||
name={habit.icon}
|
name={habit.icon}
|
||||||
@ -66,6 +74,7 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
right={() => {
|
right={() => {
|
||||||
@ -82,14 +91,25 @@ export const HabitCard: React.FC<HabitCardProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
color="black"
|
||||||
status={checked ? "checked" : "unchecked"}
|
status={checked ? "checked" : "unchecked"}
|
||||||
onPress={() => {
|
onPress={async () => {
|
||||||
setChecked(!checked)
|
const isCheckedNew = !checked
|
||||||
|
setChecked(isCheckedNew)
|
||||||
|
if (isCheckedNew) {
|
||||||
|
confettiRef.current?.play()
|
||||||
|
}
|
||||||
|
await habitsTrackerPresenter.habitUpdateProgress({
|
||||||
|
date: selectedDate,
|
||||||
|
habitHistory,
|
||||||
|
goalProgress: new GoalBooleanProgress({
|
||||||
|
goal: habit.goal as GoalBoolean,
|
||||||
|
progress: isCheckedNew,
|
||||||
|
}),
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { useState } from "react"
|
import LottieView from "lottie-react-native"
|
||||||
import { Dimensions, ScrollView } from "react-native"
|
import { useRef, useState } from "react"
|
||||||
|
import { Dimensions, ScrollView, View } from "react-native"
|
||||||
import { Divider, List } from "react-native-paper"
|
import { Divider, List } from "react-native-paper"
|
||||||
|
|
||||||
import type { GoalFrequency } from "@/domain/entities/Goal"
|
import type { GoalFrequency } from "@/domain/entities/Goal"
|
||||||
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
|
||||||
|
import confettiJSON from "../../../assets/confetti.json"
|
||||||
import { capitalize } from "@/utils/strings"
|
import { capitalize } from "@/utils/strings"
|
||||||
import { HabitCard } from "./HabitCard"
|
import { HabitCard } from "./HabitCard"
|
||||||
|
|
||||||
@ -24,7 +26,39 @@ export const HabitsList: React.FC<HabitsListProps> = (props) => {
|
|||||||
monthly: true,
|
monthly: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const confettiRef = useRef<LottieView | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<View
|
||||||
|
pointerEvents="none"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
zIndex: 100,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LottieView
|
||||||
|
ref={confettiRef}
|
||||||
|
source={confettiJSON}
|
||||||
|
autoPlay={false}
|
||||||
|
loop={false}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
resizeMode="cover"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
style={{
|
style={{
|
||||||
@ -34,6 +68,7 @@ export const HabitsList: React.FC<HabitsListProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<List.Section>
|
<List.Section>
|
||||||
{frequenciesFiltered.map((frequency) => {
|
{frequenciesFiltered.map((frequency) => {
|
||||||
return (
|
return (
|
||||||
@ -56,12 +91,12 @@ export const HabitsList: React.FC<HabitsListProps> = (props) => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{habitsTracker.habitsHistory[frequency].map((item) => {
|
{habitsTracker.habitsHistory[frequency].map((item) => {
|
||||||
const goalProgress = item.getGoalProgressByDate(selectedDate)
|
|
||||||
return (
|
return (
|
||||||
<HabitCard
|
<HabitCard
|
||||||
habit={item.habit}
|
habitHistory={item}
|
||||||
goalProgress={goalProgress}
|
selectedDate={selectedDate}
|
||||||
key={item.habit.id}
|
key={item.habit.id + selectedDate.toISOString()}
|
||||||
|
confettiRef={confettiRef}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -70,5 +105,6 @@ export const HabitsList: React.FC<HabitsListProps> = (props) => {
|
|||||||
})}
|
})}
|
||||||
</List.Section>
|
</List.Section>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user