diff --git a/package-lock.json b/package-lock.json index 4d8a875..a554296 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index fff7d45..a35dd02 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -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(); diff --git a/src/components/CreateProject/BulkAddPaymentModal.tsx b/src/components/CreateProject/BulkAddPaymentModal.tsx index 4e92d4d..ff4594e 100644 --- a/src/components/CreateProject/BulkAddPaymentModal.tsx +++ b/src/components/CreateProject/BulkAddPaymentModal.tsx @@ -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 = ({ amountToDivide && description ) { - const dividedAmount = amountToDivide / numberOfEntries; + const dividedAmount = truncateMoney(amountToDivide / numberOfEntries)!; return Array(numberOfEntries) .fill(undefined) .map((_, index) => { diff --git a/src/components/CreateProject/MilestoneSection.tsx b/src/components/CreateProject/MilestoneSection.tsx index 0275189..34f6bf8 100644 --- a/src/components/CreateProject/MilestoneSection.tsx +++ b/src/components/CreateProject/MilestoneSection.tsx @@ -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 = ({ 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 = ({ taskGroupId }) => { width: 300, editable: true, type: "number", + valueGetter(params) { + return truncateMoney(params.value); + }, valueFormatter(params) { return moneyFormatter.format(params.value); }, diff --git a/src/components/CreateProject/ProjectTotalFee.tsx b/src/components/CreateProject/ProjectTotalFee.tsx index 0db48e5..7ff3eb0 100644 --- a/src/components/CreateProject/ProjectTotalFee.tsx +++ b/src/components/CreateProject/ProjectTotalFee.tsx @@ -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 = ({ taskGroups }) => { {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 (