"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 { DecodeResult, findDecodePdf417 } from "../lib/pdf/barcodeDecoderWasm"; import { useLocale, useTranslations } from "next-intl"; import { InfoBox } from "./InfoBox"; import { Pdf417Barcode } from "./Pdf417Barcode"; // 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(() => { console.log("[BillEditForm] hub3a text from DB:", bill?.hub3aText); }, [bill?.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); console.log("[BillEditForm] Single barcode result found:", hub3aText); } else { console.log("[BillEditForm] Multiple barcode results found:", results); setPayedAmount(""); setBarcodeResults(results); setHub3aText(undefined); } } else { console.log("[BillEditForm] No barcode results found."); } 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}

}
); }