feat: add auto-balancing for the amount edit (#173)

* feat: add auto-balancing for the amount edit

this implementation allocates the rest of the total
to participants, whose rows have yet not been edited.

* fix: reset already edited on total amount change
This commit is contained in:
Miska Pajukangas
2024-08-02 18:04:21 +03:00
committed by GitHub
parent 002e867bc4
commit c392c06b39

View File

@@ -46,7 +46,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { Save } from 'lucide-react'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { match } from 'ts-pattern'
import { DeletePopup } from './delete-popup'
@@ -245,9 +245,78 @@ export function ExpenseForm({
}
const [isIncome, setIsIncome] = useState(Number(form.getValues().amount) < 0)
const [manuallyEditedParticipants, setManuallyEditedParticipants] = useState<
Set<string>
>(new Set())
const sExpense = isIncome ? 'income' : 'expense'
const sPaid = isIncome ? 'received' : 'paid'
useEffect(() => {
setManuallyEditedParticipants(new Set())
const newPaidFor = defaultSplittingOptions.paidFor.map((participant) => ({
...participant,
shares: String(participant.shares) as unknown as number,
}))
form.setValue('paidFor', newPaidFor, { shouldValidate: true })
}, [form.watch('splitMode'), form.watch('amount')])
useEffect(() => {
const totalAmount = Number(form.getValues().amount) || 0
const paidFor = form.getValues().paidFor
const splitMode = form.getValues().splitMode
let newPaidFor = [...paidFor]
if (
splitMode === 'EVENLY' ||
splitMode === 'BY_SHARES' ||
splitMode === 'BY_PERCENTAGE'
) {
return
} else {
// Only auto-balance for split mode 'Unevenly - By amount'
const editedParticipants = Array.from(manuallyEditedParticipants)
let remainingAmount = totalAmount
let remainingParticipants = newPaidFor.length - editedParticipants.length
newPaidFor = newPaidFor.map((participant) => {
if (editedParticipants.includes(participant.participant)) {
const participantShare = Number(participant.shares) || 0
if (splitMode === 'BY_AMOUNT') {
remainingAmount -= participantShare
}
return participant
}
return participant
})
if (remainingParticipants > 0) {
let amountPerRemaining = 0
if (splitMode === 'BY_AMOUNT') {
amountPerRemaining = remainingAmount / remainingParticipants
}
newPaidFor = newPaidFor.map((participant) => {
if (!editedParticipants.includes(participant.participant)) {
return {
...participant,
shares: String(
Number(amountPerRemaining.toFixed(2)),
) as unknown as number,
}
}
return participant
})
}
form.setValue('paidFor', newPaidFor, { shouldValidate: true })
}
}, [
manuallyEditedParticipants,
form.watch('amount'),
form.watch('splitMode'),
])
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(submit)}>
@@ -570,7 +639,7 @@ export function ExpenseForm({
participant === id,
)?.shares
}
onChange={(event) =>
onChange={(event) => {
field.onChange(
field.value.map((p) =>
p.participant === id
@@ -584,7 +653,10 @@ export function ExpenseForm({
: p,
),
)
}
setManuallyEditedParticipants(
(prev) => new Set(prev).add(id),
)
}}
inputMode={
form.getValues().splitMode ===
'BY_AMOUNT'