add option to create location in all subsequent months
- Added toggle in LocationEditForm for adding locations to future months (disabled by default) - Modified updateOrAddLocation action to support batch creation across subsequent months - Only creates locations in months where no location with same name exists - Added translations for toggle text in Croatian and English - Fixed unused variable warning in deleteLocationById - Improved auth.ts development comments for clarity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ const FormSchema = (t:IntlTemplateFn) => z.object({
|
|||||||
_id: z.string(),
|
_id: z.string(),
|
||||||
locationName: z.coerce.string().min(1, t("location-name-required")),
|
locationName: z.coerce.string().min(1, t("location-name-required")),
|
||||||
locationNotes: z.string(),
|
locationNotes: z.string(),
|
||||||
|
addToSubsequentMonths: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
// dont include the _id field in the response
|
// dont include the _id field in the response
|
||||||
.omit({ _id: true });
|
.omit({ _id: true });
|
||||||
@@ -47,6 +48,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
|
|||||||
const validatedFields = FormSchema(t).safeParse({
|
const validatedFields = FormSchema(t).safeParse({
|
||||||
locationName: formData.get('locationName'),
|
locationName: formData.get('locationName'),
|
||||||
locationNotes: formData.get('locationNotes'),
|
locationNotes: formData.get('locationNotes'),
|
||||||
|
addToSubsequentMonths: formData.get('addToSubsequentMonths') === 'on',
|
||||||
});
|
});
|
||||||
|
|
||||||
// If form validation fails, return errors early. Otherwise, continue...
|
// If form validation fails, return errors early. Otherwise, continue...
|
||||||
@@ -60,6 +62,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
|
|||||||
const {
|
const {
|
||||||
locationName,
|
locationName,
|
||||||
locationNotes,
|
locationNotes,
|
||||||
|
addToSubsequentMonths,
|
||||||
} = validatedFields.data;
|
} = validatedFields.data;
|
||||||
|
|
||||||
// update the bill in the mongodb
|
// update the bill in the mongodb
|
||||||
@@ -80,6 +83,7 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if(yearMonth) {
|
} else if(yearMonth) {
|
||||||
|
// Always add location to the specified month
|
||||||
await dbClient.collection<BillingLocation>("lokacije").insertOne({
|
await dbClient.collection<BillingLocation>("lokacije").insertOne({
|
||||||
_id: (new ObjectId()).toHexString(),
|
_id: (new ObjectId()).toHexString(),
|
||||||
userId,
|
userId,
|
||||||
@@ -89,6 +93,78 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat
|
|||||||
yearMonth: yearMonth,
|
yearMonth: yearMonth,
|
||||||
bills: [],
|
bills: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If addToSubsequentMonths is enabled, add to all subsequent months
|
||||||
|
if (addToSubsequentMonths) {
|
||||||
|
// Find all subsequent months that exist in the database
|
||||||
|
const subsequentMonths = await dbClient.collection<BillingLocation>("lokacije")
|
||||||
|
.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
userId,
|
||||||
|
$or: [
|
||||||
|
{ "yearMonth.year": { $gt: yearMonth.year } },
|
||||||
|
{
|
||||||
|
"yearMonth.year": yearMonth.year,
|
||||||
|
"yearMonth.month": { $gt: yearMonth.month }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: {
|
||||||
|
year: "$yearMonth.year",
|
||||||
|
month: "$yearMonth.month"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
_id: 0,
|
||||||
|
year: "$_id.year",
|
||||||
|
month: "$_id.month"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$sort: {
|
||||||
|
year: 1,
|
||||||
|
month: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
// For each subsequent month, check if location with same name already exists
|
||||||
|
const locationsToInsert = [];
|
||||||
|
for (const monthData of subsequentMonths) {
|
||||||
|
const existingLocation = await dbClient.collection<BillingLocation>("lokacije")
|
||||||
|
.findOne({
|
||||||
|
userId,
|
||||||
|
name: locationName,
|
||||||
|
"yearMonth.year": monthData.year,
|
||||||
|
"yearMonth.month": monthData.month
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only add if location with same name doesn't already exist in that month
|
||||||
|
if (!existingLocation) {
|
||||||
|
locationsToInsert.push({
|
||||||
|
_id: (new ObjectId()).toHexString(),
|
||||||
|
userId,
|
||||||
|
userEmail,
|
||||||
|
name: locationName,
|
||||||
|
notes: locationNotes,
|
||||||
|
yearMonth: { year: monthData.year, month: monthData.month },
|
||||||
|
bills: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert all new locations at once if any
|
||||||
|
if (locationsToInsert.length > 0) {
|
||||||
|
await dbClient.collection<BillingLocation>("lokacije").insertMany(locationsToInsert);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(yearMonth) await gotoHome(yearMonth);
|
if(yearMonth) await gotoHome(yearMonth);
|
||||||
@@ -235,7 +311,7 @@ export const deleteLocationById = withUser(async (user:AuthenticatedUser, locati
|
|||||||
const { id: userId } = user;
|
const { id: userId } = user;
|
||||||
|
|
||||||
// find a location with the given locationID
|
// find a location with the given locationID
|
||||||
const post = await dbClient.collection<BillingLocation>("lokacije").deleteOne({ _id: locationID, userId });
|
await dbClient.collection<BillingLocation>("lokacije").deleteOne({ _id: locationID, userId });
|
||||||
|
|
||||||
await gotoHome(yearMonth)
|
await gotoHome(yearMonth)
|
||||||
})
|
})
|
||||||
@@ -7,17 +7,31 @@ import { defaultLocale } from '../i18n';
|
|||||||
|
|
||||||
export const myAuth = () => {
|
export const myAuth = () => {
|
||||||
|
|
||||||
// Ovo koristim u developmentu
|
/**
|
||||||
|
|
||||||
// const session:Session = {
|
Google auth does not work in development environment
|
||||||
// user: {
|
- this is a hack to make it work in development environment
|
||||||
// id: "109754742613069927799",
|
- it returns a fake session object which is used by the Next-Auth middleware
|
||||||
// name: "Nikola Derežić",
|
|
||||||
// },
|
Instructions: when in dev environment, uncomment the following code snippet
|
||||||
// expires: "123",
|
- this will return a fake session object which is used by the Next-Auth middleware
|
||||||
// };
|
- when in production environment, comment the code snippet back
|
||||||
|
|
||||||
|
Note: this is not a secure way to handle authentication, it is only for development purposes
|
||||||
|
- in production environment, the auth should be handled by the Next-Auth middleware
|
||||||
|
|
||||||
|
Code snippet:
|
||||||
|
|
||||||
// return(Promise.resolve(session));
|
const session:Session = {
|
||||||
|
user: {
|
||||||
|
id: "109754742613069927799",
|
||||||
|
name: "Nikola Derežić",
|
||||||
|
},
|
||||||
|
expires: "123",
|
||||||
|
};
|
||||||
|
|
||||||
|
return(Promise.resolve(session));
|
||||||
|
*/
|
||||||
|
|
||||||
return(auth());
|
return(auth());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,16 @@ export const LocationEditForm:FC<LocationEditFormProps> = ({ location, yearMonth
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Show toggle only when adding a new location (not editing) */}
|
||||||
|
{!location && (
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label cursor-pointer">
|
||||||
|
<span className="label-text">{t("add-to-subsequent-months")}</span>
|
||||||
|
<input type="checkbox" name="addToSubsequentMonths" className="toggle toggle-primary" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div id="status-error" aria-live="polite" aria-atomic="true">
|
<div id="status-error" aria-live="polite" aria-atomic="true">
|
||||||
{
|
{
|
||||||
state.message &&
|
state.message &&
|
||||||
|
|||||||
@@ -98,6 +98,7 @@
|
|||||||
"save-button": "Save",
|
"save-button": "Save",
|
||||||
"cancel-button": "Cancel",
|
"cancel-button": "Cancel",
|
||||||
"delete-tooltip": "Delete realestate",
|
"delete-tooltip": "Delete realestate",
|
||||||
|
"add-to-subsequent-months": "Add to all subsequent months",
|
||||||
"validation": {
|
"validation": {
|
||||||
"location-name-required": "Relaestate name is required"
|
"location-name-required": "Relaestate name is required"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@
|
|||||||
"save-button": "Spremi",
|
"save-button": "Spremi",
|
||||||
"cancel-button": "Odbaci",
|
"cancel-button": "Odbaci",
|
||||||
"delete-tooltip": "Brisanje nekretnine",
|
"delete-tooltip": "Brisanje nekretnine",
|
||||||
|
"add-to-subsequent-months": "Dodaj u sve mjesece koji slijede",
|
||||||
"validation": {
|
"validation": {
|
||||||
"location-name-required": "Ime nekretnine je obavezno"
|
"location-name-required": "Ime nekretnine je obavezno"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user