diff --git a/app/bill/[id]/add/page.tsx b/app/bill/[id]/add/page.tsx new file mode 100644 index 0000000..bf11355 --- /dev/null +++ b/app/bill/[id]/add/page.tsx @@ -0,0 +1,10 @@ +import { BillEditForm } from '@/app/ui/BillEditForm'; + +export default async function Page({ params:{ id:locationID } }: { params: { id:string } }) { + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/app/bills/[id]/delete/not-found.tsx b/app/bill/[id]/delete/not-found.tsx similarity index 100% rename from app/bills/[id]/delete/not-found.tsx rename to app/bill/[id]/delete/not-found.tsx diff --git a/app/bills/[id]/delete/page.tsx b/app/bill/[id]/delete/page.tsx similarity index 100% rename from app/bills/[id]/delete/page.tsx rename to app/bill/[id]/delete/page.tsx diff --git a/app/bills/[id]/edit/not-found.tsx b/app/bill/[id]/edit/not-found.tsx similarity index 100% rename from app/bills/[id]/edit/not-found.tsx rename to app/bill/[id]/edit/not-found.tsx diff --git a/app/bills/[id]/edit/page.tsx b/app/bill/[id]/edit/page.tsx similarity index 100% rename from app/bills/[id]/edit/page.tsx rename to app/bill/[id]/edit/page.tsx diff --git a/app/lib/actions.ts b/app/lib/actions.ts index 8463227..08b6792 100644 --- a/app/lib/actions.ts +++ b/app/lib/actions.ts @@ -4,7 +4,8 @@ import { z } from 'zod'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import clientPromise from './mongodb'; -import { BillAttachment, Bill } from './db-types'; +import { BillAttachment, Bill, BillingLocation } from './db-types'; +import { ObjectId } from 'mongodb'; export type State = { errors?: { @@ -61,14 +62,14 @@ const serializeAttachment = async (billAttachment: File | null) => { } /** - * Server-side action which creates a new bill + * Server-side action which adds or updates a bill * @param locationId location of the bill * @param billId ID of the bill * @param prevState previous state of the form * @param formData form data * @returns */ -export async function updateBill(locationId: string, billId:string, prevState:State, formData: FormData) { +export async function updateOrAddBill(locationId: string, billId?:string, prevState:State, formData: FormData) { const validatedFields = UpdateBill.safeParse({ billName: formData.get('billName'), @@ -110,19 +111,37 @@ export async function updateBill(locationId: string, billId:string, prevState:St "bills.$[elem].notes": billNotes, }; - - // find a location with the given locationID - const post = await db.collection("lokacije").updateOne( - { - _id: locationId // find a location with the given locationID - }, - { - $set: mongoDbSet - }, { - arrayFilters: [ - { "elem._id": { $eq: billId } } // find a bill with the given billID - ] - }); + if(billId) { + // find a location with the given locationID + const post = await db.collection("lokacije").updateOne( + { + _id: locationId // find a location with the given locationID + }, + { + $set: mongoDbSet + }, { + arrayFilters: [ + { "elem._id": { $eq: billId } } // find a bill with the given billID + ] + }); + } else { + // find a location with the given locationID + const post = await db.collection("lokacije").updateOne( + { + _id: locationId // find a location with the given locationID + }, + { + $push: { + bills: { + _id: (new ObjectId()).toHexString(), + name: billName, + paid: billPaid, + attachment: billAttachment, + notes: billNotes, + } + } + }); + } // clear the cache for the path revalidatePath('/'); @@ -147,20 +166,14 @@ export const fetchBillById = async (locationID:string, billID:string) => { } // find a bill with the given billID - const Bill = billLocation?.bills.find(({ _id }) => _id.toString() === billID); + const bill = billLocation?.bills.find(({ _id }) => _id.toString() === billID); - if(!Bill) { + if(!bill) { console.log('Bill not found'); return(null); } - const { _id, ...billBase } = Bill; - - return({ - id: _id.toString(), - ...billBase - } as Bill); - + return(bill); } export const deleteBillById = async (locationID:string, billID:string) => { diff --git a/app/lib/fetchBillById.ts b/app/lib/fetchBillById.ts deleted file mode 100644 index b17b72b..0000000 --- a/app/lib/fetchBillById.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { Bill, BillingLocation } from '@/app/lib/db-types'; -import clientPromise from '@/app/lib/mongodb'; \ No newline at end of file diff --git a/app/ui/BillEditForm.tsx b/app/ui/BillEditForm.tsx index 5e292f9..d5f7bec 100644 --- a/app/ui/BillEditForm.tsx +++ b/app/ui/BillEditForm.tsx @@ -4,31 +4,29 @@ import { DocumentIcon, TrashIcon } from "@heroicons/react/24/outline"; import { Bill } from "../lib/db-types"; import { FC } from "react"; import { useFormState } from "react-dom"; -import { gotoHome, updateBill } from "../lib/actions"; -import { redirect } from 'next/navigation'; +import { gotoHome, updateOrAddBill } from "../lib/actions"; // 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 updateBill2 = (locationId: string, billId:string, prevState:any, formData: FormData) => { - console.log('updateBill2', formData.get('billAttachment')); - +const updateOrAddBillMiddleware = (locationId: string, billId:string|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 updateBill(locationId, billId, prevState, formData); + return updateOrAddBill(locationId, billId, prevState, formData); } export interface BillEditFormProps { locationID: string, - bill: Bill + bill?: Bill } -export const BillEditForm:FC = ({ locationID, bill: { id, name, paid, attachment, notes } }) => { +export const BillEditForm:FC = ({ locationID, bill }) => { + + const { _id: billID, name, paid, attachment, notes } = bill ?? { _id:undefined, name:"", paid:false, notes:"" }; const initialState = { message: null, errors: {} }; - const updateBillWithId = updateBill2.bind(null, locationID, id); - const [ state, dispatch ] = useFormState(updateBillWithId, initialState); + const handleAction = updateOrAddBillMiddleware.bind(null, locationID, billID); + const [ state, dispatch ] = useFormState(handleAction, initialState); // redirect to the main page const handleCancel = () => { @@ -40,9 +38,13 @@ export const BillEditForm:FC = ({ locationID, bill: { id, nam
- - - + { + // don't show the delete button if we are adding a new bill + bill ? + + + : null + }
@@ -57,7 +59,7 @@ export const BillEditForm:FC = ({ locationID, bill: { id, nam // attachment ? - + {decodeURIComponent(attachment.fileName)} diff --git a/app/ui/LocationCard.tsx b/app/ui/LocationCard.tsx index 2961902..00bfa1a 100644 --- a/app/ui/LocationCard.tsx +++ b/app/ui/LocationCard.tsx @@ -3,11 +3,11 @@ import { Cog8ToothIcon, PlusCircleIcon } from "@heroicons/react/24/outline"; import { FC } from "react"; import { BillBadge } from "./BillBadge"; -import { Bill, Location } from "../lib/db-types"; +import { BillingLocation } from "../lib/db-types"; import { formatYearMonth } from "../lib/format"; export interface LocationCardProps { - location: Location + location: BillingLocation } export const LocationCard:FC = ({location: { _id, name, yearMonth, bills }}) => @@ -19,9 +19,9 @@ export const LocationCard:FC = ({location: { _id, name, yearM { bills.map(bill => ) } -
+ -
+
; \ No newline at end of file