diff --git a/app/ui/BillEditForm.tsx b/app/ui/BillEditForm.tsx index 08343b8..7f8278e 100644 --- a/app/ui/BillEditForm.tsx +++ b/app/ui/BillEditForm.tsx @@ -2,17 +2,18 @@ import { DocumentIcon, TrashIcon } from "@heroicons/react/24/outline"; import { Bill, BilledTo, BillingLocation } from "../lib/db-types"; -import React, { FC } from "react"; +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/barcodeDecoder"; +import { decodeFromImage, DecodeResult, findDecodePdf417 } from "../lib/pdf/barcodeDecoder"; import { useLocale, useTranslations } from "next-intl"; +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) => { +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)); @@ -24,26 +25,46 @@ export interface BillEditFormProps { bill?: Bill, } -export const BillEditForm:FC = ({ location, 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, barcodeImage: initialBarcodeImage } = bill ?? { _id:undefined, name:"", paid:false, notes:"" }; + const { _id: billID, name, paid, billedTo = BilledTo.Tenant, attachment, notes, payedAmount: initialPayedAmount } = bill ?? { _id: undefined, name: "", paid: false, notes: "" }; - const { yearMonth:{year: billYear, month: billMonth}, _id: locationID } = location; + const { yearMonth: { year: billYear, month: billMonth }, _id: locationID } = 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}` : "" ); - const [ barcodeImage, setBarcodeImage ] = React.useState(initialBarcodeImage); - const [ barcodeResults, setBarcodeResults ] = React.useState | null>(null); + 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 && results.length > 0) { + const { + hub3aText: decodedHub3aText, + } = results[0]; + + setHub3aText(decodedHub3aText); + } + }).catch(error => { + console.error('Failed to migrate barcodeImage to hub3aText:', error); + }); + } + }, [bill?.barcodeImage, hub3aText]); const billedTo_handleChange = (event: React.ChangeEvent) => { @@ -62,203 +83,201 @@ export const BillEditForm:FC = ({ location, bill }) => { setIsScanningPDF(true); setPayedAmount(""); - setBarcodeImage(undefined); setBarcodeResults(null); const results = await findDecodePdf417(event); - if(results && results.length > 0) { + if (results && results.length > 0) { - if(results.length === 1) { - const { - barcodeImage, + if (results.length === 1) { + const { + hub3aText, billInfo } = results[0]; - setPayedAmount(`${billInfo.amount/100}`); - setBarcodeImage(barcodeImage); + setPayedAmount(`${billInfo.amount / 100}`); + setHub3aText(hub3aText); } else { setPayedAmount(""); - setBarcodeImage(undefined); setBarcodeResults(results); + setHub3aText(undefined); } - } setIsScanningPDF(false); } const handleBarcodeSelectClick = (result: DecodeResult) => { - setPayedAmount(`${result.billInfo.amount/100}`); - setBarcodeImage(result.barcodeImage); + 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 - } + 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")} + +
+ {state.errors?.billName && + state.errors.billName.map((error: string) => ( +

+ {error} +

+ ))}
- } - { - // if multiple results are found, show them as a list - // and notify the user to select the correct one - barcodeResults && barcodeResults.length > 0 && - <> -
- + { + // + + 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?.billAttachment && - state.errors.billAttachment.map((error: string) => ( -

- {error} -

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

+ {error} +

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

- {error} -

- ))} -
- - { - barcodeImage ? -
- -

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

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

- {error} -

- ))} -
- -
-
- {t("billed-to-label")} - - -
-
- - {/* Show toggle only when adding a new bill (not editing) */} - {!bill && ( -
- -
- )} - -
- - {t("cancel-button")} -
- -
- {state.message && -

- {state.message} -

+ + { + hub3aText ? +
+ +

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

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

+ {error} +

+ ))} +
+ +
+
+ {t("billed-to-label")} + + +
+
+ + {/* Show toggle only when adding a new bill (not editing) */} + {!bill && ( +
+ +
+ )} + +
+ + {t("cancel-button")} +
+ +
+ {state.message && +

+ {state.message} +

+ } +
+ +
+
); } \ No newline at end of file