mirror of
https://github.com/spliit-app/spliit.git
synced 2026-03-05 20:26:11 +01:00
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:
@@ -46,7 +46,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
|
|||||||
import { Save } from 'lucide-react'
|
import { Save } from 'lucide-react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useSearchParams } from 'next/navigation'
|
import { useSearchParams } from 'next/navigation'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { match } from 'ts-pattern'
|
import { match } from 'ts-pattern'
|
||||||
import { DeletePopup } from './delete-popup'
|
import { DeletePopup } from './delete-popup'
|
||||||
@@ -245,9 +245,78 @@ export function ExpenseForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [isIncome, setIsIncome] = useState(Number(form.getValues().amount) < 0)
|
const [isIncome, setIsIncome] = useState(Number(form.getValues().amount) < 0)
|
||||||
|
const [manuallyEditedParticipants, setManuallyEditedParticipants] = useState<
|
||||||
|
Set<string>
|
||||||
|
>(new Set())
|
||||||
|
|
||||||
const sExpense = isIncome ? 'income' : 'expense'
|
const sExpense = isIncome ? 'income' : 'expense'
|
||||||
const sPaid = isIncome ? 'received' : 'paid'
|
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 (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(submit)}>
|
<form onSubmit={form.handleSubmit(submit)}>
|
||||||
@@ -570,7 +639,7 @@ export function ExpenseForm({
|
|||||||
participant === id,
|
participant === id,
|
||||||
)?.shares
|
)?.shares
|
||||||
}
|
}
|
||||||
onChange={(event) =>
|
onChange={(event) => {
|
||||||
field.onChange(
|
field.onChange(
|
||||||
field.value.map((p) =>
|
field.value.map((p) =>
|
||||||
p.participant === id
|
p.participant === id
|
||||||
@@ -584,7 +653,10 @@ export function ExpenseForm({
|
|||||||
: p,
|
: p,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
setManuallyEditedParticipants(
|
||||||
|
(prev) => new Set(prev).add(id),
|
||||||
|
)
|
||||||
|
}}
|
||||||
inputMode={
|
inputMode={
|
||||||
form.getValues().splitMode ===
|
form.getValues().splitMode ===
|
||||||
'BY_AMOUNT'
|
'BY_AMOUNT'
|
||||||
|
|||||||
Reference in New Issue
Block a user