Skip to content

Commit

Permalink
feat(mobile): [Transaction] Create transaction with budget
Browse files Browse the repository at this point in the history
  • Loading branch information
bkdev98 committed Jul 23, 2024
1 parent f6486e3 commit b9289d3
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 20 deletions.
20 changes: 13 additions & 7 deletions apps/mobile/app/(app)/transaction/[transactionId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { zodResolver } from '@hookform/resolvers/zod'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { PortalHost, useModalPortalRoot } from '@rn-primitives/portal'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import * as Haptics from 'expo-haptics'
import { useLocalSearchParams, useRouter } from 'expo-router'
Expand All @@ -22,6 +23,7 @@ export default function EditRecordScreen() {
const { data: transaction } = useTransactionDetail(transactionId!)
const router = useRouter()
const queryClient = useQueryClient()
const { sideOffset, ...rootProps } = useModalPortalRoot()

const transactionForm = useForm<TransactionFormValues>({
resolver: zodResolver(zTransactionFormValues),
Expand All @@ -31,7 +33,7 @@ export default function EditRecordScreen() {
amount: Math.abs(transaction?.amount ?? 0),
date: transaction?.date,
note: transaction?.note ?? '',
budgetId: transaction?.budgetId ?? undefined,
budgetId: transaction?.budgetId,
categoryId: transaction?.categoryId ?? undefined,
},
})
Expand Down Expand Up @@ -108,11 +110,15 @@ export default function EditRecordScreen() {
}

return (
<TransactionForm
form={transactionForm}
onSubmit={(values) => mutateAsync({ id: transaction.id, data: values })}
onCancel={router.back}
onDelete={handleDelete}
/>
<View className="flex-1 bg-card" {...rootProps}>
<TransactionForm
sideOffset={sideOffset}
form={transactionForm}
onSubmit={(values) => mutateAsync({ id: transaction.id, data: values })}
onCancel={router.back}
onDelete={handleDelete}
/>
<PortalHost name="transaction-form" />
</View>
)
}
6 changes: 5 additions & 1 deletion apps/mobile/app/(app)/transaction/new-record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { zodResolver } from '@hookform/resolvers/zod'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { PortalHost, useModalPortalRoot } from '@rn-primitives/portal'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import * as Haptics from 'expo-haptics'
import { useLocalSearchParams, useRouter } from 'expo-router'
Expand All @@ -30,6 +31,7 @@ export default function NewRecordScreen() {
const { data: walletAccounts } = useWallets()
const defaultCurrency = useDefaultCurrency()
const defaultWallet = walletAccounts?.[0]
const { sideOffset, ...rootProps } = useModalPortalRoot()

const params = useLocalSearchParams()
const parsedParams = zUpdateTransaction.parse(params)
Expand Down Expand Up @@ -79,7 +81,7 @@ export default function NewRecordScreen() {
}

return (
<View className="flex-1 bg-card">
<View className="flex-1 bg-card" {...rootProps}>
<PagerView
ref={ref}
overdrag={false}
Expand All @@ -88,6 +90,7 @@ export default function NewRecordScreen() {
style={{ flex: 1 }}
>
<TransactionForm
sideOffset={sideOffset}
form={transactionForm}
onSubmit={mutateAsync}
onCancel={router.back}
Expand All @@ -112,6 +115,7 @@ export default function NewRecordScreen() {
}}
/>
</PagerView>
<PortalHost name="transaction-form" />
</View>
)
}
108 changes: 108 additions & 0 deletions apps/mobile/components/transaction/select-budget-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { cn } from '@/lib/utils'
import { useBudgetList } from '@/stores/budget/hooks'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { LandPlotIcon } from 'lucide-react-native'
import { useMemo } from 'react'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select'
import { Text } from '../ui/text'

export function SelectBudgetField({
value,
onSelect,
disabled,
sideOffset,
}: {
value?: string | null
onSelect?: (type?: string | null) => void
disabled?: boolean
sideOffset?: number
}) {
const { i18n } = useLingui()
const { data: budgets } = useBudgetList()

const defaultValue = useMemo(
() => ({
value: 'NO_SELECT',
label: t(i18n)`No budget selected`,
usagePercentage: 0,
}),
[i18n],
)

const options = useMemo(
() => [
defaultValue,
...(budgets?.map((walletAccount) => ({
value: walletAccount.id,
label: walletAccount.name,
usagePercentage: 50,
})) || []),
],
[budgets, defaultValue],
)

return (
<Select
defaultValue={defaultValue}
value={options.find((option) => option.value === value) ?? defaultValue}
onValueChange={(selected) => {
if (selected?.value === 'NO_SELECT') {
onSelect?.(null)
return
}
onSelect?.(selected?.value)
}}
>
<SelectTrigger
disabled={disabled}
hideArrow
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
className="gap-2 items-center flex-row rounded-full"
>
<LandPlotIcon className="w-5 h-5 text-primary" />
<SelectValue
className={cn(
'text-primary font-sans font-medium',
(!value || value === 'NO_SELECT') && '!text-muted-foreground',
)}
placeholder={t(i18n)`All Accounts`}
>
{value}
</SelectValue>
</SelectTrigger>
<SelectContent
sideOffset={(sideOffset || 0) + 6}
align="center"
portalHost="transaction-form"
>
<SelectGroup className="px-1 max-w-[260px]">
{options.map((option) => (
<SelectItem
key={option.value}
value={option.value}
label={option.label}
className="flex-row justify-between items-center"
extra={
option.value !== 'NO_SELECT' && (
<Text className="text-muted-foreground">
{option.usagePercentage}%
</Text>
)
}
>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
26 changes: 18 additions & 8 deletions apps/mobile/components/transaction/transaction-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { TransactionFormValues } from '@6pm/validation'
import { t } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import * as Haptics from 'expo-haptics'
import { LandPlot, ScanTextIcon, Trash2Icon, XIcon } from 'lucide-react-native'
import { ScanTextIcon, Trash2Icon, XIcon } from 'lucide-react-native'
import {
Controller,
FormProvider,
Expand All @@ -22,6 +22,7 @@ import { TextTicker } from '../text-ticker'
import { Button } from '../ui/button'
import { Text } from '../ui/text'
import { SelectAccountField } from './select-account-field'
import { SelectBudgetField } from './select-budget-field'
import { SelectCategoryField } from './select-category-field'

type TransactionFormProps = {
Expand All @@ -30,6 +31,7 @@ type TransactionFormProps = {
onDelete?: () => void
onOpenScanner?: () => void
form: UseFormReturn<TransactionFormValues>
sideOffset?: number
}

function TransactionAmount() {
Expand Down Expand Up @@ -71,6 +73,7 @@ export const TransactionForm = ({
onCancel,
onDelete,
onOpenScanner,
sideOffset,
}: TransactionFormProps) => {
const { i18n } = useLingui()

Expand Down Expand Up @@ -118,15 +121,22 @@ export const TransactionForm = ({
</View>
</View>
<View className="flex-1 items-center justify-center pb-12">
<View className="w-full h-24 justify-end mb-4">
<View className="w-full h-24 justify-end mb-2">
<TransactionAmount />
</View>
<Button variant="outline" size="sm" className="rounded-full">
<LandPlot className="w-5 h-5 text-primary" />
<Text className="text-muted-foreground">
{t(i18n)`No budget selected`}
</Text>
</Button>
<Controller
name="budgetId"
control={form.control}
render={({ field: { onChange, value } }) => (
<SelectBudgetField
sideOffset={sideOffset}
value={value}
onSelect={(type) => {
onChange(type)
}}
/>
)}
/>
<InputField
name="note"
placeholder={t(i18n)`transaction note`}
Expand Down
7 changes: 5 additions & 2 deletions apps/mobile/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ SelectLabel.displayName = SelectPrimitive.Label.displayName

const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> & {
extra?: React.ReactNode
}
>(({ className, children, extra, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
Expand All @@ -174,6 +176,7 @@ const SelectItem = React.forwardRef<
</SelectPrimitive.ItemIndicator>
</View>
<SelectPrimitive.ItemText className="text-sm font-sans text-popover-foreground native:text-base web:group-focus:text-accent-foreground" />
{extra}
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/mutations/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export async function createTransaction(data: TransactionFormValues) {
z.object({
// override Decimal type with number
amount: z.number({ coerce: true }),
amountInVnd: z.number({ coerce: true }),
}),
).parse(transaction)
}
Expand Down Expand Up @@ -51,6 +52,7 @@ export async function updateTransaction({
z.object({
// override Decimal type with number
amount: z.number({ coerce: true }),
amountInVnd: z.number({ coerce: true }),
}),
).parse(transaction)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/validation/src/transaction.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const zCreateTransaction = z.object({
amount: z.number(),
currency: z.string(),
note: z.string().optional(),
budgetId: z.string().optional(),
budgetId: z.string().nullable().optional(),
walletAccountId: z.string(),
categoryId: z.string().optional(),
})
Expand All @@ -17,7 +17,7 @@ export const zUpdateTransaction = z.object({
amount: z.number({ coerce: true }).optional(),
currency: z.string().optional(),
note: z.string().optional(),
budgetId: z.string().optional(),
budgetId: z.string().nullable().optional(),
walletAccountId: z.string().optional(),
categoryId: z.string().optional(),
})
Expand Down

0 comments on commit b9289d3

Please sign in to comment.