@@ -47,6 +47,7 @@ | |||
"react-dom": "^18", | |||
"react-hook-form": "^7.49.2", | |||
"react-i18next": "^13.5.0", | |||
"react-idle-timer": "^5.7.2", | |||
"react-intl": "^6.5.5", | |||
"react-number-format": "^5.3.4", | |||
"react-select": "^5.8.0", | |||
@@ -9043,6 +9044,15 @@ | |||
} | |||
} | |||
}, | |||
"node_modules/react-idle-timer": { | |||
"version": "5.7.2", | |||
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-5.7.2.tgz", | |||
"integrity": "sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==", | |||
"peerDependencies": { | |||
"react": ">=16", | |||
"react-dom": ">=16" | |||
} | |||
}, | |||
"node_modules/react-intl": { | |||
"version": "6.6.2", | |||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.2.tgz", | |||
@@ -21,6 +21,33 @@ export const OUTPUT_DATE_FORMAT = "YYYY/MM/DD"; | |||
export const OUTPUT_TIME_FORMAT = "HH:mm:ss"; | |||
export const truncateMoney = (amount: number | undefined) => { | |||
if (!amount) { | |||
return amount; | |||
} | |||
const { maximumFractionDigits, minimumFractionDigits } = | |||
moneyFormatter.resolvedOptions(); | |||
const fractionDigits = maximumFractionDigits ?? minimumFractionDigits ?? 0; | |||
const factor = Math.pow(10, fractionDigits); | |||
const truncatedAmount = Math.floor(amount * factor) / factor; | |||
return truncatedAmount; | |||
}; | |||
export const sumMoney = (a: number, b: number) => { | |||
const { maximumFractionDigits, minimumFractionDigits } = | |||
moneyFormatter.resolvedOptions(); | |||
const fractionDigits = maximumFractionDigits ?? minimumFractionDigits ?? 0; | |||
const factor = Math.pow(10, fractionDigits); | |||
const sum = Math.round(a * factor) + Math.round(b * factor); | |||
return sum / factor; | |||
}; | |||
export const convertDateToString = ( | |||
date: Date, | |||
format: string = OUTPUT_DATE_FORMAT, | |||
@@ -33,8 +60,8 @@ export const convertDateArrayToString = ( | |||
format: string = OUTPUT_DATE_FORMAT, | |||
needTime: boolean = false, | |||
) => { | |||
if (dateArray === null){ | |||
return "-" | |||
if (dateArray === null) { | |||
return "-"; | |||
} | |||
if (dateArray.length === 6) { | |||
if (!needTime) { | |||
@@ -48,8 +75,8 @@ export const convertDateArrayToString = ( | |||
return dayjs(dateString).format(format); | |||
} | |||
} | |||
if (dateArray.length === 0){ | |||
return "-" | |||
if (dateArray.length === 0) { | |||
return "-"; | |||
} | |||
}; | |||
@@ -134,8 +161,8 @@ export function convertLocaleStringToNumber(numberString: string): number { | |||
} | |||
export function timestampToDateString(timestamp: string): string { | |||
if (timestamp === null){ | |||
return "-" | |||
if (timestamp === null) { | |||
return "-"; | |||
} | |||
const date = new Date(timestamp); | |||
const year = date.getFullYear(); | |||
@@ -26,6 +26,7 @@ import { | |||
INPUT_DATE_FORMAT, | |||
moneyFormatter, | |||
OUTPUT_DATE_FORMAT, | |||
truncateMoney, | |||
} from "@/app/utils/formatUtil"; | |||
import { PaymentInputs } from "@/app/api/projects/actions"; | |||
import dayjs from "dayjs"; | |||
@@ -94,7 +95,7 @@ const BulkAddPaymentModal: React.FC<Props> = ({ | |||
amountToDivide && | |||
description | |||
) { | |||
const dividedAmount = amountToDivide / numberOfEntries; | |||
const dividedAmount = truncateMoney(amountToDivide / numberOfEntries)!; | |||
return Array(numberOfEntries) | |||
.fill(undefined) | |||
.map((_, index) => { | |||
@@ -35,6 +35,7 @@ import { | |||
INPUT_DATE_FORMAT, | |||
OUTPUT_DATE_FORMAT, | |||
moneyFormatter, | |||
truncateMoney, | |||
} from "@/app/utils/formatUtil"; | |||
import isDate from "lodash/isDate"; | |||
import BulkAddPaymentModal from "./BulkAddPaymentModal"; | |||
@@ -148,7 +149,11 @@ const MilestoneSection: React.FC<Props> = ({ taskGroupId }) => { | |||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |||
const { _isNew, _errors, ...updatedRow } = newRow; | |||
setPayments((ps) => | |||
ps.map((p) => (p.id === updatedRow.id ? updatedRow : p)), | |||
ps.map((p) => | |||
p.id === updatedRow.id | |||
? { ...updatedRow, amount: truncateMoney(updatedRow.amount) } | |||
: p, | |||
), | |||
); | |||
return updatedRow; | |||
}, | |||
@@ -246,6 +251,9 @@ const MilestoneSection: React.FC<Props> = ({ taskGroupId }) => { | |||
width: 300, | |||
editable: true, | |||
type: "number", | |||
valueGetter(params) { | |||
return truncateMoney(params.value); | |||
}, | |||
valueFormatter(params) { | |||
return moneyFormatter.format(params.value); | |||
}, | |||
@@ -1,6 +1,6 @@ | |||
import { CreateProjectInputs } from "@/app/api/projects/actions"; | |||
import { TaskGroup } from "@/app/api/tasks"; | |||
import { moneyFormatter } from "@/app/utils/formatUtil"; | |||
import { moneyFormatter, sumMoney } from "@/app/utils/formatUtil"; | |||
import { | |||
Button, | |||
Divider, | |||
@@ -52,9 +52,12 @@ const ProjectTotalFee: React.FC<Props> = ({ taskGroups }) => { | |||
<Stack spacing={1}> | |||
{taskGroups.map((group, index) => { | |||
const payments = milestones[group.id]?.payments || []; | |||
const paymentTotal = payments.reduce((acc, p) => acc + p.amount, 0); | |||
const paymentTotal = payments.reduce( | |||
(acc, p) => sumMoney(acc, p.amount), | |||
0, | |||
); | |||
projectTotal += paymentTotal; | |||
projectTotal = sumMoney(projectTotal, paymentTotal); | |||
return ( | |||
<Stack | |||