From efb99cad62f5883b58c8765b8f85089a1613547d Mon Sep 17 00:00:00 2001 From: VV Date: Mon, 4 Dec 2023 17:19:06 +0100 Subject: [PATCH] added realtime calculation --- package-lock.json | 2 +- src/components/layouts/SettingsMenu.tsx | 53 ++++++++++++- src/i18next/locales/en/common.json | 4 +- src/i18next/locales/fr/common.json | 4 +- .../features/settings/settingsSelector.ts | 4 + src/store/features/settings/settingsSlice.ts | 22 +++++- src/store/features/wallets/walletsSelector.ts | 75 ++++++++++++++++--- src/types/RentCalculation.ts | 4 + src/utils/date.ts | 16 ++++ yarn.lock | 21 ++++-- 10 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 src/types/RentCalculation.ts create mode 100644 src/utils/date.ts diff --git a/package-lock.json b/package-lock.json index 7cf7795..ed80a84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "realtoken-dashboard-v2", - "version": "2.0.0", + "version": "2.0.2", "dependencies": { "@apollo/client": "^3.7.3", "@emotion/react": "^11.10.5", diff --git a/src/components/layouts/SettingsMenu.tsx b/src/components/layouts/SettingsMenu.tsx index 1064e42..af8c391 100644 --- a/src/components/layouts/SettingsMenu.tsx +++ b/src/components/layouts/SettingsMenu.tsx @@ -16,6 +16,8 @@ import { import { useDisclosure } from '@mantine/hooks' import { IconCash, + IconClock, + IconClockOff, IconLanguage, IconMoon, IconSettings, @@ -26,10 +28,15 @@ import { setCookie } from 'cookies-next' import { selectUserCurrency, + selectUserRentCalculation, selectVersion, } from 'src/store/features/settings/settingsSelector' -import { userCurrencyChanged } from 'src/store/features/settings/settingsSlice' +import { + userCurrencyChanged, + userRentCalculationChanged, +} from 'src/store/features/settings/settingsSlice' import { Currency } from 'src/types/Currencies' +import { RentCalculation } from 'src/types/RentCalculation' import { expiresLocalStorageCaches } from 'src/utils/useCache' const ColorSchemeMenuItem: FC = () => { @@ -69,6 +76,48 @@ const ColorSchemeMenuItem: FC = () => { ) } +const RealtimeRentMenuItem: FC = () => { + const dispatch = useDispatch() + const rentCalculation = useSelector(selectUserRentCalculation) + + function setUserRentCalculation(rentCalculation: RentCalculation) { + dispatch(userRentCalculationChanged(rentCalculation)) + } + + const { t } = useTranslation('common', { keyPrefix: 'settings' }) + + return ( + + setUserRentCalculation(value as RentCalculation)} + data={[ + { + value: 'realtime', + label: ( +
+ + {t('realtime')} +
+ ), + }, + { + value: 'global', + label: ( +
+ + {t('global')} +
+ ), + }, + ]} + /> +
+ ) +} + const LanguageSelect: FC = () => { const { i18n, t } = useTranslation('common', { keyPrefix: 'settings' }) @@ -163,6 +212,8 @@ export const SettingsMenu: FC = () => { + + diff --git a/src/i18next/locales/en/common.json b/src/i18next/locales/en/common.json index ef36343..7fff52b 100644 --- a/src/i18next/locales/en/common.json +++ b/src/i18next/locales/en/common.json @@ -12,7 +12,9 @@ "chf": "Swiss franc (CHF)", "light": "Light", "dark": "Dark", - "refreshDataButton": "Refresh data" + "refreshDataButton": "Refresh data", + "realtime":"Realtime", + "global":"Global" }, "walletButton": { "connectWallet": "Connect wallet", diff --git a/src/i18next/locales/fr/common.json b/src/i18next/locales/fr/common.json index 141d8c0..96e2b69 100644 --- a/src/i18next/locales/fr/common.json +++ b/src/i18next/locales/fr/common.json @@ -12,7 +12,9 @@ "chf": "Franc suisse (CHF)", "light": "Clair", "dark": "Sombre", - "refreshDataButton": "Actualiser les données" + "refreshDataButton": "Actualiser les données", + "realtime":"Temps réel", + "global":"Global" }, "walletButton": { "connectWallet": "Connecter mon portefeuille", diff --git a/src/store/features/settings/settingsSelector.ts b/src/store/features/settings/settingsSelector.ts index 1194151..1fcfa92 100644 --- a/src/store/features/settings/settingsSelector.ts +++ b/src/store/features/settings/settingsSelector.ts @@ -28,5 +28,9 @@ export const selectUserCurrency = (state: RootState): string => export const selectUser = (state: RootState): User | undefined => state.settings.user +export const selectUserRentCalculation = ( + state: RootState + ): string | undefined => state.settings.rentCalculation + export const selectVersion = (state: RootState): string | undefined => state.settings.version diff --git a/src/store/features/settings/settingsSlice.ts b/src/store/features/settings/settingsSlice.ts index 3f6d53e..726fa68 100644 --- a/src/store/features/settings/settingsSlice.ts +++ b/src/store/features/settings/settingsSlice.ts @@ -7,9 +7,11 @@ import { t } from 'i18next' import { UserRepository } from 'src/repositories/user.repository' import { AppDispatch } from 'src/store/store' import { Currency } from 'src/types/Currencies' +import { RentCalculation } from 'src/types/RentCalculation' const USER_LS_KEY = 'store:settings/user' const USER_CURRENCY_LS_KEY = 'store:settings/userCurrency' +const USER_RENT_CALCULATION_LS_KEY = 'store:settings/userRentCalculation' export interface User { id: string @@ -22,12 +24,14 @@ interface SettingsInitialStateType { user?: User userCurrency: Currency isInitialized: boolean + rentCalculation: RentCalculation version?: string } const settingsInitialState: SettingsInitialStateType = { user: undefined, userCurrency: Currency.USD, + rentCalculation: RentCalculation.Global, isInitialized: false, } @@ -35,6 +39,8 @@ const settingsInitialState: SettingsInitialStateType = { export const initializeSettingsDispatchType = 'settings/initialize' export const userChangedDispatchType = 'settings/userChanged' export const userCurrencyChangedDispatchType = 'settings/userCurrencyChanged' +export const userRentCalculationChangedDispatchType = + 'settings/userRentCalculationChanged' // ACTIONS export const initializeSettings = createAction(initializeSettingsDispatchType) @@ -42,6 +48,9 @@ export const userChanged = createAction(userChangedDispatchType) export const userCurrencyChanged = createAction( userCurrencyChangedDispatchType ) +export const userRentCalculationChanged = createAction( + userRentCalculationChangedDispatchType +) // THUNKS export function setUserAddress(address: string) { @@ -86,14 +95,23 @@ export const settingsReducers = createReducer( state.userCurrency = action.payload localStorage.setItem(USER_CURRENCY_LS_KEY, action.payload) }) + .addCase(userRentCalculationChanged, (state, action) => { + ;(state.rentCalculation = action.payload), + localStorage.setItem(USER_RENT_CALCULATION_LS_KEY, action.payload) + }) .addCase(initializeSettings, (state) => { const user = localStorage.getItem(USER_LS_KEY) const userCurrency = localStorage.getItem(USER_CURRENCY_LS_KEY) + const userRentCalculation = localStorage.getItem( + USER_RENT_CALCULATION_LS_KEY + ) state.user = user ? JSON.parse(user) : undefined state.userCurrency = userCurrency ? (userCurrency as Currency) : Currency.USD - + state.rentCalculation = userRentCalculation + ? (userRentCalculation as RentCalculation) + : RentCalculation.Global const { publicRuntimeConfig } = getConfig() as { publicRuntimeConfig?: { version: string } } @@ -104,4 +122,4 @@ export const settingsReducers = createReducer( state.isInitialized = true }) } -) +) \ No newline at end of file diff --git a/src/store/features/wallets/walletsSelector.ts b/src/store/features/wallets/walletsSelector.ts index 6ff099d..8c470d0 100644 --- a/src/store/features/wallets/walletsSelector.ts +++ b/src/store/features/wallets/walletsSelector.ts @@ -2,11 +2,15 @@ import { createSelector } from '@reduxjs/toolkit' import _mapValues from 'lodash/mapValues' import _sumBy from 'lodash/sumBy' +import moment from 'moment' import { WalletBalances, WalletType } from 'src/repositories' import { RootState } from 'src/store/store' +import { RentCalculation } from 'src/types/RentCalculation' +import { numberOfDaysIn } from 'src/utils/date' import { Realtoken, selectRealtokens } from '../realtokens/realtokensSelector' +import { selectUserRentCalculation } from '../settings/settingsSelector' export interface UserRealtoken extends Realtoken { id: string @@ -83,17 +87,68 @@ export const selectOwnedRealtokensValue = createSelector( ) export const selectOwnedRealtokensRents = createSelector( + selectUserRentCalculation, selectOwnedRealtokens, - (realtokens) => - realtokens.reduce( - (acc, item) => ({ - daily: acc.daily + item.netRentDayPerToken * item.amount, - weekly: acc.weekly + item.netRentDayPerToken * 7 * item.amount, - monthly: acc.monthly + item.netRentMonthPerToken * item.amount, - yearly: acc.yearly + item.netRentYearPerToken * item.amount, - }), - { daily: 0, weekly: 0, monthly: 0, yearly: 0 } - ) + (rentCalculation, realtokens) => { + const rents = { daily: 0, weekly: 0, monthly: 0, yearly: 0 } + + if (rentCalculation === RentCalculation.Global) { + return realtokens.reduce( + (acc, item) => ({ + daily: acc.daily + item.netRentDayPerToken * item.amount, + weekly: acc.weekly + item.netRentDayPerToken * 7 * item.amount, + monthly: acc.monthly + item.netRentMonthPerToken * item.amount, + yearly: acc.yearly + item.netRentYearPerToken * item.amount, + }), + rents + ) + } + + const now = moment(new Date().toUTCString()) + const rentDay = 'Monday' + const oneMonthLater = now.clone().add(1, 'M') + const oneYearLater = now.clone().add(1, 'y') + const oneWeekLater = now.clone().add(1, 'w') + const nbMondaysInMonth = numberOfDaysIn(now, oneMonthLater, now, rentDay) + const nbMondaysInYear = numberOfDaysIn(now, oneYearLater, now, rentDay) + + for (const item of realtokens) { + const rentStartDate = moment(item.rentStartDate.date) + const daysDiff = rentStartDate.diff(now, 'days') + const rentPerWeek = item.netRentDayPerToken * 7 * item.amount + + if (daysDiff > 0) { + const nbDayLeftInWeek = numberOfDaysIn( + now, + oneWeekLater, + rentStartDate, + rentDay + ) + const nbDayLeftInMonth = numberOfDaysIn( + now, + oneMonthLater, + rentStartDate, + rentDay + ) + const nbDayLeftInYear = numberOfDaysIn( + now, + oneYearLater, + rentStartDate, + rentDay + ) + + rents.weekly += !nbDayLeftInWeek ? 0 : rentPerWeek + rents.monthly += !nbDayLeftInMonth ? 0 : rentPerWeek * nbDayLeftInMonth + rents.yearly += !nbDayLeftInYear ? 0 : rentPerWeek * nbDayLeftInYear + } else { + rents.daily += item.netRentDayPerToken * item.amount + rents.weekly += rentPerWeek + rents.monthly += rentPerWeek * nbMondaysInMonth + rents.yearly += rentPerWeek * nbMondaysInYear + } + } + return rents + } ) export const selectOwnedRealtokensAPY = createSelector( diff --git a/src/types/RentCalculation.ts b/src/types/RentCalculation.ts new file mode 100644 index 0000000..554a029 --- /dev/null +++ b/src/types/RentCalculation.ts @@ -0,0 +1,4 @@ +export enum RentCalculation { + Realtime = 'realtime', + Global = 'global', +} diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..0233f42 --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,16 @@ +import moment from 'moment' + +export const numberOfDaysIn = ( + startDate: moment.Moment, + endDate: moment.Moment, + afterDate: moment.Moment, + day: string +) => { + let nbDays = 0 + for (let m = startDate.clone(); m.isBefore(endDate); m.add(1, 'day')) { + if (m.isSameOrAfter(afterDate) && m.clone().format('dddd') === day) { + nbDays++ + } + } + return nbDays +} diff --git a/yarn.lock b/yarn.lock index 41e2fe6..a91f208 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2746,9 +2746,9 @@ dependencies: "glob" "7.1.7" -"@next/swc-win32-x64-msvc@12.1.5": - "integrity" "sha512-/SoXW1Ntpmpw3AXAzfDRaQidnd8kbZ2oSni8u5z0yw6t4RwJvmdZy1eOaAADRThWKV+2oU90++LSnXJIwBRWYQ==" - "resolved" "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.5.tgz" +"@next/swc-darwin-arm64@12.1.5": + "integrity" "sha512-y8mhldb/WFZ6lFeowkGfi0cO/lBdiBqDk4T4LZLvCpoQp4Or/NzUN6P5NzBQZ5/b4oUHM/wQICEM+1wKA4qIVw==" + "resolved" "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.5.tgz" "version" "12.1.5" "@noble/curves@~1.2.0", "@noble/curves@1.2.0": @@ -2794,6 +2794,11 @@ "@nodelib/fs.scandir" "2.1.5" "fastq" "^1.6.0" +"@parcel/watcher-darwin-arm64@2.3.0": + "integrity" "sha512-mKY+oijI4ahBMc/GygVGvEdOq0L4DxhYgwQqYAz/7yPzuGi79oXrZG52WdpGA1wLBPrYb0T8uBaGFo7I6rvSKw==" + "resolved" "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.3.0.tgz" + "version" "2.3.0" + "@parcel/watcher-wasm@2.3.0": "integrity" "sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==" "resolved" "https://registry.npmjs.org/@parcel/watcher-wasm/-/watcher-wasm-2.3.0.tgz" @@ -2803,11 +2808,6 @@ "micromatch" "^4.0.5" "napi-wasm" "^1.1.0" -"@parcel/watcher-win32-x64@2.3.0": - "integrity" "sha512-dLx+0XRdMnVI62kU3wbXvbIRhLck4aE28bIGKbRGS7BJNt54IIj9+c/Dkqb+7DJEbHUZAX1bwaoM8PqVlHJmCA==" - "resolved" "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.3.0.tgz" - "version" "2.3.0" - "@parcel/watcher@^2.3.0": "integrity" "sha512-pW7QaFiL11O0BphO+bq3MgqeX/INAk9jgBldVDYjlQPO4VddoZnF22TcF9onMhnLVHuNqBJeRf+Fj7eezi/+rQ==" "resolved" "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.3.0.tgz" @@ -7154,6 +7154,11 @@ "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" "version" "1.0.0" +"fsevents@^2.3.2", "fsevents@~2.3.2": + "integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" + "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + "version" "2.3.2" + "function-bind@^1.1.1": "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"