Skip to content

Commit

Permalink
Add ability to write math expression in amount field
Browse files Browse the repository at this point in the history
  • Loading branch information
parthokunda committed Jul 27, 2024
1 parent e990e00 commit 2531142
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 34 deletions.
13 changes: 9 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"dayjs": "^1.11.10",
"embla-carousel-react": "^8.0.0-rc21",
"lucide-react": "^0.290.0",
"math-expression-evaluator": "^2.0.5",
"nanoid": "^5.0.4",
"next": "^14.2.3",
"next-s3-upload": "^0.3.4",
Expand Down
72 changes: 43 additions & 29 deletions src/components/expense-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ import { match } from 'ts-pattern'
import { DeletePopup } from './delete-popup'
import { extractCategoryFromTitle } from './expense-form-actions'
import { Textarea } from './ui/textarea'
import Mexp from 'math-expression-evaluator'

const mexp = new Mexp()

export type Props = {
group: NonNullable<Awaited<ReturnType<typeof getGroup>>>
Expand Down Expand Up @@ -245,6 +248,7 @@ export function ExpenseForm({
}

const [isIncome, setIsIncome] = useState(Number(form.getValues().amount) < 0)
const [evaluatedAmount, setEvaluatedAmount] = useState('0')
const sExpense = isIncome ? 'income' : 'expense'
const sPaid = isIncome ? 'received' : 'paid'

Expand Down Expand Up @@ -322,47 +326,57 @@ export function ExpenseForm({
<span>{group.currency}</span>
<FormControl>
<Input
className="text-base max-w-[120px]"
className="text-base"
type="text"
inputMode="decimal"
placeholder="0.00"
onChange={(event) => {
const v = enforceCurrencyPattern(event.target.value)
const income = Number(v) < 0
setIsIncome(income)
if (income) form.setValue('isReimbursement', false)
let v = (event.target.value)
if (v === ''){
setEvaluatedAmount('0')
}
else{
try{
const evaluatedValue = Number(mexp.eval(v)).toFixed(2).replace(/\.?0+$/, '') // replace trailing zeros
setEvaluatedAmount(evaluatedValue)
const income = Number(evaluatedValue) < 0
setIsIncome(income)
if (income) form.setValue('isReimbursement', false)
}
catch{
setEvaluatedAmount('Invalid Expression')
}
}
onChange(v)
}}
onFocus={(e) => {
// we're adding a small delay to get around safaris issue with onMouseUp deselecting things again
const target = e.currentTarget
setTimeout(() => target.select(), 1)
}}
{...field}
/>
</FormControl>
</div>
<FormMessage />
<div className="flex flex-row gap-2 items-center justify-between">
<div className="text-white/50 pl-5">
{' = ' + evaluatedAmount}
</div>
{!isIncome && (
<FormField
control={form.control}
name="isReimbursement"
render={({ field }) => (
<FormItem className="flex flex-row gap-2 items-center space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>Reimbursement</FormLabel>
</FormItem>

{!isIncome && (
<FormField
control={form.control}
name="isReimbursement"
render={({ field }) => (
<FormItem className="flex flex-row gap-2 items-center space-y-0 pt-2">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div>
<FormLabel>This is a reimbursement</FormLabel>
</div>
</FormItem>
)}
/>
)}
)}
/>
)}
</div>
</FormItem>
)}
/>
Expand Down
5 changes: 4 additions & 1 deletion src/lib/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { SplitMode } from '@prisma/client'
import * as z from 'zod'
import Mexp from 'math-expression-evaluator'

const mexp = new Mexp()

export const groupFormSchema = z
.object({
Expand Down Expand Up @@ -51,7 +54,7 @@ export const expenseFormSchema = z
[
z.number(),
z.string().transform((value, ctx) => {
const valueAsNumber = Number(value)
const valueAsNumber = Number(mexp.eval(value).toFixed(2).replace(/\.?0+$/, ''))
if (Number.isNaN(valueAsNumber))
ctx.addIssue({
code: z.ZodIssueCode.custom,
Expand Down

0 comments on commit 2531142

Please sign in to comment.