"use client"; import { DocumentIcon, TicketIcon, TrashIcon } from "@heroicons/react/24/outline"; import { Bill, BilledTo, BillingLocation } from "../lib/db-types"; import React, { FC, useEffect } from "react"; import { useFormState } from "react-dom"; import { updateOrAddBill } from "../lib/actions/billActions"; import Link from "next/link"; import { formatYearMonth } from "../lib/format"; import { decodeFromImage, DecodeResult, findDecodePdf417 } from "../lib/pdf/barcodeDecoder"; import { useLocale, useTranslations } from "next-intl"; import { Pdf417Barcode } from "./Pdf417Barcode"; import { InfoBox } from "./InfoBox"; // Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment // This is a workaround for that const updateOrAddBillMiddleware = (locationId: string, billId: string | undefined, billYear: number | undefined, billMonth: number | undefined, prevState: any, formData: FormData) => { // URL encode the file name of the attachment so it is correctly sent to the server const billAttachment = formData.get('billAttachment') as File; formData.set('billAttachment', billAttachment, encodeURIComponent(billAttachment.name)); return updateOrAddBill(locationId, billId, billYear, billMonth, prevState, formData); } export interface BillEditFormProps { location: BillingLocation, bill?: Bill, } export const BillEditForm: FC = ({ location, bill }) => { const t = useTranslations("bill-edit-form"); const locale = useLocale(); const { _id: billID, name, paid, billedTo = BilledTo.Tenant, attachment, notes, payedAmount: initialPayedAmount, proofOfPayment } = bill ?? { _id: undefined, name: "", paid: false, notes: "" }; const { yearMonth: { year: billYear, month: billMonth }, _id: locationID, proofOfPaymentType } = location; const initialState = { message: null, errors: {} }; const handleAction = updateOrAddBillMiddleware.bind(null, locationID, billID, billYear, billMonth); const [isScanningPDF, setIsScanningPDF] = React.useState(false); const [state, dispatch] = useFormState(handleAction, initialState); const [isPaid, setIsPaid] = React.useState(paid); const [billedToValue, setBilledToValue] = React.useState(billedTo); const [payedAmount, setPayedAmount] = React.useState(initialPayedAmount ? `${initialPayedAmount / 100}` : ""); // legacy support - to be removed const [hub3aText, setHub3aText] = React.useState(bill?.hub3aText); const [barcodeResults, setBarcodeResults] = React.useState | null>(null); useEffect(() => { // migrating the legacy `barcodeImage` field to `hub3aText` // by converting it to `hub3aText` if (!hub3aText && bill?.barcodeImage) { decodeFromImage(bill.barcodeImage).then(results => { if (results) { const { hub3aText: decodedHub3aText, } = results; setHub3aText(decodedHub3aText); } }).catch(error => { console.error('Failed to migrate barcodeImage to hub3aText:', error); }); } }, [bill?.barcodeImage, hub3aText]); const billedTo_handleChange = (event: React.ChangeEvent) => { setBilledToValue(event.target.value as BilledTo); } const billPaid_handleChange = (event: React.ChangeEvent) => { setIsPaid(event.target.checked); } const payedAmount_handleChange = (event: React.ChangeEvent) => { setPayedAmount(event.target.value); } const billAttachment_handleChange = async (event: React.ChangeEvent) => { setIsScanningPDF(true); setPayedAmount(""); setBarcodeResults(null); const results = await findDecodePdf417(event); if (results && results.length > 0) { if (results.length === 1) { const { hub3aText, billInfo } = results[0]; setPayedAmount(`${billInfo.amount / 100}`); setHub3aText(hub3aText); } else { setPayedAmount(""); setBarcodeResults(results); setHub3aText(undefined); } } setIsScanningPDF(false); } const handleBarcodeSelectClick = (result: DecodeResult) => { setPayedAmount(`${result.billInfo.amount / 100}`); setHub3aText(result.hub3aText); setBarcodeResults(null); } return (

{`${formatYearMonth(location.yearMonth)} ${location.name}`}

{ // don't show the delete button if we are adding a new bill bill ? : null }
{state.errors?.billName && state.errors.billName.map((error: string) => (

{error}

))}
{ // attachment ? {decodeURIComponent(attachment.fileName)} : null }
{ isScanningPDF &&
{t("scanning-pdf")}
} { // if multiple results are found, show them as a list // and notify the user to select the correct one barcodeResults && barcodeResults.length > 0 && <>
}
{state.errors?.billAttachment && state.errors.billAttachment.map((error: string) => (

{error}

))}
{state.errors?.payedAmount && state.errors.payedAmount.map((error: string) => (

{error}

))}
{ hub3aText ?

{t.rich('barcode-disclaimer', { br: () =>
})}

: null }
{state.errors?.billNotes && state.errors.billNotes.map((error: string) => (

{error}

))}
{t("billed-to-info")} {t("billed-to-legend")}
{ // IF proof of payment type is "per-bill" and proof of payment was uploaded proofOfPaymentType === "per-bill" && proofOfPayment?.uploadedAt ?
{t("upload-proof-of-payment-legend")} { // IF file name is available, show link to download // ELSE it's not available that means that the uploaded file was purged by housekeeping // -> don't show anything proofOfPayment.fileName ? (
{decodeURIComponent(proofOfPayment.fileName)}
) : null }
: null } {/* Show toggle only when adding a new bill (not editing) */} {!bill && (
)}
{t("cancel-button")}
{state.message &&

{state.message}

}
); }