diff --git a/app/lib/actions/locationActions.ts b/app/lib/actions/locationActions.ts index b19b9d5..91c2dbb 100644 --- a/app/lib/actions/locationActions.ts +++ b/app/lib/actions/locationActions.ts @@ -89,22 +89,23 @@ export const updateOrAddLocation = withUser(async (user:AuthenticatedUser, locat }); -export const fetchAllLocations = withUser(async (user:AuthenticatedUser, pageIx:number=0, pageSize:number=2000) => { +export const fetchAllLocations = withUser(async (user:AuthenticatedUser, year:number) => { const dbClient = await getDbClient(); const { id: userId } = user; - // fetch `pageSize` locations for the given page index + // fetch all locations for the given year const locations = await dbClient.collection("lokacije") - .find({ userId }) + .find({ + userId, + "yearMonth.year": year, + }) .sort({ "yearMonth.year": -1, "yearMonth.month": -1, name: 1, }) - .skip(pageIx * pageSize) - .limit(pageSize) .toArray(); return(locations); diff --git a/app/lib/actions/monthActions.ts b/app/lib/actions/monthActions.ts index b46ca95..2641fd5 100644 --- a/app/lib/actions/monthActions.ts +++ b/app/lib/actions/monthActions.ts @@ -76,7 +76,13 @@ export const fetchAvailableYears = withUser(async (user:AuthenticatedUser) => { const dbClient = await getDbClient(); // query mnogodb for all `yearMonth` values - const yearMonths = await dbClient.collection("lokacije").distinct("yearMonth.year", { userId }); + const years = await dbClient.collection("lokacije") + .distinct("yearMonth.year", { userId }) - return(yearMonths); + // sort the years in descending order + const sortedYears = years.sort((a, b) => b - a); + + console.log("sortedYears", sortedYears); + + return(sortedYears); }) diff --git a/app/page.tsx b/app/page.tsx index 734b689..72fc4b5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,12 +3,13 @@ import { MonthTitle } from './ui/MonthTitle'; import { AddMonthButton } from './ui/AddMonthButton'; import { AddLocationButton } from './ui/AddLocationButton'; 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'; import { YearMonth } from './lib/db-types'; import { formatYearMonth } from './lib/format'; +import { FC } from 'react'; +import Pagination from './ui/Pagination'; const getNextYearMonth = (yearMonth:YearMonth) => { const {year, month} = yearMonth; @@ -18,20 +19,27 @@ const getNextYearMonth = (yearMonth:YearMonth) => { } as YearMonth); } -const Page = async () => { +export interface PageProps { + searchParams?: { + year?: string; + }; +} - const locations = await fetchAllLocations(); - const availableYears = await fetchAvailableYears(); +const Page:FC = async ({ searchParams }) => { - if(isAuthErrorMessage(locations)) { - return ( -
-

{locations.message}

-
); + let availableYears: number[]; + + try { + availableYears = await fetchAvailableYears(); + } catch (error:any) { + return ( +
+

{error.message}

+
); } // if the database is in it's initial state, show the add location button for the current month - if(locations.length === 0) { + if(availableYears.length === 0) { const currentYearMonth:YearMonth = { year: new Date().getFullYear(), @@ -47,11 +55,21 @@ const Page = async () => { ); } + console.log("page.availableYears", availableYears) + + const [ latestYear ] = availableYears; + const currentYear = Number(searchParams?.year) || availableYears[0]; + const locations = await fetchAllLocations(currentYear); + let monthlyExpense = 0; return (
- + { + // if this is the latest year, show the add month button + currentYear === latestYear && + + } { locations.map((location, ix, array) => { @@ -59,9 +77,10 @@ const Page = async () => { const { year: prevYear, month: prevMonth } = array[ix-1]?.yearMonth ?? { year: undefined, month: undefined }; const { year: nextYear, month: nextMonth } = array[ix+1]?.yearMonth ?? { year: undefined, month: undefined }; + const isLatestYear = year === latestYear; const isFirstLocationInMonth = ix === 0 || year !== prevYear || month !== prevMonth; const isLastLocationInMonth = year !== nextYear || month !== nextMonth; - const isLastLocationOfFirstMonth = isLastLocationInMonth && year === array[0].yearMonth.year && month === array[0].yearMonth.month + const isLastLocationOfLatestMonth = isLastLocationInMonth && year === array[0].yearMonth.year && month === array[0].yearMonth.month if(isFirstLocationInMonth) { monthlyExpense = 0; @@ -78,8 +97,8 @@ const Page = async () => { } { - // show AddLocationButton as a last item in the firts month - isLastLocationOfFirstMonth ? + // show AddLocationButton as a last item in the first month + isLastLocationOfLatestMonth && isLatestYear ? : null } { @@ -96,8 +115,10 @@ const Page = async () => { ) }) } +
+ +
- { availableYears.map(ym =>

{ym}

) }
); } diff --git a/app/ui/Pagination.tsx b/app/ui/Pagination.tsx new file mode 100644 index 0000000..8455029 --- /dev/null +++ b/app/ui/Pagination.tsx @@ -0,0 +1,141 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname, useSearchParams } from 'next/navigation'; + +export const generatePagination = (availableYears: number[], currentYear:number) => { + + const [firstYear, secondYear, thirdYear] = availableYears; + const [lastYear, yearBeforeLast1, yearBeforeLast2] = [...availableYears].reverse(); + const currentYearIndex = availableYears.indexOf(currentYear); + + // If the current year is among the first 3 years, + // show the first 3 and ellipsis + if (currentYearIndex < 2) { + return [firstYear, secondYear, thirdYear, '...']; + } + + // If the current year is among the last 2 years, + // ellipsis and last 3 years. + if (currentYearIndex > availableYears.length - 3) { + return ['...', yearBeforeLast2, yearBeforeLast1, lastYear]; + } + + // If the current year is somewhere in the middle, + // show the first year, an ellipsis, the current year and its neighbors, + // another ellipsis, and the last year. + return [ + '...', + availableYears[currentYearIndex - 1], + currentYear, + availableYears[currentYearIndex + 1], + '...', + ]; +}; + +export default function Pagination({ availableYears } : { availableYears: number[] }) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + + console.log("Pagination.availableYears", availableYears); + + // don't show pagination if there's less than 2 years available + if(availableYears.length < 2) { + return null; + } + + const showAllPages = availableYears.length < 5; + + const createYearURL = (yearNumber: number | string | undefined) => { + if(!yearNumber) return "/"; + + const params = new URLSearchParams(searchParams); + params.set('year', yearNumber.toString()); + return `${pathname}?${params.toString()}`; + } + + const currentYear = Number(searchParams.get('year')) || 1; + + const selectedYears = showAllPages ? availableYears : generatePagination(availableYears, currentYear); + + console.log("selectedYears", selectedYears); + + const latestYear = availableYears[0]; + const earliestYear = availableYears[availableYears.length-1]; + + return ( + <> +
+ { + // If there are more than 3 years, show the left arrow. + !showAllPages && + + } + +
+ {selectedYears.map((year, index) => { + let position: 'first' | 'last' | 'single' | 'middle' | undefined; + + if (index === 0) position = 'first'; + if (index === selectedYears.length - 1) position = 'last'; + if (selectedYears.length === 1) position = 'single'; + if (year === '...') position = 'middle'; + + return ( + + ); + })} +
+ + { + // If there are more than 3 years, show the left arrow. + !showAllPages && + + } + +
+ + ); +} + +function YearNumber({ + year, + href, + isActive, + position, +}: { + year: number | string; + href: string; + position?: 'first' | 'last' | 'middle' | 'single'; + isActive: boolean; +}) { + return({year}); +} + +function YearArrow({ + href, + direction, + isDisabled, +}: { + href: string; + direction: 'left' | 'right'; + isDisabled?: boolean; +}) { + + const icon = direction === 'left' ? "⏴" : "⏵"; + return ({icon}); +}