Merge branch 'feat/stats' into 'develop'

feat: habits statistics

See merge request rrll/p61-project!6
This commit is contained in:
Théo LUDWIG 2024-05-22 11:46:14 +00:00
commit d98f3144cb
13 changed files with 612 additions and 195 deletions

View File

@ -1,4 +1,5 @@
{ {
"root": true,
"extends": [ "extends": [
"conventions", "conventions",
"plugin:react/recommended", "plugin:react/recommended",
@ -15,9 +16,6 @@
"version": "detect" "version": "detect"
} }
}, },
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": { "rules": {
"react/react-in-jsx-scope": "off", "react/react-in-jsx-scope": "off",
"react/prop-types": "off", "react/prop-types": "off",
@ -35,8 +33,9 @@
{ {
"files": ["*.ts", "*.tsx"], "files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"rules": { "plugins": ["@typescript-eslint"],
"@typescript-eslint/member-delimiter-style": "off" "parserOptions": {
"project": "./tsconfig.json"
} }
} }
] ]

View File

@ -45,11 +45,11 @@ const TabLayout: React.FC = () => {
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="habits/history" name="habits/statistics"
options={{ options={{
title: "History", title: "Statistics",
tabBarIcon: ({ color }) => { tabBarIcon: ({ color }) => {
return <TabBarIcon name="history" color={color} /> return <TabBarIcon name="line-chart" color={color} />
}, },
}} }}
/> />

View File

@ -1,50 +0,0 @@
import { useMemo, useState } from "react"
import { View } from "react-native"
import { Agenda } from "react-native-calendars"
import { Text } from "react-native-paper"
import { SafeAreaView } from "react-native-safe-area-context"
import { getISODate, getNowDateUTC } from "@/utils/dates"
const HistoryPage: React.FC = () => {
const today = useMemo(() => {
return getNowDateUTC()
}, [])
const todayISO = getISODate(today)
const [selectedDate, setSelectedDate] = useState<Date>(today)
const selectedISODate = getISODate(selectedDate)
return (
<SafeAreaView
style={[
{
flex: 1,
backgroundColor: "white",
},
]}
>
<Agenda
firstDay={1}
showClosingKnob
showOnlySelectedDayItems
onDayPress={(date) => {
setSelectedDate(new Date(date.dateString))
}}
markedDates={{
[todayISO]: { marked: true },
}}
selected={selectedISODate}
renderList={() => {
return (
<View>
<Text>{selectedDate.toISOString()}</Text>
</View>
)
}}
/>
</SafeAreaView>
)
}
export default HistoryPage

View File

@ -0,0 +1,23 @@
import { SafeAreaView } from "react-native-safe-area-context"
import { HabitsStatistics } from "@/presentation/react-native/components/HabitsStatistics"
import { useHabitsTracker } from "@/presentation/react/contexts/HabitsTracker"
const StatisticsPage: React.FC = () => {
const { habitsTracker } = useHabitsTracker()
return (
<SafeAreaView
style={[
{
flex: 1,
backgroundColor: "white",
},
]}
>
<HabitsStatistics habitsTracker={habitsTracker} />
</SafeAreaView>
)
}
export default StatisticsPage

View File

@ -11,6 +11,12 @@ export const GOAL_FREQUENCIES = GOAL_FREQUENCIES_ZOD.map((frequency) => {
}) })
export type GoalFrequency = (typeof GOAL_FREQUENCIES)[number] export type GoalFrequency = (typeof GOAL_FREQUENCIES)[number]
export const GOAL_FREQUENCIES_TYPES = {
daily: "day",
weekly: "week",
monthly: "month",
} as const
export const GOAL_TYPES_ZOD = [ export const GOAL_TYPES_ZOD = [
z.literal("boolean"), z.literal("boolean"),
z.literal("numeric"), z.literal("numeric"),

View File

@ -96,4 +96,29 @@ export class HabitsTracker implements HabitsTrackerData {
) )
}) })
} }
public getHabitsStatisticsByDateAndFrequency({
selectedDate,
frequency,
}: {
selectedDate: Date
frequency: GoalFrequency
}): {
totalGoalsSuccess: number
totalGoals: number
} {
const habitsHistory = this.getHabitsHistoriesByDate({
selectedDate,
frequency,
})
let totalGoalsSuccess = 0
const totalGoals = habitsHistory.length
for (const habitHistory of habitsHistory) {
const goalProgress = habitHistory.getGoalProgressByDate(selectedDate)
if (goalProgress.isCompleted()) {
totalGoalsSuccess++
}
}
return { totalGoalsSuccess, totalGoals }
}
} }

465
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@fortawesome/fontawesome-svg-core": "6.5.2", "@fortawesome/fontawesome-svg-core": "6.5.2",
"@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/free-solid-svg-icons": "6.5.2",
"@fortawesome/react-native-fontawesome": "0.3.1", "@fortawesome/react-native-fontawesome": "0.3.1",
"@hookform/resolvers": "3.4.0", "@hookform/resolvers": "3.4.2",
"@react-native-async-storage/async-storage": "1.23.1", "@react-native-async-storage/async-storage": "1.23.1",
"@react-navigation/native": "6.1.17", "@react-navigation/native": "6.1.17",
"@supabase/supabase-js": "2.43.2", "@supabase/supabase-js": "2.43.2",
@ -28,9 +28,10 @@
"lottie-react-native": "6.7.0", "lottie-react-native": "6.7.0",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "7.51.4", "react-hook-form": "7.51.5",
"react-native": "0.74.1", "react-native": "0.74.1",
"react-native-calendars": "1.1305.0", "react-native-calendars": "1.1305.0",
"react-native-circular-progress-indicator": "4.4.2",
"react-native-elements": "3.4.3", "react-native-elements": "3.4.3",
"react-native-gesture-handler": "2.16.2", "react-native-gesture-handler": "2.16.2",
"react-native-paper": "5.12.3", "react-native-paper": "5.12.3",
@ -56,21 +57,21 @@
"@types/node": "20.12.12", "@types/node": "20.12.12",
"@types/react": "18.2.79", "@types/react": "18.2.79",
"@types/react-test-renderer": "18.3.0", "@types/react-test-renderer": "18.3.0",
"@typescript-eslint/eslint-plugin": "7.9.0", "@typescript-eslint/eslint-plugin": "7.10.0",
"@typescript-eslint/parser": "7.9.0", "@typescript-eslint/parser": "7.10.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-config-conventions": "14.1.0", "eslint-config-conventions": "14.2.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.34.1", "eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-native": "4.1.0", "eslint-plugin-react-native": "4.1.0",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "53.0.0",
"husky": "9.0.11", "husky": "9.0.11",
"jest": "29.7.0", "jest": "29.7.0",
"jest-expo": "51.0.2", "jest-expo": "51.0.2",
"jest-junit": "16.0.0", "jest-junit": "16.0.0",
"lint-staged": "15.2.2", "lint-staged": "15.2.4",
"prettier": "3.2.5", "prettier": "3.2.5",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"supabase": "1.167.4", "supabase": "1.167.4",
@ -4361,9 +4362,9 @@
} }
}, },
"node_modules/@hookform/resolvers": { "node_modules/@hookform/resolvers": {
"version": "3.4.0", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.4.0.tgz", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.4.2.tgz",
"integrity": "sha512-+oAqK3okmoEDnvUkJ3N/mvNMeeMv5Apgy1jkoRmlaaAF4vBgcJs9tHvtXU7VE4DvPosvAUUkPOaNFunzt1dbgA==", "integrity": "sha512-1m9uAVIO8wVf7VCDAGsuGA0t6Z3m6jVGAN50HkV9vYLl0yixKK/Z1lr01vaRvYCkIKGoy1noVRxMzQYb4y/j1Q==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"react-hook-form": "^7.0.0" "react-hook-form": "^7.0.0"
@ -8750,9 +8751,9 @@
} }
}, },
"node_modules/@types/babel__traverse": { "node_modules/@types/babel__traverse": {
"version": "7.20.5", "version": "7.20.6",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
"integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -8965,17 +8966,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.10.0.tgz",
"integrity": "sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==", "integrity": "sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.9.0", "@typescript-eslint/scope-manager": "7.10.0",
"@typescript-eslint/type-utils": "7.9.0", "@typescript-eslint/type-utils": "7.10.0",
"@typescript-eslint/utils": "7.9.0", "@typescript-eslint/utils": "7.10.0",
"@typescript-eslint/visitor-keys": "7.9.0", "@typescript-eslint/visitor-keys": "7.10.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -8999,16 +9000,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.10.0.tgz",
"integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==", "integrity": "sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "7.9.0", "@typescript-eslint/scope-manager": "7.10.0",
"@typescript-eslint/types": "7.9.0", "@typescript-eslint/types": "7.10.0",
"@typescript-eslint/typescript-estree": "7.9.0", "@typescript-eslint/typescript-estree": "7.10.0",
"@typescript-eslint/visitor-keys": "7.9.0", "@typescript-eslint/visitor-keys": "7.10.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -9028,14 +9029,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz",
"integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==", "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.9.0", "@typescript-eslint/types": "7.10.0",
"@typescript-eslint/visitor-keys": "7.9.0" "@typescript-eslint/visitor-keys": "7.10.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -9046,14 +9047,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.10.0.tgz",
"integrity": "sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==", "integrity": "sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "7.9.0", "@typescript-eslint/typescript-estree": "7.10.0",
"@typescript-eslint/utils": "7.9.0", "@typescript-eslint/utils": "7.10.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.3.0" "ts-api-utils": "^1.3.0"
}, },
@ -9074,9 +9075,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz",
"integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==", "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -9088,14 +9089,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz",
"integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==", "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.9.0", "@typescript-eslint/types": "7.10.0",
"@typescript-eslint/visitor-keys": "7.9.0", "@typescript-eslint/visitor-keys": "7.10.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -9130,16 +9131,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.10.0.tgz",
"integrity": "sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==", "integrity": "sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "7.9.0", "@typescript-eslint/scope-manager": "7.10.0",
"@typescript-eslint/types": "7.9.0", "@typescript-eslint/types": "7.10.0",
"@typescript-eslint/typescript-estree": "7.9.0" "@typescript-eslint/typescript-estree": "7.10.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -9153,13 +9154,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "7.9.0", "version": "7.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz",
"integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==", "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.9.0", "@typescript-eslint/types": "7.10.0",
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
}, },
"engines": { "engines": {
@ -9245,6 +9246,12 @@
"node": ">=6.5" "node": ">=6.5"
} }
}, },
"node_modules/abs-svg-path": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
"integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==",
"license": "MIT"
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -9971,9 +9978,9 @@
} }
}, },
"node_modules/babel-plugin-react-native-web": { "node_modules/babel-plugin-react-native-web": {
"version": "0.19.11", "version": "0.19.12",
"resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.11.tgz", "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.12.tgz",
"integrity": "sha512-0sHf8GgDhsRZxGwlwHHdfL3U8wImFaLw4haEa60U9M3EiO3bg6u3BJ+1vXhwgrevqSq76rMb5j1HJs+dNvMj5g==", "integrity": "sha512-eYZ4+P6jNcB37lObWIg0pUbi7+3PKoU1Oie2j0C8UF3cXyXoR74tO2NBjI/FORb2LJyItJZEAmjU5pSaJYEL1w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/babel-plugin-transform-flow-enums": { "node_modules/babel-plugin-transform-flow-enums": {
@ -10182,12 +10189,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -10338,13 +10345,13 @@
} }
}, },
"node_modules/cacache/node_modules/glob": { "node_modules/cacache/node_modules/glob": {
"version": "10.3.15", "version": "10.3.16",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz",
"integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^2.3.6", "jackspeak": "^3.1.2",
"minimatch": "^9.0.1", "minimatch": "^9.0.1",
"minipass": "^7.0.4", "minipass": "^7.0.4",
"path-scurry": "^1.11.0" "path-scurry": "^1.11.0"
@ -10439,9 +10446,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001620", "version": "1.0.30001621",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz",
"integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -11985,9 +11992,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.774", "version": "1.4.777",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.774.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.777.tgz",
"integrity": "sha512-132O1XCd7zcTkzS3FgkAzKmnBuNJjK8WjcTtNuoylj7MYbqw5eXehjQ5OK91g0zm7OTKIPeaAG4CPoRfD9M1Mg==", "integrity": "sha512-n02NCwLJ3wexLfK/yQeqfywCblZqLcXphzmid5e8yVPdtEcida7li0A5WQKghHNG0FeOMCzeFOzEbtAh5riXFw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/emittery": { "node_modules/emittery": {
@ -12385,20 +12392,19 @@
} }
}, },
"node_modules/eslint-config-conventions": { "node_modules/eslint-config-conventions": {
"version": "14.1.0", "version": "14.2.0",
"resolved": "https://registry.npmjs.org/eslint-config-conventions/-/eslint-config-conventions-14.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-config-conventions/-/eslint-config-conventions-14.2.0.tgz",
"integrity": "sha512-oJOQcUfXD874XuJOCtoRmjuVKAJ+6TG92So0VDgwvsBx3R2pVAc34CmuvAhqamFeaNOkLTSFu/mVrnmZggzgIw==", "integrity": "sha512-f6BY4d5ibQfNY4MYRN5jat/bCZW/m/6lRXAca5cc3Q84DnTYwimby1GutBvnKfiA+/ZhIUcgqbFZozLOldBIhQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.0.0", "node": ">=18.0.0"
"npm": ">=10.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-unicorn": "^51.0.1" "eslint-plugin-unicorn": "^51.0.1 || ^52.0.0 || ^53.0.0"
} }
}, },
"node_modules/eslint-import-resolver-node": { "node_modules/eslint-import-resolver-node": {
@ -12665,18 +12671,18 @@
} }
}, },
"node_modules/eslint-plugin-unicorn": { "node_modules/eslint-plugin-unicorn": {
"version": "51.0.1", "version": "53.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz",
"integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==", "integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-validator-identifier": "^7.22.20", "@babel/helper-validator-identifier": "^7.24.5",
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@eslint/eslintrc": "^2.1.4", "@eslint/eslintrc": "^3.0.2",
"ci-info": "^4.0.0", "ci-info": "^4.0.0",
"clean-regexp": "^1.0.0", "clean-regexp": "^1.0.0",
"core-js-compat": "^3.34.0", "core-js-compat": "^3.37.0",
"esquery": "^1.5.0", "esquery": "^1.5.0",
"indent-string": "^4.0.0", "indent-string": "^4.0.0",
"is-builtin-module": "^3.2.1", "is-builtin-module": "^3.2.1",
@ -12685,11 +12691,11 @@
"read-pkg-up": "^7.0.1", "read-pkg-up": "^7.0.1",
"regexp-tree": "^0.1.27", "regexp-tree": "^0.1.27",
"regjsparser": "^0.10.0", "regjsparser": "^0.10.0",
"semver": "^7.5.4", "semver": "^7.6.1",
"strip-indent": "^3.0.0" "strip-indent": "^3.0.0"
}, },
"engines": { "engines": {
"node": ">=16" "node": ">=18.18"
}, },
"funding": { "funding": {
"url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
@ -12698,6 +12704,102 @@
"eslint": ">=8.56.0" "eslint": ">=8.56.0"
} }
}, },
"node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/eslint-visitor-keys": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/espree": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz",
"integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.11.3",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/jsesc": { "node_modules/eslint-plugin-unicorn/node_modules/jsesc": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
@ -12711,6 +12813,26 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/eslint-plugin-unicorn/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/eslint-plugin-unicorn/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/eslint-plugin-unicorn/node_modules/semver": { "node_modules/eslint-plugin-unicorn/node_modules/semver": {
"version": "7.6.2", "version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
@ -13718,9 +13840,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@ -15645,9 +15767,9 @@
} }
}, },
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "2.3.6", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
@ -18546,13 +18668,16 @@
} }
}, },
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "3.0.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
} }
}, },
"node_modules/lines-and-columns": { "node_modules/lines-and-columns": {
@ -18562,22 +18687,22 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/lint-staged": { "node_modules/lint-staged": {
"version": "15.2.2", "version": "15.2.4",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.4.tgz",
"integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==", "integrity": "sha512-3F9KRQIS2fVDGtCkBp4Bx0jswjX7zUcKx6OF0ZeY1prksUyKPRIIUqZhIUYAstJfvj6i48VFs4dwVIbCYwvTYQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "5.3.0", "chalk": "5.3.0",
"commander": "11.1.0", "commander": "12.1.0",
"debug": "4.3.4", "debug": "4.3.4",
"execa": "8.0.1", "execa": "8.0.1",
"lilconfig": "3.0.0", "lilconfig": "3.1.1",
"listr2": "8.0.1", "listr2": "8.2.1",
"micromatch": "4.0.5", "micromatch": "4.0.6",
"pidtree": "0.6.0", "pidtree": "0.6.0",
"string-argv": "0.3.2", "string-argv": "0.3.2",
"yaml": "2.3.4" "yaml": "2.4.2"
}, },
"bin": { "bin": {
"lint-staged": "bin/lint-staged.js" "lint-staged": "bin/lint-staged.js"
@ -18603,19 +18728,46 @@
} }
}, },
"node_modules/lint-staged/node_modules/commander": { "node_modules/lint-staged/node_modules/commander": {
"version": "11.1.0", "version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=16" "node": ">=18"
}
},
"node_modules/lint-staged/node_modules/micromatch": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.6.tgz",
"integrity": "sha512-Y4Ypn3oujJYxJcMacVgcs92wofTHxp9FzfDpQON4msDefoC0lb3ETvQLOdLcbhSwU1bz8HrL/1sygfBIHudrkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/lint-staged/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/listr2": { "node_modules/listr2": {
"version": "8.0.1", "version": "8.2.1",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz",
"integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==", "integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -18623,7 +18775,7 @@
"colorette": "^2.0.20", "colorette": "^2.0.20",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"log-update": "^6.0.0", "log-update": "^6.0.0",
"rfdc": "^1.3.0", "rfdc": "^1.3.1",
"wrap-ansi": "^9.0.0" "wrap-ansi": "^9.0.0"
}, },
"engines": { "engines": {
@ -19868,12 +20020,12 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.5", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"braces": "^3.0.2", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
@ -20435,6 +20587,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/normalize-svg-path": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz",
"integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==",
"license": "MIT",
"dependencies": {
"svg-arc-to-cubic-bezier": "^3.0.0"
}
},
"node_modules/npm-normalize-package-bin": { "node_modules/npm-normalize-package-bin": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz",
@ -20861,6 +21022,7 @@
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"deprecated": "This package is no longer supported.",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"os-homedir": "^1.0.0", "os-homedir": "^1.0.0",
@ -21002,6 +21164,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/parse-svg-path": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz",
"integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==",
"license": "MIT"
},
"node_modules/parse5": { "node_modules/parse5": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
@ -21677,9 +21845,9 @@
} }
}, },
"node_modules/react-hook-form": { "node_modules/react-hook-form": {
"version": "7.51.4", "version": "7.51.5",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.5.tgz",
"integrity": "sha512-V14i8SEkh+V1gs6YtD0hdHYnoL4tp/HX/A45wWQN15CYr9bFRmmRdYStSO5L65lCCZRF+kYiSKhm9alqbcdiVA==", "integrity": "sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=12.22.0" "node": ">=12.22.0"
@ -21776,6 +21944,21 @@
"moment": "^2.29.4" "moment": "^2.29.4"
} }
}, },
"node_modules/react-native-circular-progress-indicator": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-native-circular-progress-indicator/-/react-native-circular-progress-indicator-4.4.2.tgz",
"integrity": "sha512-BlgshzIIIk0TP/CZY+5oyRsHl2Sb2l6cK5tzfmrVETfn6pN8dhOKrV0i3Z6i0SM8wRgncdUUSRBpCPAexh7JoQ==",
"license": "MIT",
"dependencies": {
"react-native-redash": "*"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-native": ">=0.59.0",
"react-native-reanimated": ">=2.2.0",
"react-native-svg": ">=12.1.1"
}
},
"node_modules/react-native-elements": { "node_modules/react-native-elements": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz", "resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz",
@ -21899,6 +22082,23 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-redash": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/react-native-redash/-/react-native-redash-18.1.3.tgz",
"integrity": "sha512-RKdmAjs87iwA8iD7UOQ11rlQL+tZh56O5+5cV1M37Jk+4uH1Fvs22G3Q5v/wGx+0ncwz3wMolLLCezONOodTlA==",
"license": "MIT",
"dependencies": {
"abs-svg-path": "^0.1.1",
"normalize-svg-path": "^1.0.1",
"parse-svg-path": "^0.1.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-gesture-handler": "*",
"react-native-reanimated": ">=2.0.0"
}
},
"node_modules/react-native-safe-area-context": { "node_modules/react-native-safe-area-context": {
"version": "4.10.1", "version": "4.10.1",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.1.tgz", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.1.tgz",
@ -23990,14 +24190,14 @@
} }
}, },
"node_modules/supabase/node_modules/glob": { "node_modules/supabase/node_modules/glob": {
"version": "10.3.15", "version": "10.3.16",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz",
"integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^2.3.6", "jackspeak": "^3.1.2",
"minimatch": "^9.0.1", "minimatch": "^9.0.1",
"minipass": "^7.0.4", "minipass": "^7.0.4",
"path-scurry": "^1.11.0" "path-scurry": "^1.11.0"
@ -24180,6 +24380,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/svg-arc-to-cubic-bezier": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
"integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==",
"license": "ISC"
},
"node_modules/svg-parser": { "node_modules/svg-parser": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
@ -24872,9 +25078,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.17.0", "version": "6.18.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.17.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.18.1.tgz",
"integrity": "sha512-fs13QiDjPIzJ7gFAOal9CSG0c92rT2xw6MuMUJ4H30Eg5GCauLWYCCZA1tInjd6M4y+JZjVCCFr9pFpbhcC64w==", "integrity": "sha512-/0BWqR8rJNRysS5lqVmfc7eeOErcOP4tZpATVjJOojjHZ71gSYVAtFhEmadcIjwMIUehh5NFyKGsXCnXIajtbA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"
@ -25714,10 +25920,13 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.3.4", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"license": "ISC", "license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
} }

View File

@ -24,7 +24,7 @@
"@fortawesome/fontawesome-svg-core": "6.5.2", "@fortawesome/fontawesome-svg-core": "6.5.2",
"@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/free-solid-svg-icons": "6.5.2",
"@fortawesome/react-native-fontawesome": "0.3.1", "@fortawesome/react-native-fontawesome": "0.3.1",
"@hookform/resolvers": "3.4.0", "@hookform/resolvers": "3.4.2",
"@react-native-async-storage/async-storage": "1.23.1", "@react-native-async-storage/async-storage": "1.23.1",
"@react-navigation/native": "6.1.17", "@react-navigation/native": "6.1.17",
"@supabase/supabase-js": "2.43.2", "@supabase/supabase-js": "2.43.2",
@ -39,9 +39,10 @@
"lottie-react-native": "6.7.0", "lottie-react-native": "6.7.0",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "7.51.4", "react-hook-form": "7.51.5",
"react-native": "0.74.1", "react-native": "0.74.1",
"react-native-calendars": "1.1305.0", "react-native-calendars": "1.1305.0",
"react-native-circular-progress-indicator": "4.4.2",
"react-native-elements": "3.4.3", "react-native-elements": "3.4.3",
"react-native-gesture-handler": "2.16.2", "react-native-gesture-handler": "2.16.2",
"react-native-paper": "5.12.3", "react-native-paper": "5.12.3",
@ -67,21 +68,21 @@
"@types/node": "20.12.12", "@types/node": "20.12.12",
"@types/react": "18.2.79", "@types/react": "18.2.79",
"@types/react-test-renderer": "18.3.0", "@types/react-test-renderer": "18.3.0",
"@typescript-eslint/eslint-plugin": "7.9.0", "@typescript-eslint/eslint-plugin": "7.10.0",
"@typescript-eslint/parser": "7.9.0", "@typescript-eslint/parser": "7.10.0",
"eslint": "8.57.0", "eslint": "8.57.0",
"eslint-config-conventions": "14.1.0", "eslint-config-conventions": "14.2.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.34.1", "eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-native": "4.1.0", "eslint-plugin-react-native": "4.1.0",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "53.0.0",
"husky": "9.0.11", "husky": "9.0.11",
"jest": "29.7.0", "jest": "29.7.0",
"jest-expo": "51.0.2", "jest-expo": "51.0.2",
"jest-junit": "16.0.0", "jest-junit": "16.0.0",
"lint-staged": "15.2.2", "lint-staged": "15.2.4",
"prettier": "3.2.5", "prettier": "3.2.5",
"react-test-renderer": "18.2.0", "react-test-renderer": "18.2.0",
"supabase": "1.167.4", "supabase": "1.167.4",

View File

@ -6,7 +6,7 @@ import { Divider, List, Text } from "react-native-paper"
import { GOAL_FREQUENCIES, type GoalFrequency } from "@/domain/entities/Goal" import { GOAL_FREQUENCIES, type GoalFrequency } from "@/domain/entities/Goal"
import type { HabitHistory } from "@/domain/entities/HabitHistory" import type { HabitHistory } from "@/domain/entities/HabitHistory"
import type { HabitsTracker } from "@/domain/entities/HabitsTracker" import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
import { capitalize } from "@/utils/strings" import { LOCALE, capitalize } from "@/utils/strings"
import confettiJSON from "../../../assets/confetti.json" import confettiJSON from "../../../assets/confetti.json"
import { HabitCard } from "./HabitCard" import { HabitCard } from "./HabitCard"
@ -96,7 +96,7 @@ export const HabitsList: React.FC<HabitsListProps> = (props) => {
marginTop: 20, marginTop: 20,
}} }}
> >
{selectedDate.toLocaleDateString("en-US", { {selectedDate.toLocaleDateString(LOCALE, {
weekday: "long", weekday: "long",
year: "numeric", year: "numeric",
month: "long", month: "long",

View File

@ -0,0 +1,110 @@
import { Card, Divider, Text } from "react-native-paper"
import CircularProgress from "react-native-circular-progress-indicator"
import { Agenda } from "react-native-calendars"
import { useState } from "react"
import { getNowDateUTC, getISODate } from "@/utils/dates"
import type { HabitsTracker } from "@/domain/entities/HabitsTracker"
import { LOCALE } from "@/utils/strings"
import {
GOAL_FREQUENCIES,
GOAL_FREQUENCIES_TYPES,
} from "@/domain/entities/Goal"
import { calculateRatio } from "@/utils/maths"
export interface HabitsStatisticsProps {
habitsTracker: HabitsTracker
}
export const HabitsStatistics: React.FC<HabitsStatisticsProps> = (props) => {
const { habitsTracker } = props
const today = getNowDateUTC()
const todayISO = getISODate(today)
const [selectedDate, setSelectedDate] = useState<Date>(today)
const selectedDateISO = getISODate(selectedDate)
return (
<Agenda
firstDay={1}
showClosingKnob
onDayPress={(date) => {
setSelectedDate(new Date(date.dateString))
}}
markedDates={{
[todayISO]: { marked: true, today: true },
}}
maxDate={todayISO}
selected={selectedDateISO}
renderList={() => {
return (
<>
<Divider />
<Text
style={{
fontWeight: "bold",
fontSize: 22,
textAlign: "center",
marginVertical: 10,
}}
>
{selectedDate.toLocaleDateString(LOCALE, {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
})}
</Text>
{GOAL_FREQUENCIES.map((frequency) => {
const { totalGoalsSuccess, totalGoals } =
habitsTracker.getHabitsStatisticsByDateAndFrequency({
selectedDate,
frequency,
})
const percentage =
calculateRatio(totalGoalsSuccess, totalGoals) * 100
return {
totalGoalsSuccess,
totalGoals,
percentage,
frequency,
}
})
.filter(({ totalGoals }) => {
return totalGoals > 0
})
.map(
({ frequency, totalGoals, totalGoalsSuccess, percentage }) => {
return (
<Card
key={frequency}
mode="elevated"
style={{ marginVertical: 8, marginHorizontal: 10 }}
>
<Card.Content>
<Text variant="bodyMedium">
{totalGoalsSuccess} achieved goals in the{" "}
{GOAL_FREQUENCIES_TYPES[frequency]} out of{" "}
{totalGoals}.
</Text>
<CircularProgress
value={percentage}
progressValueColor={"#ecf0f1"}
circleBackgroundColor="black"
titleColor="white"
title="%"
/>
</Card.Content>
</Card>
)
},
)}
</>
)
}}
/>
)
}

View File

@ -0,0 +1,83 @@
import { calculateRatio } from "../maths"
describe("utils/maths", () => {
describe("calculateRatio", () => {
it("should calculate the ratio of a value to a total", () => {
// Arrange - Given
const value = 3
const total = 10
// Act - When
const result = calculateRatio(value, total)
// Assert - Then
const expected = 0.3
expect(result).toEqual(expected)
})
it("should return 0 if the total is 0", () => {
// Arrange - Given
const value = 3
const total = 0
// Act - When
const result = calculateRatio(value, total)
// Assert - Then
const expected = 0
expect(result).toEqual(expected)
})
it("should return 0 if the total is negative", () => {
// Arrange - Given
const value = 3
const total = -1
// Act - When
const result = calculateRatio(value, total)
// Assert - Then
const expected = 0
expect(result).toEqual(expected)
})
it("should return 0 if the value is 0", () => {
// Arrange - Given
const value = 0
const total = 10
// Act - When
const result = calculateRatio(value, total)
// Assert - Then
const expected = 0
expect(result).toEqual(expected)
})
it("should return 1 if the value is equal to the total", () => {
// Arrange - Given
const value = 10
const total = 10
// Act - When
const result = calculateRatio(value, total)
// Assert - Then
const expected = 1
expect(result).toEqual(expected)
})
it("should return 1 if the value is greater than the total", () => {
// Arrange - Given
const value = 11
const total = 10
// Act - When
const result = calculateRatio(value, total)
// Assert - Then
const expected = 1
expect(result).toEqual(expected)
})
})
})

9
utils/maths.ts Normal file
View File

@ -0,0 +1,9 @@
export const calculateRatio = (value: number, total: number): number => {
if (total <= 0) {
return 0
}
if (value >= total) {
return 1
}
return value / total
}

View File

@ -1,3 +1,5 @@
export const LOCALE = "en-US"
export const capitalize = (string: string): string => { export const capitalize = (string: string): string => {
return string.charAt(0).toUpperCase() + string.slice(1) return string.charAt(0).toUpperCase() + string.slice(1)
} }