"use client"; import { DocumentIcon, TrashIcon } from "@heroicons/react/24/outline"; import { Bill, BillingLocation } from "../lib/db-types"; import React, { FC } 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 { useLocale, useTranslations } from "next-intl"; // 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, billedToTenant = true, attachment, notes, payedAmount: initialPayedAmount, barcodeImage: initialBarcodeImage } = bill ?? { _id:undefined, name:"", paid:false, notes:"" }; 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 [ isBilledToTenant, setIsBilledToTenant ] = React.useState(billedToTenant); const [ payedAmount, setPayedAmount ] = React.useState(initialPayedAmount ? `${initialPayedAmount/100}` : "" ); const [ barcodeImage, setBarcodeImage ] = React.useState(initialBarcodeImage); const [ barcodeResults, setBarcodeResults ] = React.useState | null>(null); const billedToTenant_handleChange = (event: React.ChangeEvent) => { setIsBilledToTenant(event.target.checked); } 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(""); setBarcodeImage(undefined); setBarcodeResults(null); const results = await findDecodePdf417(event); if(results && results.length > 0) { if(results.length === 1) { const { barcodeImage, billInfo } = results[0]; setPayedAmount(`${billInfo.amount/100}`); setBarcodeImage(barcodeImage); } else { setPayedAmount(""); setBarcodeImage(undefined); setBarcodeResults(results); } } setIsScanningPDF(false); } const handleBarcodeSelectClick = (result: DecodeResult) => { setPayedAmount(`${result.billInfo.amount/100}`); setBarcodeImage(result.barcodeImage); 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}

))}
{ barcodeImage ?

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

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

{error}

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

{state.message}

}
); }