Skip to content

Commit

Permalink
feat(mobile): correct budget amount (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
bkdev98 committed Aug 22, 2024
1 parent 773ad4d commit 8463550
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 62 deletions.
42 changes: 40 additions & 2 deletions apps/mobile/app/(app)/(tabs)/budgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import { Text } from '@/components/ui/text'
import { useColorScheme } from '@/hooks/useColorScheme'
import { theme } from '@/lib/theme'
import { useBudgetList } from '@/stores/budget/hooks'
import { useTransactionList } from '@/stores/transaction/hooks'
import { dayjsExtended } from '@6pm/utilities'
import type { Budget, BudgetPeriodConfig } from '@6pm/validation'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { LinearGradient } from 'expo-linear-gradient'
import { groupBy, map } from 'lodash-es'
import { Dimensions, SectionList, View } from 'react-native'
import Animated, {
Extrapolation,
Expand Down Expand Up @@ -128,11 +131,39 @@ export default function BudgetsScreen() {
savingBudgets,
investingBudgets,
debtBudgets,
totalBudget,
isRefetching,
isLoading,
refetch,
} = useBudgetList()

const { transactions } = useTransactionList({
from: dayjsExtended().startOf('month').toDate(),
to: dayjsExtended().endOf('month').toDate(),
})

const totalBudgetUsage = transactions.reduce((acc, t) => {
if (!t.budgetId) {
return acc
}
return acc + t.amountInVnd
}, 0)

const chartData = map(
groupBy(transactions, (t) => t.date),
(transactions, key) => ({
day: new Date(key).getDate(),
amount: transactions.reduce((acc, t) => acc + t.amountInVnd, 0),
}),
)

const totalRemaining = totalBudget.add(totalBudgetUsage).round()

const daysInMonth = dayjsExtended().daysInMonth()
const remainingDays = daysInMonth - dayjsExtended().get('date')
const remainingPerDay = totalRemaining.div(remainingDays).round()
const averagePerDay = totalBudget.div(daysInMonth).round()

const sections = [
{ key: 'SPENDING', title: t(i18n)`Spending`, data: spendingBudgets },
{ key: 'SAVING', title: t(i18n)`Saving`, data: savingBudgets },
Expand All @@ -154,13 +185,20 @@ export default function BudgetsScreen() {
}}
>
<Animated.View className="gap-6 px-6 py-6" style={summaryStyle}>
<BudgetStatistic totalRemaining={1000} remainingPerDay={100} />
<BudgetStatistic
totalRemaining={totalRemaining.toNumber()}
remainingPerDay={remainingPerDay.toNumber()}
/>
</Animated.View>
<Animated.View
className="px-6 pb-5"
style={[{ flexGrow: 0 }, chartStyle]}
>
<BurndownChart />
<BurndownChart
totalBudget={totalBudget.toNumber()}
averagePerDay={averagePerDay.toNumber()}
data={chartData}
/>
</Animated.View>
</View>
<AnimatedSectionList
Expand Down
35 changes: 29 additions & 6 deletions apps/mobile/app/(app)/budget/[budgetId]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useLingui } from '@lingui/react'
import { format } from 'date-fns'
import { LinearGradient } from 'expo-linear-gradient'
import { Link, useLocalSearchParams, useNavigation } from 'expo-router'
import { groupBy, mapValues, orderBy, sortBy, sumBy } from 'lodash-es'
import { groupBy, map, mapValues, orderBy, sortBy, sumBy } from 'lodash-es'
import { SettingsIcon } from 'lucide-react-native'
import { useEffect, useMemo, useState } from 'react'
import { ActivityIndicator, Dimensions, SectionList, View } from 'react-native'
Expand Down Expand Up @@ -65,6 +65,8 @@ export default function BudgetDetailScreen() {
},
)

const totalUsage = transactions.reduce((acc, t) => acc + t.amountInVnd, 0)

const transactionsGroupByDate = useMemo(() => {
const groupedByDay = groupBy(transactions, (transaction) =>
format(new Date(transaction.date), 'yyyy-MM-dd'),
Expand Down Expand Up @@ -193,6 +195,25 @@ export default function BudgetDetailScreen() {
)
}

const totalRemaining = Math.round(
Number(currentPeriod.amount ?? 0) + totalUsage,
)
const remainingDays =
dayjsExtended().daysInMonth() - dayjsExtended().get('date')
const remainingPerDay = Math.round(totalRemaining / remainingDays)

const averagePerDay = Math.round(
Number(currentPeriod.amount) / dayjsExtended().daysInMonth(),
)

const chartData = map(
groupBy(transactions, (t) => t.date),
(transactions, key) => ({
day: new Date(key).getDate(),
amount: transactions.reduce((acc, t) => acc + t.amountInVnd, 0),
}),
)

return (
<View className="flex-1 bg-card">
<PeriodControl
Expand All @@ -213,17 +234,19 @@ export default function BudgetDetailScreen() {
>
<Animated.View className="gap-6 px-6 py-6" style={summaryStyle}>
<BudgetStatistic
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
totalRemaining={currentPeriod.amount as any}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
remainingPerDay={currentPeriod.amount as any}
totalRemaining={totalRemaining}
remainingPerDay={remainingPerDay}
/>
</Animated.View>
<Animated.View
className="px-6 pb-5"
style={[{ flexGrow: 0 }, chartStyle]}
>
<BurndownChart />
<BurndownChart
totalBudget={Number(currentPeriod?.amount)}
averagePerDay={Math.abs(averagePerDay)}
data={chartData}
/>
</Animated.View>
</View>
<AnimatedSectionList
Expand Down
72 changes: 50 additions & 22 deletions apps/mobile/components/budget/budget-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { type FC, useMemo } from 'react'
import { Pressable, View } from 'react-native'

import type { BudgetItem as BudgetItemData } from '@/stores/budget/store'
import { calculateBudgetPeriodStartEndDates } from '@6pm/utilities'
import { useTransactionList } from '@/stores/transaction/hooks'
import {
calculateBudgetPeriodStartEndDates,
dayjsExtended,
} from '@6pm/utilities'
import { useUser } from '@clerk/clerk-expo'
import { first, orderBy } from 'lodash-es'
import { ChevronRightIcon } from 'lucide-react-native'
Expand All @@ -29,15 +33,23 @@ export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {
orderBy(budget.periodConfigs, 'startDate', 'desc'),
)

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const remainingBalance = (latestPeriodConfig?.amount ?? 0) as any
const { totalExpense, totalIncome } = useTransactionList({
from: latestPeriodConfig?.startDate!,
to: latestPeriodConfig?.endDate!,
budgetId: budget.id,
})

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const amountPerDay = latestPeriodConfig?.amount ?? (0 as any)
const totalBudgetUsage = totalExpense + totalIncome

const usagePercentage = Math.random() * 100
const remainingBalance = Math.round(
Number(latestPeriodConfig?.amount!) + totalBudgetUsage,
)

const usagePercentage = Math.round(
(Math.abs(totalBudgetUsage) / Number(latestPeriodConfig?.amount)) * 100,
)

const remainingDays = useMemo(() => {
const remainingDuration = useMemo(() => {
let periodEndDate: Date | null
if (latestPeriodConfig?.type === 'CUSTOM') {
periodEndDate = latestPeriodConfig?.endDate
Expand All @@ -50,22 +62,35 @@ export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {
}

if (!periodEndDate) {
return null
}

return intervalToDuration({
start: new Date(),
end: periodEndDate,
})
}, [latestPeriodConfig])

const remainingDaysText = useMemo(() => {
if (!remainingDuration) {
return t(i18n)`Unknown`
}

const duration = formatDuration(
intervalToDuration({
start: new Date(),
end: periodEndDate,
}),
{
format: ['days', 'hours'],
delimiter: ',',
},
)
const duration = formatDuration(remainingDuration, {
format: ['days', 'hours'],
delimiter: ',',
})

return t(i18n)`${duration.split(',')[0]} left`
}, [remainingDuration, i18n])

const remainingDays =
dayjsExtended().daysInMonth() - dayjsExtended().get('date')

return t(i18n)`${duration?.split(',')[0]} left`
}, [latestPeriodConfig, i18n])
const amountPerDay = remainingBalance / remainingDays

const isOver =
remainingBalance < 0 || remainingBalance < amountPerDay * remainingDays

return (
<Link
Expand Down Expand Up @@ -99,7 +124,10 @@ export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {
</View>
</View>
<View className="flex-row items-center gap-2">
<CircularProgress progress={usagePercentage} />
<CircularProgress
progress={usagePercentage}
strokeColor={isOver ? '#ef4444' : undefined}
/>
<ChevronRightIcon className="size-6 text-foreground" />
</View>
</View>
Expand All @@ -115,12 +143,12 @@ export const BudgetItem: FC<BudgetItemProps> = ({ budget }) => {
numberOfLines={1}
className="line-clamp-1 flex-1 text-muted-foreground text-sm"
>
{remainingDays}
{remainingDaysText}
</Text>
</View>
<View className="justify-end gap-1">
<AmountFormat
amount={amountPerDay}
amount={Math.round(amountPerDay)}
displayNegativeSign
className="text-xl"
/>
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/components/budget/budget-statistic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ export function BudgetStatistic({
return (
<View className="flex-row items-center justify-between gap-6">
<View className="gap-1">
<AmountFormat amount={totalRemaining} />
<AmountFormat amount={totalRemaining} displayNegativeSign />
<Text className="text-muted-foreground">
{t(i18n)`Left this month`}
</Text>
</View>
<View className="items-end gap-1">
<AmountFormat amount={remainingPerDay} />
<AmountFormat amount={remainingPerDay} displayNegativeSign />
<Text className="text-right text-muted-foreground">
{t(i18n)`Left per day`}
</Text>
Expand Down
Loading

0 comments on commit 8463550

Please sign in to comment.