diff --git a/App.js b/App.js index f414028b6..9a635df3c 100644 --- a/App.js +++ b/App.js @@ -3,6 +3,9 @@ import * as Sentry from '@sentry/react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { Provider } from 'react-redux'; import { RecoilRoot } from 'recoil'; +import dayjs from 'dayjs'; +import 'dayjs/locale/fr'; +import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; import { PersistGate } from 'redux-persist/integration/react'; import { InteractionManager } from 'react-native'; import { persistor, store } from './src/redux/store'; @@ -14,6 +17,9 @@ import { ToastProvider } from './src/services/toast'; import './src/styles/theme'; import { hasMigratedFromAsyncStorage, migrateFromAsyncStorage } from './src/services/storage'; +dayjs.extend(isSameOrAfter); +dayjs.locale('fr'); + if (!__DEV__) { Sentry.init({ dsn: SENTRY_XXX }); } diff --git a/src/scenes/ConsoFollowUp/consoDuck.js b/src/scenes/ConsoFollowUp/consoDuck.js index 4036b85f9..ce6f085cd 100644 --- a/src/scenes/ConsoFollowUp/consoDuck.js +++ b/src/scenes/ConsoFollowUp/consoDuck.js @@ -68,16 +68,16 @@ const formatHtmlTable = (drinks, catalog) => { Quantité ${sortDrinksByDate(drinks) - .map((drink) => { - const doses = mapDrinkToDose(drink, catalog); - const name = getDisplayName(drink.drinkKey, drink.quantity, catalog); - const time = new Date(drink.timestamp).getLocaleDateAndTime('fr'); - return ` + .map((drink) => { + const doses = mapDrinkToDose(drink, catalog); + const name = getDisplayName(drink.drinkKey, drink.quantity, catalog); + const time = new Date(drink.timestamp).getLocaleDateAndTime('fr'); + return ` ${time} ${drink.quantity} ${name} (${doses} dose${doses > 1 ? 's' : ''}) `; - }) - .join('')} + }) + .join('')} @@ -138,7 +138,7 @@ export const getConsolidatedCatalog = createSelector(getOwnDrinks, (ownDrinks) = export const checkIfThereIsDrinks = createSelector(getDrinksState, (drinks) => drinks.length > 0); export const getModalTimestamp = createSelector(getConsoState, (conso) => conso.modalTimestamp); const getStartDate = createSelector(getConsoState, (conso) => conso.startDate); -const getDays = createSelector([getDrinksState, getStartDate], (drinks, startDate) => { +export const getDays = createSelector([getDrinksState, getStartDate], (drinks, startDate) => { const lastDayOfDrinks = Math.max(...drinks.map(({ timestamp }) => timestamp)); const days = []; const amplitudeOfRecords = differenceOfDays(startDate, lastDayOfDrinks); diff --git a/src/scenes/Gains/CategorieGain.js b/src/scenes/Gains/CategorieGain.js index 25b26302a..12572c4a6 100644 --- a/src/scenes/Gains/CategorieGain.js +++ b/src/scenes/Gains/CategorieGain.js @@ -4,7 +4,7 @@ import H1 from '../../components/H1'; import TextStyled from '../../components/TextStyled'; import { screenWidth } from '../../styles/theme'; -const CategorieGain = ({ children, icon = null, value = '?', unit = '', description }) => { +const CategorieGain = ({ children, icon = null, value = '?', unit = '', description, maximize }) => { return ( @@ -42,9 +42,10 @@ const ComponentCategorie = styled.View` border: 1px solid #4030a5; border-radius: 5px; width: ${width * 0.85}px; - height: ${width * 0.85}px; + min-height: ${width * 0.85}px; justify-content: center; align-items: center; + overflow: hidden; `; const IconCategorie = styled.View` @@ -59,6 +60,9 @@ const UnitCategorie = styled.View` flex-direction: row; align-items: flex-end; height: ${width * 0.85 * 0.5}px; + + align-items: baseline; + flex-wrap: wrap; `; const CategorieUnit = styled(H1)``; diff --git a/src/scenes/Gains/Estimation.js b/src/scenes/Gains/Estimation.js index ffe3854c0..fee067ddc 100644 --- a/src/scenes/Gains/Estimation.js +++ b/src/scenes/Gains/Estimation.js @@ -7,7 +7,7 @@ import ButtonPrimary from '../../components/ButtonPrimary'; import H1 from '../../components/H1'; import TextStyled from '../../components/TextStyled'; import { screenHeight } from '../../styles/theme'; -import { estimationDrinksPerWeekState, maxDrinksPerWeekSelector } from './recoil'; +import { previousDrinksPerWeekState, maxDrinksPerWeekSelector } from './recoil'; import DrinksCategory from '../../components/DrinksCategory'; import { drinksCatalog } from '../ConsoFollowUp/drinksCatalog'; import { Container, MarginBottom, ModalContent } from '../AddDrink/styles'; @@ -20,24 +20,24 @@ const Estimation = () => { const complete = () => { navigation.navigate('GAINS'); }; - const [estimationDrinksPerWeek, setEstimationDrinksPerWeek] = useRecoilState(estimationDrinksPerWeekState); + const [previousDrinksPerWeek, setEstimationDrinksPerWeek] = useRecoilState(previousDrinksPerWeekState); const scrollRef = useRef(null); const setDrinkQuantityRequest = (drinkKey, quantity) => { - const oldDrink = estimationDrinksPerWeek.find((drink) => drink.drinkKey === drinkKey); + const oldDrink = previousDrinksPerWeek.find((drink) => drink.drinkKey === drinkKey); if (oldDrink) { setEstimationDrinksPerWeek([ - ...estimationDrinksPerWeek.filter((drink) => drink.drinkKey !== drinkKey), + ...previousDrinksPerWeek.filter((drink) => drink.drinkKey !== drinkKey), { - ...estimationDrinksPerWeek.find((drink) => drink.drinkKey === drinkKey), + ...previousDrinksPerWeek.find((drink) => drink.drinkKey === drinkKey), quantity, }, ]); } else { setEstimationDrinksPerWeek([ - ...estimationDrinksPerWeek, + ...previousDrinksPerWeek, { drinkKey, quantity, @@ -47,8 +47,6 @@ const Estimation = () => { } }; - console.log({ estimationDrinksPerWeek }); - return ( @@ -60,23 +58,25 @@ const Estimation = () => { - Sur une semaine type, combien de verres consommez-vous ? + Sur une semaine type, actuellement, combien de verres consommez-vous ? - Vos réponses sont anonymes, répondez avec le plus de transparence possible. + Cette estimation sera comparée à ce que vous consommerez par la suite, pour calculer vos gains en € + et kCal. - Pour rappel votre objectif est de - - {' '} - ne pas dépasser - {maxDrinksPerWeekGoal} verres par semaine. - + Vos réponses sont anonymes, répondez avec le plus de transparence possible. + {/* + + Pour rappel votre objectif est de ne pas dépasser + {maxDrinksPerWeekGoal} verres par semaine. + + */} @@ -90,7 +90,7 @@ const Estimation = () => { drinksCatalog={drinksCatalog} category={category} index={index} - drinks={estimationDrinksPerWeek} + drinks={previousDrinksPerWeek} setDrinkQuantity={setDrinkQuantityRequest} /> ))} @@ -98,7 +98,7 @@ const Estimation = () => { - + ); diff --git a/src/scenes/Gains/MyGains.js b/src/scenes/Gains/MyGains.js index bd4a3e02b..bca3bd71e 100644 --- a/src/scenes/Gains/MyGains.js +++ b/src/scenes/Gains/MyGains.js @@ -16,31 +16,108 @@ import Rocket from '../../components/Illustrations/Rocket'; import TextStyled from '../../components/TextStyled'; import CategorieGain from './CategorieGain'; import GainsCalendar from './GainsCalendar'; -import MyGoal from './MyGoal'; +import CocktailGlass from '../../components/Illustrations/CocktailGlassTriangle'; +import Done from '../../components/Illustrations/Done'; +import { drinksCatalog } from '../ConsoFollowUp/drinksCatalog'; +import { daysWithGoalNoDrinkState, maxDrinksPerWeekSelector, previousDrinksPerWeekState } from './recoil'; import OnBoardingGain from './OnBoardingGain'; import { getDaysForFeed, getDailyDoses, getDrinksState } from '../ConsoFollowUp/consoDuck'; -import { maxDrinksPerWeekSelector } from './recoil'; +import dayjs from 'dayjs'; -const MyGains = ({ days, dailyDoses }) => { +const MyGains = ({ days, dailyDoses, drinks }) => { const navigation = useNavigation(); + const maxDrinksPerWeekGoal = useRecoilValue(maxDrinksPerWeekSelector); + const previousDrinksPerWeek = useRecoilValue(previousDrinksPerWeekState); + const dayNoDrink = useRecoilValue(daysWithGoalNoDrinkState)?.length; - const toGoal = () => { + const navigateToGoal = () => { navigation.navigate('GOAL'); setShowOnboardingGainModal((show) => !show); }; + const navigateToEstimation = () => navigation.navigate('ESTIMATION'); + const isOnboarded = useMemo( + () => !!maxDrinksPerWeekGoal && !!previousDrinksPerWeek.length, + [maxDrinksPerWeekGoal, previousDrinksPerWeek] + ); + const [showOnboardingGainModal, setShowOnboardingGainModal] = useState(false); + const [showGoalfix, setShowGoalfix] = useState(true); - const beginDate = '3 avril'; - const beginDay = 'lundi'; + const beginDateOfOz = useMemo(() => { + if (!days.length) return null; + return dayjs(days[days.length - 1]); + }, [days]); - const maxDrinksPerWeekGoal = useRecoilValue(maxDrinksPerWeekSelector); + const notDrinkDaythisWeek = useMemo( + () => + days.filter((day) => dayjs(day).isSameOrAfter(dayjs().startOf('week'))).filter((day) => dailyDoses[day] === 0) + .length, + [days, dailyDoses] + ); + const numberDrinkThisWeek = useMemo( + () => + days + .filter((day) => dayjs(day).isSameOrAfter(dayjs().startOf('week'))) + .reduce((sum, day) => sum + (dailyDoses[day] ? dailyDoses[day] : 0), 0), + [days, dailyDoses] + ); + const remaindrink = useMemo( + () => (maxDrinksPerWeekGoal - numberDrinkThisWeek > 0 ? maxDrinksPerWeekGoal - numberDrinkThisWeek : 0), + [maxDrinksPerWeekGoal, numberDrinkThisWeek] + ); - const isOnboarded = useMemo(() => !!maxDrinksPerWeekGoal, [maxDrinksPerWeekGoal]); - const [showOnboardingGainModal, setShowOnboardingGainModal] = useState(false); - const [showGoalfix, setShowGoalfix] = useState(true); + const myWeeklyNumberOfDrinksBeforeObjective = useMemo(() => { + return previousDrinksPerWeek.reduce((sum, drink) => sum + drink.quantity, 0); + }, [previousDrinksPerWeek]); - const notDrinkDaythisWeek = days.slice(0, 7).filter((day) => dailyDoses[day] === 0).length; - const numberDrinkThisWeek = days.slice(0, 7).reduce((sum, day) => sum + (dailyDoses[day] ? dailyDoses[day] : 0), 0); - const remaindrink = maxDrinksPerWeekGoal - numberDrinkThisWeek > 0 ? maxDrinksPerWeekGoal - numberDrinkThisWeek : 0; + const myWeeklyExpensesBeforeObjective = useMemo( + () => + previousDrinksPerWeek.reduce( + (sum, drink) => + sum + + drink.quantity * (drinksCatalog.find((drinkCatalog) => drinkCatalog.drinkKey === drink.drinkKey)?.price || 0), + 0 + ), + [previousDrinksPerWeek] + ); + + const myWeeklyKcalBeforeObjective = useMemo( + () => + previousDrinksPerWeek.reduce( + (sum, drink) => + sum + + drink.quantity * (drinksCatalog.find((drinkCatalog) => drinkCatalog.drinkKey === drink.drinkKey)?.kcal || 0), + 0 + ), + [previousDrinksPerWeek] + ); + + const mySavingsSinceBeginning = useMemo(() => { + if (!days.length) return null; + const myExpensesSinceBegnining = drinks.reduce( + (sum, drink) => + sum + + drink.quantity * (drinksCatalog.find((drinkCatalog) => drinkCatalog.drinkKey === drink.drinkKey)?.price || 0), + 0 + ); + const numberOfDaysSinceBeginning = Math.abs(dayjs(beginDateOfOz).diff(dayjs(), 'days')); + const averageDailyExpenses = myExpensesSinceBegnining / numberOfDaysSinceBeginning; + const averageDailyExpensesBeforeObjective = myWeeklyExpensesBeforeObjective / 7; + return Math.ceil(averageDailyExpensesBeforeObjective - averageDailyExpenses) * numberOfDaysSinceBeginning; + }, [drinks, days, myWeeklyExpensesBeforeObjective, beginDateOfOz]); + + const myKcalSavingsSinceBeginning = useMemo(() => { + if (!days.length) return null; + const myKcalSinceBegnining = drinks.reduce( + (sum, drink) => + sum + + drink.quantity * (drinksCatalog.find((drinkCatalog) => drinkCatalog.drinkKey === drink.drinkKey)?.kcal || 0), + 0 + ); + const numberOfDaysSinceBeginning = Math.abs(dayjs(beginDateOfOz).diff(dayjs(), 'days')); + const averageDailyKcal = myKcalSinceBegnining / numberOfDaysSinceBeginning; + const averageDailyKcalBeforeObjective = myWeeklyKcalBeforeObjective / 7; + return Math.ceil(averageDailyKcalBeforeObjective - averageDailyKcal) * numberOfDaysSinceBeginning; + }, [drinks, days, myWeeklyKcalBeforeObjective, beginDateOfOz]); return ( @@ -80,34 +157,54 @@ const MyGains = ({ days, dailyDoses }) => { - {isOnboarded && ( + {!!isOnboarded && ( - Depuis le {beginDate} + Depuis le + + {' '} + {beginDateOfOz.get('year') < dayjs().get('year') + ? beginDateOfOz.format('D MMM yyyy') + : beginDateOfOz.format('D MMM')} + )} - } unit={'€'} description="Mes économies" /> - } unit="kcal" description="Mes calories économisées" /> + } + unit={'€'} + description="Mes économies" + value={isOnboarded ? mySavingsSinceBeginning * 100 : '?'} + maximize + /> + } + unit="kcal" + description="Mes calories économisées" + value={isOnboarded ? myKcalSavingsSinceBeginning * 100 : '?'} + maximize + /> - {isOnboarded && ( + {!!isOnboarded && ( - Sur la semaine en cours depuis {beginDay} + Sur la semaine en cours depuis lundi )} - + 1 ? 's' : ''} restant${remaindrink > 1 ? 's' : ''}`} + value={isOnboarded ? remaindrink : '?'}> { /> setShowOnboardingGainModal(false)} /> @@ -140,7 +237,63 @@ const MyGains = ({ days, dailyDoses }) => { ) : ( - + + + <H1 color="#4030a5">Mon objectif</H1> + + + + + + + {' '} + {dayNoDrink} {dayNoDrink > 1 ? 'jours' : 'jour'} où je ne bois pas + + + + + + {' '} + {maxDrinksPerWeekGoal} {maxDrinksPerWeekGoal > 1 ? 'verres' : 'verre'} max par semaine + + + + + + + + Modifier l'objectif + + + + + <H2 color="#4030a5">Estimation de ma consommation avant objectif</H2> + <H2>Par semaine</H2> + + + + + + {myWeeklyExpensesBeforeObjective} € + + + + + {' '} + {myWeeklyNumberOfDrinksBeforeObjective}{' '} + {myWeeklyNumberOfDrinksBeforeObjective > 1 ? 'verres' : 'verre'}{' '} + + + + + + + + Modifier l'estimation + + + + )} ); @@ -209,6 +362,42 @@ const Bold = styled.Text` font-weight: bold; `; +const Title = styled.View` + flex-shrink: 0; + margin-top: 10px; +`; + +const MyGoalContainer = styled.View` + padding: 20px 30px 0px; +`; + +const MyGoalSubContainer = styled.View` + border: 1px solid #ddd; + border-radius: 5px; + margin: 10px 5px 10px; +`; + +const PartContainer = styled.View` + flex-direction: row; + align-items: center; + padding: 10px 20px; +`; + +const MyGoalSubContainerInside = styled.View` + margin-top: 10px; + margin-bottom: 10px; +`; + +const ModifyContainer = styled.View` + align-items: center; + margin-top: 10px; + margin-bottom: 10px; +`; + +const TextModify = styled.Text` + text-decoration: underline; +`; + const makeStateToProps = () => (state) => ({ drinks: getDrinksState(state), days: getDaysForFeed(state), @@ -216,3 +405,5 @@ const makeStateToProps = () => (state) => ({ }); export default connect(makeStateToProps)(MyGains); + +const ButtonTouchable = styled.TouchableOpacity``; diff --git a/src/scenes/Gains/MyGoal.js b/src/scenes/Gains/MyGoal.js deleted file mode 100644 index 2b09cff15..000000000 --- a/src/scenes/Gains/MyGoal.js +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useMemo } from 'react'; -import { useNavigation } from '@react-navigation/native'; -import styled from 'styled-components'; -import { useRecoilValue } from 'recoil'; -import H1 from '../../components/H1'; -import H2 from '../../components/H2'; -import CocktailGlass from '../../components/Illustrations/CocktailGlassTriangle'; -import Done from '../../components/Illustrations/Done'; -import Economy from '../../components/Illustrations/Economy'; -import TextStyled from '../../components/TextStyled'; -import { drinksCatalog } from '../ConsoFollowUp/drinksCatalog'; -import { daysWithGoalNoDrinkState, maxDrinksPerWeekSelector, estimationDrinksPerWeekState } from './recoil'; - -const MyGoal = () => { - const navigation = useNavigation(); - - const maxDrinksPerWeekGoal = useRecoilValue(maxDrinksPerWeekSelector); - const dayNoDrink = useRecoilValue(daysWithGoalNoDrinkState)?.length; - - const toGoal = () => { - navigation.navigate('GOAL'); - }; - - const toEstimation = () => { - navigation.navigate('ESTIMATION'); - }; - - const estimationDrinksPerWeek = useRecoilValue(estimationDrinksPerWeekState); - - const price = useMemo(() => { - return estimationDrinksPerWeek.reduce( - (sum, drink) => - sum + drink.quantity * drinksCatalog.find((drinkCatalog) => drinkCatalog.drinkKey === drink.drinkKey).price, - 0 - ); - }, [estimationDrinksPerWeek]); - - const numberOfDrink = useMemo(() => { - return estimationDrinksPerWeek.reduce((sum, drink) => sum + drink.quantity, 0); - }, [estimationDrinksPerWeek]); - - return ( - - - <H1 color="#4030a5">Mon objectif</H1> - - - - } - value={` ${dayNoDrink} ${dayNoDrink > 1 ? 'jours' : 'jour'} où je ne bois pas`} - /> - } - value={` ${maxDrinksPerWeekGoal} ${maxDrinksPerWeekGoal > 1 ? 'verres' : 'verre'} max par semaine`} - /> - - - - - - Modifier l'objectif - - - - - <H2 color="#4030a5">Estimation de ma consommation avant objectif</H2> - <H2>Par semaine</H2> - - - - } value={` ${price} €`} /> - } - value={` ${numberOfDrink} ${numberOfDrink > 1 ? 'verres' : 'verre'} `} - /> - - - - - - Modifier l'estimation - - - - - ); -}; - -const PartMyGoalSubContainer = ({ icon, value }) => ( - - {icon} - {value} - -); - -const Title = styled.View` - flex-shrink: 0; - margin-top: 10px; -`; - -const MyGoalContainer = styled.View` - padding: 20px 30px 0px; -`; - -const MyGoalSubContainer = styled.View` - border: 1px solid #ddd; - border-radius: 5px; - margin: 10px 5px 10px; -`; - -const PartContainer = styled.View` - flex-direction: row; - align-items: center; - padding: 10px 20px; -`; - -const MyGoalSubContainerInside = styled.View` - margin-top: 10px; - margin-bottom: 10px; -`; - -const ModifyContainer = styled.View` - align-items: center; - margin-top: 10px; - margin-bottom: 10px; -`; - -const TextModify = styled.Text` - text-decoration: underline; -`; - -const ButtonTouchable = styled.TouchableOpacity``; - -export default MyGoal; diff --git a/src/scenes/Gains/recoil.js b/src/scenes/Gains/recoil.js index a32837ef8..068c088ad 100644 --- a/src/scenes/Gains/recoil.js +++ b/src/scenes/Gains/recoil.js @@ -30,10 +30,10 @@ export const drinksByDrinkingDayState = atom({ effects: [({ onSet }) => onSet((newValue) => storage.set('@StoredDrinksByDrinkingDay', newValue))], }); -export const estimationDrinksPerWeekState = atom({ - key: 'estimationDrinksPerWeekState', - default: getInitValueFromStorage('@GainEstimationDrinksPerWeek', []), - effects: [({ onSet }) => onSet((newValue) => storage.set('@GainEstimationDrinksPerWeek', JSON.stringify(newValue)))], +export const previousDrinksPerWeekState = atom({ + key: 'previousDrinksPerWeekState', + default: getInitValueFromStorage('@GainPreviousDrinksPerWeek', []), + effects: [({ onSet }) => onSet((newValue) => storage.set('@GainPreviousDrinksPerWeek', JSON.stringify(newValue)))], }); export const maxDrinksPerWeekSelector = selector({