implemented bill update

This commit is contained in:
2024-01-04 16:25:22 +01:00
parent 76ddedf652
commit 79dbe04245
4 changed files with 155 additions and 35 deletions

View File

@@ -1,4 +1,4 @@
import { Location } from '@/app/lib/db-types';
import { PlainLocation, PlainBill, MongoLocation } from '@/app/lib/db-types';
import clientPromise from '@/app/lib/mongodb';
import { BillEditForm } from '@/app/ui/BillEditForm';
import { ObjectId } from 'mongodb';
@@ -9,9 +9,28 @@ const fetchBillById = async (locationID:string, billID:string) => {
const db = client.db("rezije");
// find a location with the given locationID
const billLocation = await db.collection<Location>("lokacije").findOne({ _id: locationID })
const billLocation = await db.collection<MongoLocation>("lokacije").findOne({ _id: locationID })
if(!billLocation) {
console.log(`Location ${locationID} not found`);
return(null);
}
// find a bill with the given billID
return(billLocation?.bills.find(({ _id }) => _id.toString() === billID))
const mongoBill = billLocation?.bills.find(({ _id }) => _id.toString() === billID);
if(!mongoBill) {
console.log('Bill not found');
return(null);
}
const { _id, ...billBase } = mongoBill;
return({
id: _id.toString(),
...billBase
} as PlainBill);
}
export default async function Page({ params:{ id } }: { params: { id:string } }) {
@@ -21,7 +40,6 @@ export default async function Page({ params:{ id } }: { params: { id:string } })
const bill = await fetchBillById(invoiceID, billID);
if (!bill) {
console.log('Bill not found');
notFound();
}
return (

View File

@@ -1,2 +1,69 @@
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import clientPromise from './mongodb';
import { ObjectId } from 'mongodb';
export type State = {
errors?: {
billName?: string[];
};
message?:string | null;
}
const FormSchema = z.object({
_id: z.string(),
billName: z.string({
invalid_type_error: 'Please select a bill name',
}),
});
const UpdateBill = FormSchema.omit({ _id: true });
export async function updateBill(locationId: string, billId:string, prevState:State, formData: FormData) {
const validatedFields = UpdateBill.safeParse({
billName: formData.get('billName'),
});
// If form validation fails, return errors early. Otherwise, continue...
if(!validatedFields.success) {
console.log("updateBill.validation-error");
return({
errors: validatedFields.error.flatten().fieldErrors,
message: "Missing Fields. Field to Update Bill.",
});
}
const {
billName,
} = validatedFields.data;
// update the bill in the mongodb
const client = await clientPromise;
const db = client.db("rezije");
// find a location with the given locationID
const post = await db.collection<Location>("lokacije").updateOne(
{
_id: locationId // find a location with the given locationID
},
{
$set: {
"bills.$[elem].name": billName,
}
}, {
arrayFilters: [
{ "elem._id": { $eq: new ObjectId(billId) } } // find a bill with the given billID
]
});
console.log("updateBill.success", post);
// clear the cache for the path
revalidatePath('/');
// go to the bill list
redirect('/');
}

View File

@@ -1,16 +1,40 @@
import { ObjectId } from "mongodb";
export interface Location {
_id: string;
/** Bill basic data */
export interface LocationBase {
name: string;
bills: Bill[];
/** the value is encoded as yyyymm (i.e. 202301) */
yearMonth: number;
};
export interface Bill {
/** bill object in the form returned by MongoDB */
export interface MongoLocation {
_id: ObjectId;
bills: MongoBill[];
};
/** plain-object Location version */
export interface PlainLocation {
id: string;
bills: PlainBill[];
};
/** Bill basic data */
export interface BillBase {
name: string;
paid: boolean;
document?: string|null;
};
/** bill object in the form returned by MongoDB */
export interface MongoBill extends BillBase {
_id: ObjectId;
};
/** plain-object bill version */
export interface PlainBill extends BillBase {
id: string;
};

View File

@@ -1,35 +1,46 @@
import { Cog8ToothIcon, DocumentIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Bill } from "../lib/db-types";
"use client";
import { TrashIcon } from "@heroicons/react/24/outline";
import { PlainBill } from "../lib/db-types";
import { FC } from "react";
import { ObjectId } from "mongodb";
import { useFormState } from "react-dom";
import { updateBill } from "../lib/actions";
export interface BillEditFormProps {
invoiceID: string,
bill: Bill
bill: PlainBill
}
export const BillEditForm:FC<BillEditFormProps> = ({ bill: { name, paid } }) =>
export const BillEditForm:FC<BillEditFormProps> = ({ invoiceID, bill: { id, name, paid } }) => {
const initialState = { message: null, errors: {} };
const updateBillWithId = updateBill.bind(null, invoiceID, id);
const [ state, dispatch ] = useFormState(updateBillWithId, initialState);
return(
<div className="card card-compact card-bordered max-w-sm bg-base-100 shadow-s my-1">
<div className="card-body">
<form>
<TrashIcon className="h-[1em] w-[1em] absolute cursor-pointer text-error bottom-5 right-4 text-2xl" />
<input type="text" placeholder="Naziv računa" className="input input-bordered w-full" defaultValue={name} />
{
// <textarea className="textarea textarea-bordered my-1 w-full max-w-sm block" placeholder="Opis" value="Pričuva, Voda, Smeće"></textarea>
// <a href="#document.pdf" className='text-center block max-w-[24em] text-nowrap truncate inline-block'>
// <DocumentIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" />
// 2023-22-12 document GSKG račun za 2023.pdf
// </a>
}
<input type="file" className="file-input file-input-bordered w-full max-w-sm file-input-xs my-2" />
<div className="form-control w-32 p-1">
<label className="cursor-pointer label p-0">
<span className="label-text">Plaćeno</span>
<input type="checkbox" className="toggle toggle-success" checked={paid} />
</label>
<div className="card-body">
<form action={ dispatch }>
<TrashIcon className="h-[1em] w-[1em] absolute cursor-pointer text-error bottom-5 right-4 text-2xl" />
<input id="billName" name="billName" type="text" placeholder="Naziv računa" className="input input-bordered w-full" defaultValue={name} />
{
// <textarea className="textarea textarea-bordered my-1 w-full max-w-sm block" placeholder="Opis" value="Pričuva, Voda, Smeće"></textarea>
// <a href="#document.pdf" className='text-center block max-w-[24em] text-nowrap truncate inline-block'>
// <DocumentIcon className="h-[1em] w-[1em] text-2xl inline-block mr-1" />
// 2023-22-12 document GSKG račun za 2023.pdf
// </a>
}
<input type="file" className="file-input file-input-bordered w-full max-w-sm file-input-xs my-2" />
<div className="form-control w-32 p-1">
<label className="cursor-pointer label p-0">
<span className="label-text">Plaćeno</span>
<input type="checkbox" className="toggle toggle-success" defaultChecked={paid} />
</label>
</div>
<textarea className="textarea textarea-bordered my-2 w-full max-w-sm block" placeholder="Napomena"></textarea>
<button type="submit" className="btn btn-primary">Spremi</button>
</form>
</div>
<textarea className="textarea textarea-bordered my-2 w-full max-w-sm block" placeholder="Napomena"></textarea>
<button className="btn btn-primary">Spremi</button>
</form>
</div>
</div>
</div>);
}