diff --git a/app/lib/billActions.ts b/app/lib/billActions.ts index 74e640a..40bb27f 100644 --- a/app/lib/billActions.ts +++ b/app/lib/billActions.ts @@ -6,7 +6,7 @@ import { redirect } from 'next/navigation'; import clientPromise from './mongodb'; import { BillAttachment, BillingLocation } from './db-types'; import { ObjectId } from 'mongodb'; -import { auth, withUser } from '@/app/lib/auth'; +import { withUser } from '@/app/lib/auth'; import { AuthenticatedUser } from './types/next-auth'; export type State = { @@ -14,6 +14,7 @@ export type State = { billName?: string[]; billAttachment?: string[], billNotes?: string[], + payedAmount?: string[], }; message?:string | null; } @@ -22,8 +23,47 @@ const FormSchema = z.object({ _id: z.string(), billName: z.coerce.string().min(1, "Bill Name is required."), billNotes: z.string(), + payedAmount: z.string().nullable().transform((val, ctx) => { + + if(!val || val === '') { + return null; + } + + const parsed = parseFloat(val.replace(',', '.')); + + if (isNaN(parsed)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Not a number", + }); + + // This is a special symbol you can use to + // return early from the transform function. + // It has type `never` so it does not affect the + // inferred return type. + return z.NEVER; + } + + if (parsed < 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Value must be a positive number", + }); + + // This is a special symbol you can use to + // return early from the transform function. + // It has type `never` so it does not affect the + // inferred return type. + return z.NEVER; + } + + return Math.floor(parsed * 100); // value is stored in cents + + }), }); + parseFloat + const UpdateBill = FormSchema.omit({ _id: true }); /** @@ -52,7 +92,6 @@ const serializeAttachment = async (billAttachment: File | null) => { const fileContents = await billAttachment.arrayBuffer(); const fileContentsBase64 = Buffer.from(fileContents).toString('base64'); - // create an object to store the file in the database return({ fileName, @@ -75,9 +114,12 @@ export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationI const { id: userId } = user; + const x = formData.get('payedAmount'); + const validatedFields = UpdateBill.safeParse({ billName: formData.get('billName'), billNotes: formData.get('billNotes'), + payedAmount: formData.get('payedAmount'), }); // If form validation fails, return errors early. Otherwise, continue... @@ -92,6 +134,7 @@ export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationI const { billName, billNotes, + payedAmount, } = validatedFields.data; const billPaid = formData.get('billPaid') === 'on'; @@ -102,20 +145,23 @@ export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationI const billAttachment = await serializeAttachment(formData.get('billAttachment') as File); - // if there is an attachment, update the attachment field - // otherwise, do not update the attachment field - const mongoDbSet = billAttachment ? { - "bills.$[elem].name": billName, - "bills.$[elem].paid": billPaid, - "bills.$[elem].attachment": billAttachment, - "bills.$[elem].notes": billNotes, - }: { - "bills.$[elem].name": billName, - "bills.$[elem].paid": billPaid, - "bills.$[elem].notes": billNotes, - }; - if(billId) { + + // if there is an attachment, update the attachment field + // otherwise, do not update the attachment field + const mongoDbSet = billAttachment ? { + "bills.$[elem].name": billName, + "bills.$[elem].paid": billPaid, + "bills.$[elem].attachment": billAttachment, + "bills.$[elem].notes": billNotes, + "bills.$[elem].payedAmount": payedAmount, + }: { + "bills.$[elem].name": billName, + "bills.$[elem].paid": billPaid, + "bills.$[elem].notes": billNotes, + "bills.$[elem].payedAmount": payedAmount, + }; + // find a location with the given locationID const post = await db.collection("lokacije").updateOne( { @@ -144,6 +190,7 @@ export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationI paid: billPaid, attachment: billAttachment, notes: billNotes, + payedAmount } } }); diff --git a/app/lib/db-types.ts b/app/lib/db-types.ts index 02ee9b8..fe4087d 100644 --- a/app/lib/db-types.ts +++ b/app/lib/db-types.ts @@ -11,20 +11,31 @@ export interface BillAttachment { /** bill object in the form returned by MongoDB */ export interface BillingLocation { _id: string; + /** user's ID */ userId: string; + /** user's email */ userEmail?: string | null; + /** name of the location */ name: string; /** the value is encoded as yyyymm (i.e. 202301) */ yearMonth: number; + /** array of bills */ bills: Bill[]; + /** (optional) notes */ notes: string|null; }; /** Bill basic data */ export interface Bill { _id: string; + /** bill name */ name: string; + /** is the bill paid */ paid: boolean; + /** payed amount amount in cents */ + payedAmount?: number | null; + /** attached document (optional) */ attachment?: BillAttachment|null; + /** (optional) notes */ notes?: string|null; }; \ No newline at end of file diff --git a/app/ui/BillEditForm.tsx b/app/ui/BillEditForm.tsx index 39a62f4..6acb84f 100644 --- a/app/ui/BillEditForm.tsx +++ b/app/ui/BillEditForm.tsx @@ -2,7 +2,7 @@ import { DocumentIcon, TrashIcon } from "@heroicons/react/24/outline"; import { Bill } from "../lib/db-types"; -import { FC } from "react"; +import React, { FC } from "react"; import { useFormState } from "react-dom"; import { gotoHome, updateOrAddBill } from "../lib/billActions"; @@ -22,18 +22,24 @@ export interface BillEditFormProps { export const BillEditForm:FC = ({ locationID, bill }) => { - const { _id: billID, name, paid, attachment, notes } = bill ?? { _id:undefined, name:"", paid:false, notes:"" }; + const { _id: billID, name, paid, attachment, notes, payedAmount } = bill ?? { _id:undefined, name:"", paid:false, notes:"" }; const initialState = { message: null, errors: {} }; const handleAction = updateOrAddBillMiddleware.bind(null, locationID, billID); const [ state, dispatch ] = useFormState(handleAction, initialState); + const [ isPaid, setIsPaid ] = React.useState(paid); + // redirect to the main page const handleCancel = () => { console.log('handleCancel'); gotoHome(); }; + const billPaid_handleChange = (event: React.ChangeEvent) => { + setIsPaid(event.target.checked); + } + return(
@@ -77,11 +83,32 @@ export const BillEditForm:FC = ({ locationID, bill }) => {
+ { + isPaid && <> +
+ +
+
+ {state.errors?.payedAmount && + state.errors.payedAmount.map((error: string) => ( +

+ {error} +

+ ))} +
+ + } +
{state.errors?.billNotes && @@ -92,6 +119,11 @@ export const BillEditForm:FC = ({ locationID, bill }) => { ))}
+
+ + +
+
{state.message &&

@@ -99,8 +131,6 @@ export const BillEditForm:FC = ({ locationID, bill }) => {

}
- -
);