import { Check, ExpandMore } from "@mui/icons-material"; import { Accordion, AccordionDetails, AccordionSummary, Alert, Box, Button, Divider, FormControl, FormHelperText, InputLabel, MenuItem, Modal, ModalProps, Paper, Select, SxProps, TextField, Typography, } from "@mui/material"; import React, { useCallback, useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { INPUT_DATE_FORMAT, moneyFormatter, OUTPUT_DATE_FORMAT, } from "@/app/utils/formatUtil"; import { PaymentInputs } from "@/app/api/projects/actions"; import dayjs from "dayjs"; import { DatePicker } from "@mui/x-date-pickers"; export interface BulkAddPaymentForm { numberOfEntries: number; amountToDivide: number; dateType: "monthly" | "weekly" | "fixed"; dateReference?: dayjs.Dayjs; description: string; } export interface Props extends Omit { onSave: (payments: PaymentInputs[]) => Promise; modalSx?: SxProps; } const modalSx: SxProps = { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", width: "90%", maxWidth: "sm", maxHeight: "90%", padding: 3, display: "flex", flexDirection: "column", gap: 2, }; let idOffset = Date.now(); const getID = () => { return ++idOffset; }; const BulkAddPaymentModal: React.FC = ({ onSave, open, onClose, modalSx: mSx, }) => { const { t } = useTranslation(); const { register, reset, trigger, formState, watch, control } = useForm({ defaultValues: { dateType: "monthly", dateReference: dayjs() }, }); const formValues = watch(); const newPayments = useMemo(() => { const { numberOfEntries, amountToDivide, dateType, dateReference = dayjs(), description, } = formValues; if (numberOfEntries > 0 && amountToDivide && description) { const dividedAmount = amountToDivide / numberOfEntries; return Array(numberOfEntries) .fill(undefined) .map((_, index) => { const date = dateType === "fixed" ? dateReference : dateReference.add( index, dateType === "monthly" ? "month" : "week", ); return { id: getID(), amount: dividedAmount, description: replaceTemplateString(description, { "{index}": (index + 1).toString(), "{date}": date.format(OUTPUT_DATE_FORMAT), }), date: date.format(INPUT_DATE_FORMAT), }; }); } else { return []; } }, [formValues]); const saveHandler = useCallback(async () => { const valid = await trigger(); if (valid) { onSave(newPayments); reset(); } }, [trigger, onSave, reset, newPayments]); const closeHandler = useCallback>( (...args) => { onClose?.(...args); reset(); }, [onClose, reset], ); return ( {t("Default Date Type")} ( )} rules={{ required: t("Required"), }} /> {"The type of date to use for each payment entry."} ( )} rules={{ required: t("Required"), }} /> { "The reference date for calculating the date for each payment entry." } }> {t("Milestone payments preview")} {newPayments.length > 0 ? ( ) : ( {t( "Please input the amount to divde, the number of payments, and the description.", )} )} ); }; const PaymentSummary: React.FC<{ paymentInputs: PaymentInputs[] }> = ({ paymentInputs, }) => { const { t } = useTranslation(); return ( {paymentInputs.map(({ id, date, description, amount }, index) => { return ( {t("Description")} {description} {t("Date")} {date} {t("Amount")} {moneyFormatter.format(amount)} {index !== paymentInputs.length - 1 && ( )} ); })} ); }; const replaceTemplateString = ( template: string, replacements: { [key: string]: string }, ): string => { let returnString = template; Object.entries(replacements).forEach(([key, replacement]) => { returnString = returnString.replaceAll(key, replacement); }); return returnString; }; export default BulkAddPaymentModal;