diff options
| author | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 11:11:53 -0500 |
|---|---|---|
| committer | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 11:11:53 -0500 |
| commit | 6611e3a187e5398f686449938b7cf1ddbfcb5392 (patch) | |
| tree | 0fae44f29faf8efa68bcdb113ae1ca003cbd024b | |
| parent | ec981fd237a50dfc547d8c162b22f4e1a691c9fc (diff) | |
Store amounts as cents
| -rw-r--r-- | src/app/groups/[groupId]/balances-list.tsx | 69 | ||||
| -rw-r--r-- | src/app/groups/[groupId]/expenses/expense-list.tsx | 2 | ||||
| -rw-r--r-- | src/app/groups/[groupId]/reimbursement-list.tsx | 10 | ||||
| -rw-r--r-- | src/app/groups/[groupId]/save-recent-group.tsx | 2 | ||||
| -rw-r--r-- | src/components/expense-form.tsx | 6 | ||||
| -rw-r--r-- | src/lib/balances.ts | 6 | ||||
| -rw-r--r-- | src/lib/schemas.ts | 24 |
7 files changed, 67 insertions, 52 deletions
diff --git a/src/app/groups/[groupId]/balances-list.tsx b/src/app/groups/[groupId]/balances-list.tsx index 26092ec..0e321bb 100644 --- a/src/app/groups/[groupId]/balances-list.tsx +++ b/src/app/groups/[groupId]/balances-list.tsx @@ -15,51 +15,38 @@ export function BalancesList({ balances, participants, currency }: Props) { return ( <div className="text-sm"> - {participants.map((participant) => ( - <div - key={participant.id} - className={cn( - 'flex', - balances[participant.id]?.total >= 0 || 'flex-row-reverse', - )} - > + {participants.map((participant) => { + const balance = balances[participant.id]?.total ?? 0 + const isLeft = balance >= 0 + return ( <div - className={cn( - 'w-1/2 p-2', - balances[participant.id]?.total >= 0 && 'text-right', - )} + key={participant.id} + className={cn('flex', isLeft || 'flex-row-reverse')} > - {participant.name} - </div> - <div - className={cn( - 'w-1/2 relative', - balances[participant.id]?.total >= 0 || 'text-right', - )} - > - <div className="absolute inset-0 p-2 z-20"> - {currency} {(balances[participant.id]?.total ?? 0).toFixed(2)} + <div className={cn('w-1/2 p-2', isLeft && 'text-right')}> + {participant.name} + </div> + <div className={cn('w-1/2 relative', isLeft || 'text-right')}> + <div className="absolute inset-0 p-2 z-20"> + {currency} {(balance / 100).toFixed(2)} + </div> + {balance !== 0 && ( + <div + className={cn( + 'absolute top-1 h-7 z-10', + isLeft + ? 'bg-green-200 left-0 rounded-r-lg border border-green-300' + : 'bg-red-200 right-0 rounded-l-lg border border-red-300', + )} + style={{ + width: (Math.abs(balance) / maxBalance) * 100 + '%', + }} + ></div> + )} </div> - {balances[participant.id]?.total !== 0 && ( - <div - className={cn( - 'absolute top-1 h-7 z-10', - balances[participant.id]?.total > 0 - ? 'bg-green-200 left-0 rounded-r-lg border border-green-300' - : 'bg-red-200 right-0 rounded-l-lg border border-red-300', - )} - style={{ - width: - (Math.abs(balances[participant.id]?.total ?? 0) / - maxBalance) * - 100 + - '%', - }} - ></div> - )} </div> - </div> - ))} + ) + })} </div> ) } diff --git a/src/app/groups/[groupId]/expenses/expense-list.tsx b/src/app/groups/[groupId]/expenses/expense-list.tsx index 0c03756..1ba7df7 100644 --- a/src/app/groups/[groupId]/expenses/expense-list.tsx +++ b/src/app/groups/[groupId]/expenses/expense-list.tsx @@ -53,7 +53,7 @@ export function ExpenseList({ </div> <div className="flex items-center"> <div className="tabular-nums whitespace-nowrap font-bold"> - {currency} {expense.amount.toFixed(2)} + {currency} {(expense.amount / 100).toFixed(2)} </div> <Button size="icon" variant="link" className="-my-2" asChild> <Link href={`/groups/${groupId}/expenses/${expense.id}/edit`}> diff --git a/src/app/groups/[groupId]/reimbursement-list.tsx b/src/app/groups/[groupId]/reimbursement-list.tsx index e8400ca..54ee9d7 100644 --- a/src/app/groups/[groupId]/reimbursement-list.tsx +++ b/src/app/groups/[groupId]/reimbursement-list.tsx @@ -16,6 +16,14 @@ export function ReimbursementList({ currency, groupId, }: Props) { + if (reimbursements.length === 0) { + return ( + <p className="px-6 text-sm pb-6"> + It looks like your group doesn’t need any reimbursement 😁 + </p> + ) + } + const getParticipant = (id: string) => participants.find((p) => p.id === id) return ( <div className="text-sm"> @@ -35,7 +43,7 @@ export function ReimbursementList({ </Button> </div> <div> - {currency} {reimbursement.amount.toFixed(2)} + {currency} {(reimbursement.amount / 100).toFixed(2)} </div> </div> ))} diff --git a/src/app/groups/[groupId]/save-recent-group.tsx b/src/app/groups/[groupId]/save-recent-group.tsx index e55d7c8..084681a 100644 --- a/src/app/groups/[groupId]/save-recent-group.tsx +++ b/src/app/groups/[groupId]/save-recent-group.tsx @@ -12,7 +12,7 @@ type Props = { export function SaveGroupLocally({ group }: Props) { useEffect(() => { saveRecentGroup(group) - }, []) + }, [group]) return null } diff --git a/src/components/expense-form.tsx b/src/components/expense-form.tsx index 3f6490f..d6a8049 100644 --- a/src/components/expense-form.tsx +++ b/src/components/expense-form.tsx @@ -47,7 +47,7 @@ export function ExpenseForm({ group, expense, onSubmit, onDelete }: Props) { defaultValues: expense ? { title: expense.title, - amount: expense.amount, + amount: String(expense.amount / 100) as unknown as number, // hack paidBy: expense.paidById, paidFor: expense.paidFor.map(({ participantId }) => participantId), isReimbursement: expense.isReimbursement, @@ -55,7 +55,9 @@ export function ExpenseForm({ group, expense, onSubmit, onDelete }: Props) { : searchParams.get('reimbursement') ? { title: 'Reimbursement', - amount: Number(searchParams.get('amount')) || 0, + amount: String( + (Number(searchParams.get('amount')) || 0) / 100, + ) as unknown as number, // hack paidBy: searchParams.get('from') ?? undefined, paidFor: [searchParams.get('to') ?? undefined], isReimbursement: true, diff --git a/src/lib/balances.ts b/src/lib/balances.ts index 20a83fb..77f4622 100644 --- a/src/lib/balances.ts +++ b/src/lib/balances.ts @@ -42,7 +42,7 @@ export function getBalances( } function divide(total: number, count: number, isLast: boolean): number { - if (!isLast) return Math.floor((total * 100) / count) / 100 + if (!isLast) return Math.floor(total / count) return total - divide(total, count, false) * (count - 1) } @@ -58,7 +58,7 @@ export function getSuggestedReimbursements( while (balancesArray.length > 1) { const first = balancesArray[0] const last = balancesArray[balancesArray.length - 1] - const amount = Math.round(first.total * 100 + last.total * 100) / 100 + const amount = first.total + last.total if (first.total > -last.total) { reimbursements.push({ from: last.participantId, @@ -77,5 +77,5 @@ export function getSuggestedReimbursements( balancesArray.shift() } } - return reimbursements + return reimbursements.filter(({ amount }) => amount !== 0) } diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index 5c3489b..ec7f3fa 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -42,9 +42,27 @@ export const expenseFormSchema = z.object({ title: z .string({ required_error: 'Please enter a title.' }) .min(2, 'Enter at least two characters.'), - amount: z.coerce - .number({ required_error: 'You must enter an amount.' }) - .min(0.01, 'The amount must be higher than 0.01.'), + amount: z + .union( + [ + z.number(), + z.string().transform((value, ctx) => { + const valueAsNumber = Number(value) + if (Number.isNaN(valueAsNumber)) + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid number.', + }) + return Math.floor(valueAsNumber * 100) + }), + ], + { required_error: 'You must enter an amount.' }, + ) + .refine((amount) => amount >= 1, 'The amount must be higher than 0.01.') + .refine( + (amount) => amount <= 10_000_000_00, + 'The amount must be lower than 10,000,000.', + ), paidBy: z.string({ required_error: 'You must select a participant.' }), paidFor: z .array(z.string()) |
