From 3ce158825e7c81bc3f3b1eab7a5c69e8e2929d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Mon, 11 Aug 2025 12:11:10 +0200 Subject: [PATCH] add bulk bill deletion across subsequent months MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement ability to delete bills from all subsequent months with toggle option and warning message similar to location deletion. Includes centered warning box and efficient bulk operations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/lib/actions/billActions.ts | 87 ++++++++++++++++++++++++++++------ app/ui/BillDeleteForm.tsx | 33 +++++++++++-- messages/en.json | 7 ++- messages/hr.json | 7 ++- 4 files changed, 113 insertions(+), 21 deletions(-) diff --git a/app/lib/actions/billActions.ts b/app/lib/actions/billActions.ts index 1bb5c15..1d251f2 100644 --- a/app/lib/actions/billActions.ts +++ b/app/lib/actions/billActions.ts @@ -352,27 +352,86 @@ export const fetchBillById = async (locationID:string, billID:string, includeAtt return([billLocation, bill] as [BillingLocation, Bill]); }; -export const deleteBillById = withUser(async (user:AuthenticatedUser, locationID:string, billID:string, year:number, month:number) => { +export const deleteBillById = withUser(async (user:AuthenticatedUser, locationID:string, billID:string, year:number, month:number, _prevState:any, formData?: FormData) => { const { id: userId } = user; const dbClient = await getDbClient(); + + const deleteInSubsequentMonths = formData?.get('deleteInSubsequentMonths') === 'on'; - // find a location with the given locationID - const post = await dbClient.collection("lokacije").updateOne( - { - _id: locationID, // find a location with the given locationID - userId // make sure that the location belongs to the user - }, - { - // remove the bill with the given billID - $pull: { - bills: { - _id: billID + if (deleteInSubsequentMonths) { + // Get the current location and bill to find the bill name and location name + const location = await dbClient.collection("lokacije") + .findOne({ _id: locationID, userId }); + + if (location) { + const bill = location.bills.find(b => b._id === billID); + + if (bill) { + // Find all subsequent locations with the same name that have the same bill + const subsequentLocations = await dbClient.collection("lokacije") + .find({ + userId, + name: location.name, + $or: [ + { "yearMonth.year": { $gt: year } }, + { + "yearMonth.year": year, + "yearMonth.month": { $gt: month } + } + ], + "bills.name": bill.name + }, { projection: { bills: 0 } }) + .toArray(); + + // Delete the bill from all subsequent locations (by name) + const updateOperations = subsequentLocations.map(loc => ({ + updateOne: { + filter: { _id: loc._id, userId }, + update: { + $pull: { + bills: { name: bill.name } + } + } + } + })); + + // Also delete from current location (by ID for precision) + updateOperations.push({ + updateOne: { + filter: { _id: locationID, userId }, + update: { + $pull: { + bills: { _id: billID } + } + } + } + }); + + // Execute all delete operations + if (updateOperations.length > 0) { + await dbClient.collection("lokacije").bulkWrite(updateOperations); } } - }); + } + } else { + // Delete only from current location (original behavior) + await dbClient.collection("lokacije").updateOne( + { + _id: locationID, // find a location with the given locationID + userId // make sure that the location belongs to the user + }, + { + // remove the bill with the given billID + $pull: { + bills: { + _id: billID + } + } + }); + } await gotoHome({year, month}); - return(post.modifiedCount); + return { message: null }; }); \ No newline at end of file diff --git a/app/ui/BillDeleteForm.tsx b/app/ui/BillDeleteForm.tsx index b5d79b0..77d39ae 100644 --- a/app/ui/BillDeleteForm.tsx +++ b/app/ui/BillDeleteForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { FC, ReactNode } from "react"; +import { FC, ReactNode, useState } from "react"; import { Bill, BillingLocation } from "../lib/db-types"; import { useFormState } from "react-dom"; import { Main } from "./Main"; @@ -19,6 +19,7 @@ export const BillDeleteForm:FC = ({ bill, location }) => { const handleAction = deleteBillById.bind(null, location._id, bill._id, year, month); const [ state, dispatch ] = useFormState(handleAction, null); const t = useTranslations("bill-delete-form"); + const [deleteInSubsequentMonths, setDeleteInSubsequentMonths] = useState(false); return(
@@ -33,9 +34,35 @@ export const BillDeleteForm:FC = ({ bill, location }) => { }) }

+ +
+ +
+ + {deleteInSubsequentMonths && ( +
+
+ ⚠️ +
+

{t("warning-title")}

+
{t("warning-message")}
+
+
+
+ )} +
- - {t("cancel-button")} + + {t("cancel-button")}
diff --git a/messages/en.json b/messages/en.json index 341a7a0..9bbc3e0 100644 --- a/messages/en.json +++ b/messages/en.json @@ -62,9 +62,12 @@ } }, "bill-delete-form": { - "text": "Please confirm deletion of bill “{bill_name}” at “{location_name}”.", + "text": "Please confirm deletion of bill \"{bill_name}\" at \"{location_name}\".", "cancel-button": "Cancel", - "confirm-button": "Confirm" + "confirm-button": "Confirm", + "delete-in-subsequent-months": "Also delete in all subsequent months", + "warning-title": "Warning", + "warning-message": "This operation cannot be undone and will delete the bill in all future months!" }, "bill-edit-form": { "bill-name-placeholder": "Bill name", diff --git a/messages/hr.json b/messages/hr.json index ebcb4e2..ada51c9 100644 --- a/messages/hr.json +++ b/messages/hr.json @@ -62,9 +62,12 @@ } }, "bill-delete-form": { - "text": "Molim potvrdi brisanje računa “{bill_name}” koji pripada nekretnini “{location_name}”.", + "text": "Molim potvrdi brisanje računa \"{bill_name}\" koji pripada nekretnini \"{location_name}\".", "cancel-button": "Odustani", - "confirm-button": "Potvrdi" + "confirm-button": "Potvrdi", + "delete-in-subsequent-months": "Također obriši i u svim mjesecima koji slijede", + "warning-title": "Upozorenje", + "warning-message": "Ova operacija je nepovratna i obrisat će račun u svim mjesecima koji slijede!" }, "bill-edit-form": { "bill-name-placeholder": "Ime računa",