diff --git a/app/lib/actions/locationActions.ts b/app/lib/actions/locationActions.ts index a06c6b2..dc7192b 100644 --- a/app/lib/actions/locationActions.ts +++ b/app/lib/actions/locationActions.ts @@ -33,7 +33,7 @@ const UpdateLocation = FormSchema.omit({ _id: true }); * @param formData form data * @returns */ -export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locationId?: string, yearMonth?: string, prevState:State, formData: FormData) => { +export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locationId?: string, year?: string, month?: string, prevState:State, formData: FormData) => { const validatedFields = UpdateLocation.safeParse({ locationName: formData.get('locationName'), @@ -70,14 +70,15 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat notes: locationNotes, } }); - } else if(yearMonth) { + } else if(year && month) { await dbClient.collection("lokacije").insertOne({ _id: (new ObjectId()).toHexString(), userId, userEmail, name: locationName, notes: locationNotes, - yearMonth: parseInt(yearMonth), // ToDo: get the current year and month + year: parseInt(year), // ToDo: get the current year and month + month: parseInt(month), // ToDo: get the current year and month bills: [], }); } @@ -98,7 +99,7 @@ export const fetchAllLocations = withUser(async (user:AuthenticatedUser, pageIx: // fetch `pageSize` locations for the given page index const locations = await dbClient.collection("lokacije") .find({ userId }) - .sort({ yearMonth: -1, name: 1 }) + .sort({ year: -1, month: -1, name: 1 }) .skip(pageIx * pageSize) .limit(pageSize) .toArray(); diff --git a/app/lib/actions/yearMonthActions.ts b/app/lib/actions/monthActions.ts similarity index 53% rename from app/lib/actions/yearMonthActions.ts rename to app/lib/actions/monthActions.ts index 21cc856..7689d62 100644 --- a/app/lib/actions/yearMonthActions.ts +++ b/app/lib/actions/monthActions.ts @@ -16,20 +16,23 @@ import { withUser } from '../auth'; * @param formData form data * @returns */ -export const addYearMonth = withUser(async (user:AuthenticatedUser, yearMonthString: string) => { +export const addMonth = withUser(async (user:AuthenticatedUser, yearString: string, monthString: string) => { const { id: userId } = user; // update the bill in the mongodb const dbClient = await getDbClient(); - const yearMonth = parseInt(yearMonthString); + const year = parseInt(yearString); + const month = parseInt(monthString); - const prevYearMonth = (yearMonth - 1) % 100 === 0 ? yearMonth - 89 : yearMonth - 1; + const prevYear = month === 1 ? year - 1 : year; + const prevMonth = month === 1 ? 12 : month - 1; // find all locations for the previous month const prevMonthLocations = await dbClient.collection("lokacije").find({ userId, // make sure that the locations belongs to the user - yearMonth: prevYearMonth + year: prevYear, + month: prevMonth, }); const newMonthLocationsCursor = prevMonthLocations.map((prevLocation) => { @@ -38,7 +41,8 @@ export const addYearMonth = withUser(async (user:AuthenticatedUser, yearMonthStr ...prevLocation, // assign a new ID _id: (new ObjectId()).toHexString(), - yearMonth, + year: year, + month: month, // copy bill array, but set all bills to unpaid and remove attachments and notes bills: prevLocation.bills.map((bill) => { return { @@ -64,52 +68,13 @@ export async function gotoHome() { redirect('/'); } -export const fetchBillById = withUser(async (user:AuthenticatedUser, locationID:string, billID:string) => { +export const fetchAvailableYears = withUser(async (user:AuthenticatedUser) => { const { id: userId } = user; const dbClient = await getDbClient(); - // find a location with the given locationID - const billLocation = await dbClient.collection("lokacije").findOne({ - _id: locationID, - userId // make sure that the location belongs to the user - }) + // query mnogodb for all `yearMonth` values + const yearMonths = await dbClient.collection("lokacije").distinct("year", { userId }); - if(!billLocation) { - console.log(`Location ${locationID} not found`); - return(null); - } - - // find a bill with the given billID - const bill = billLocation?.bills.find(({ _id }) => _id.toString() === billID); - - if(!bill) { - console.log('Bill not found'); - return(null); - } - - return(bill); + return(yearMonths); }) - -export const deleteBillById = withUser(async (user:AuthenticatedUser, locationID:string, billID:string) => { - const { id: userId } = user; - - const dbClient = await getDbClient(); - - // 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 - } - } - }); - - return(post.modifiedCount); -}); \ No newline at end of file diff --git a/app/lib/db-types.ts b/app/lib/db-types.ts index fe4087d..d7bdc20 100644 --- a/app/lib/db-types.ts +++ b/app/lib/db-types.ts @@ -1,4 +1,5 @@ import { ObjectId } from "mongodb"; +import { inter } from "../ui/fonts"; export interface BillAttachment { fileName: string; @@ -17,8 +18,10 @@ export interface BillingLocation { userEmail?: string | null; /** name of the location */ name: string; - /** the value is encoded as yyyymm (i.e. 202301) */ - yearMonth: number; + /** billing period year */ + year: number; + /** billing period month */ + month: number; /** array of bills */ bills: Bill[]; /** (optional) notes */ diff --git a/app/lib/format.ts b/app/lib/format.ts index 4be1d36..cd0ea6a 100644 --- a/app/lib/format.ts +++ b/app/lib/format.ts @@ -1,6 +1,4 @@ -export const formatYearMonth = (yearMonth: number): string => { - const year = Math.floor(yearMonth / 100); - const month = yearMonth % 100; +export const formatYearMonth = (year: number, month:number): string => { return `${year}-${month<10?"0":""}${month}`; } diff --git a/app/location/[id]/add/page.tsx b/app/location/[id]/add/page.tsx index 7b7be22..26036aa 100644 --- a/app/location/[id]/add/page.tsx +++ b/app/location/[id]/add/page.tsx @@ -1,6 +1,7 @@ import { LocationEditForm } from '@/app/ui/LocationEditForm'; -export default async function Page({ params:{ id:yearMonth } }: { params: { id:string } }) { +export default async function Page({ params:{ id } }: { params: { id:string } }) { - return (); + const [year, month] = id.split("-"); + return (); } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 3271209..91f5f7e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,14 +6,19 @@ import { PageFooter } from './ui/PageFooter'; import { isAuthErrorMessage } from '@/app/lib/auth'; import { fetchAllLocations } from './lib/actions/locationActions'; import { formatCurrency } from './lib/formatStrings'; +import { fetchAvailableYears } from './lib/actions/monthActions'; -const getNextYearMonth = (yearMonth:number) => { - return(yearMonth % 100 === 12 ? yearMonth + 89 : yearMonth + 1); +const getNextYearMonth = (year:number, month:number) => { + return({ + year: month<12 ? year+1 : year, + month: month<12 ? month+1 : 1 + }); } export const Page = async () => { const locations = await fetchAllLocations(); + const availableYearMonths = await fetchAvailableYears(); if(isAuthErrorMessage(locations)) { return ( @@ -24,11 +29,14 @@ export const Page = async () => { // if the database is in it's initial state, show the add location button for the current month if(locations.length === 0) { - const currentYearMonth = new Date().getFullYear() * 100 + new Date().getMonth() + 1; + + const currentYear = new Date().getFullYear(); + const currentMonth = new Date().getMonth() + 1; + return (
- - + +
); @@ -38,13 +46,13 @@ export const Page = async () => { return (
- + { locations.map((location, ix, array) => { - const isFirstLocationInMonth = ix === 0 || location.yearMonth !== array[ix-1].yearMonth; - const isLastLocationInMonth = location.yearMonth !== array[ix+1]?.yearMonth; - const isLastLocationOfFirstMonth = isLastLocationInMonth && location.yearMonth === array[0].yearMonth; + const isFirstLocationInMonth = ix === 0 || location.year !== array[ix-1].year || location.month !== array[ix-1].month; + const isLastLocationInMonth = location.year !== array[ix+1]?.year || location.month !== array[ix+1]?.month; + const isLastLocationOfFirstMonth = isLastLocationInMonth && location.year === array[0].year && location.month === array[0].month; if(isFirstLocationInMonth) { monthlyExpense = 0; @@ -57,13 +65,13 @@ export const Page = async () => { { // show month title above the first LocationCard in the month isFirstLocationInMonth ? - : null + : null } { // show AddLocationButton as a last item in the firts month isLastLocationOfFirstMonth ? - : null + : null } { isLastLocationInMonth && monthlyExpense>0 ? @@ -80,6 +88,7 @@ export const Page = async () => { }) } + { availableYearMonths.map(ym =>

{ym}

) }
); } diff --git a/app/ui/AddLocationButton.tsx b/app/ui/AddLocationButton.tsx index fbf0737..7191385 100644 --- a/app/ui/AddLocationButton.tsx +++ b/app/ui/AddLocationButton.tsx @@ -2,13 +2,15 @@ import { PlusCircleIcon } from "@heroicons/react/24/outline"; export interface AddLocationButtonProps { - /** year month at which the new billing location should be addes */ - yyyymm: number + /** year at which the new billing location should be addes */ + year: number + /** month at which the new billing location should be addes */ + month: number } -export const AddLocationButton:React.FC = ({yyyymm}) => +export const AddLocationButton:React.FC = ({year,month}) =>
- + diff --git a/app/ui/AddMonthButton.tsx b/app/ui/AddMonthButton.tsx index dc62ed8..535b9bc 100644 --- a/app/ui/AddMonthButton.tsx +++ b/app/ui/AddMonthButton.tsx @@ -2,10 +2,11 @@ import { PlusCircleIcon } from "@heroicons/react/24/outline"; import React from "react"; export interface AddMonthButtonProps { - nextYearMonth: number; + year: number; + month: number; } -export const AddMonthButton:React.FC = ({ nextYearMonth }) => - +export const AddMonthButton:React.FC = ({ year, month }) => + diff --git a/app/ui/LocationCard.tsx b/app/ui/LocationCard.tsx index addc8ca..d51eb1f 100644 --- a/app/ui/LocationCard.tsx +++ b/app/ui/LocationCard.tsx @@ -11,7 +11,7 @@ export interface LocationCardProps { location: BillingLocation } -export const LocationCard:FC = ({location: { _id, name, yearMonth, bills }}) => { +export const LocationCard:FC = ({location: { _id, name, year, month, bills }}) => { // sum all the billAmounts const monthlyExpense = bills.reduce((acc, bill) => acc + (bill.payedAmount ?? 0), 0); @@ -22,7 +22,7 @@ export const LocationCard:FC = ({location: { _id, name, yearM -

{formatYearMonth(yearMonth)} {name}

+

{formatYearMonth(year, month)} {name}

{ bills.map(bill => ) diff --git a/app/ui/LocationEditForm.tsx b/app/ui/LocationEditForm.tsx index 0cd2a79..64769fa 100644 --- a/app/ui/LocationEditForm.tsx +++ b/app/ui/LocationEditForm.tsx @@ -10,14 +10,16 @@ import { gotoHome } from "../lib/actions/billActions"; export interface LocationEditFormProps { /** location which should be edited */ location?: BillingLocation, - /** year month at a new billing location should be assigned */ - yearMonth?: string + /** year at a new billing location should be assigned */ + year?: string + /** month at a new billing location should be assigned */ + month?: string } -export const LocationEditForm:FC = ({ location, yearMonth }) => +export const LocationEditForm:FC = ({ location, year, month }) => { const initialState = { message: null, errors: {} }; - const handleAction = updateOrAddLocation.bind(null, location?._id, yearMonth); + const handleAction = updateOrAddLocation.bind(null, location?._id, year, month); const [ state, dispatch ] = useFormState(handleAction, initialState); // redirect to the main page diff --git a/app/ui/MonthTitle.tsx b/app/ui/MonthTitle.tsx index babe101..f02940f 100644 --- a/app/ui/MonthTitle.tsx +++ b/app/ui/MonthTitle.tsx @@ -2,8 +2,9 @@ import { FC } from "react"; import { formatYearMonth } from "../lib/format"; export interface MonthTitleProps { - yearMonth: number; + year: number; + month: number; } -export const MonthTitle:FC = ({yearMonth}) => -
{`${formatYearMonth(yearMonth)}`}
\ No newline at end of file +export const MonthTitle:FC = ({year, month}) => +
{`${formatYearMonth(year, month)}`}
\ No newline at end of file diff --git a/app/year-month/[id]/add/page.tsx b/app/year-month/[id]/add/page.tsx index fef23e4..ed28252 100644 --- a/app/year-month/[id]/add/page.tsx +++ b/app/year-month/[id]/add/page.tsx @@ -1,10 +1,12 @@ -import { addYearMonth } from '@/app/lib/actions/yearMonthActions'; +import { addMonth } from '@/app/lib/actions/monthActions'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; export default async function Page({ params:{ id } }: { params: { id:string } }) { - await addYearMonth(id); + const [year, month] = id.split("-"); + + await addMonth(year, month); revalidatePath('/'); redirect(`/`);