From 27b696faab2d7e01e61cf13f40b8dd21370874aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Fri, 9 Feb 2024 09:40:43 +0100 Subject: [PATCH 1/4] home page converted back to server-side component --- app/page.tsx | 19 +++-- app/ui/HomePage.tsx | 158 ++++++++++++----------------------- app/ui/LocationCard.tsx | 4 +- app/ui/MonthCard.tsx | 2 +- app/ui/MonthLocationList.tsx | 4 +- 5 files changed, 70 insertions(+), 117 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 01601df..6505d33 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,11 +1,7 @@ import { FC, Suspense } from 'react'; import { Main } from './ui/Main'; -import dynamic from 'next/dynamic' - -const HomePage = dynamic( - () => import('./ui/HomePage'), - { ssr: false } -) +import HomePage from './ui/HomePage'; +import { MonthCardSkeleton } from './ui/MonthCardSkeleton'; export interface PageProps { searchParams?: { @@ -14,11 +10,20 @@ export interface PageProps { }; } +const HomePageSkeleton = () => +<> + + + + + const Page:FC = async ({ searchParams }) => { return (
- + }> + +
); } diff --git a/app/ui/HomePage.tsx b/app/ui/HomePage.tsx index 262da6c..2f3f59a 100644 --- a/app/ui/HomePage.tsx +++ b/app/ui/HomePage.tsx @@ -1,118 +1,30 @@ -"use client"; - +import { fetchAllLocations } from '@/app/lib/actions/locationActions'; +import { fetchAvailableYears } from '@/app/lib/actions/monthActions'; import { BillingLocation, YearMonth } from '@/app/lib/db-types'; -import { FC, useEffect, useState } from 'react'; +import { FC } from 'react'; import { MonthLocationList } from '@/app/ui/MonthLocationList'; -import { WithId } from 'mongodb'; -import { MonthCardSkeleton } from './MonthCardSkeleton'; -import { useSearchParams } from 'next/navigation'; export interface HomePageProps { + searchParams?: { + year?: string; + month?: string; + }; } -type MonthsLocations = { - [key:string]:{ - yearMonth: YearMonth, - locations: BillingLocation[], - monthlyExpense: number - } -} +export const HomePage:FC = async ({ searchParams }) => { -const fetchAllLocations = async (year: number) => { - const response = await fetch(`/api/locations/in-year/?year=${year}`); - const { locations } : { locations: WithId[] } = await response.json(); - return locations; -} + let availableYears: number[]; -const fetchAvailableYears = async () => { - const response = await fetch(`/api/locations/available-years/`); - const { availableYears }: { availableYears: number[]} = await response.json(); - return availableYears; -} + // const asyncTimout = (ms:number) => new Promise(resolve => setTimeout(resolve, ms)); + // await asyncTimout(5000); -export const HomePage:FC = () => { - - const searchParams = useSearchParams(); - const year = searchParams.get('year'); - const currentYear = year ? parseInt(year, 10) : new Date().getFullYear(); - - const [ homePageStatus, setHomePageStatus ] = useState<{ - status: "loading" | "loaded" | "error", - availableYears: number[], - months?: MonthsLocations, - error?: string - }>({ - status: "loading", - availableYears: [], - }); - - const {availableYears, months, status, error} = homePageStatus; - - useEffect(() => { - - const fetchData = async () => { - - try { - const locations = await fetchAllLocations(currentYear); - - // group locations by month - const months = locations.reduce((acc, location) => { - const {year, month} = location.yearMonth; - const key = `${year}-${month}`; - - const locationsInMonth = acc[key]; - - if(locationsInMonth) { - return({ - ...acc, - [key]: { - yearMonth: location.yearMonth, - locations: [...locationsInMonth.locations, location], - monthlyExpense: locationsInMonth.monthlyExpense + location.bills.reduce((acc, bill) => bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0) - } - }) - } - - return({ - ...acc, - [key]: { - yearMonth: location.yearMonth, - locations: [location], - monthlyExpense: location.bills.reduce((acc, bill) => bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0) - } - }); - }, {} as MonthsLocations); - - setHomePageStatus({ - availableYears: await fetchAvailableYears(), - months, - status: "loaded", - }); - - } catch (error: any) { - setHomePageStatus({ - status: "error", - availableYears: [], - error: error.message - }); - } - } - - fetchData(); - }, [currentYear]); - - if(status === "loading") { + try { + availableYears = await fetchAvailableYears(); + } catch (error:any) { return ( - <> - - - - - ); - } - - if(status === "error") { - return(

{error}

); +
+

{error.message}

+
); } // if the database is in it's initial state, show the add location button for the current month @@ -120,6 +32,42 @@ export const HomePage:FC = () => { return (); } + const currentYear = Number(searchParams?.year) || availableYears[0]; + + const locations = await fetchAllLocations(currentYear); + + // group locations by month + const months = locations.reduce((acc, location) => { + const {year, month} = location.yearMonth; + const key = `${year}-${month}`; + + const locationsInMonth = acc[key]; + + if(locationsInMonth) { + return({ + ...acc, + [key]: { + yearMonth: location.yearMonth, + locations: [...locationsInMonth.locations, location], + monthlyExpense: locationsInMonth.monthlyExpense + location.bills.reduce((acc, bill) => bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0) + } + }) + } + + return({ + ...acc, + [key]: { + yearMonth: location.yearMonth, + locations: [location], + monthlyExpense: location.bills.reduce((acc, bill) => bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0) + } + }); + }, {} as {[key:string]:{ + yearMonth: YearMonth, + locations: BillingLocation[], + monthlyExpense: number + } }); + return ( ); diff --git a/app/ui/LocationCard.tsx b/app/ui/LocationCard.tsx index 7e9c613..ae1d556 100644 --- a/app/ui/LocationCard.tsx +++ b/app/ui/LocationCard.tsx @@ -18,12 +18,12 @@ export const LocationCard:FC = ({location: { _id, name, yearM const monthlyExpense = bills.reduce((acc, bill) => bill.paid ? acc + (bill.payedAmount ?? 0) : acc, 0); return( -
+
-

{formatYearMonth(yearMonth)} {name}

+

{formatYearMonth(yearMonth)} {name}

{ bills.map(bill => ) diff --git a/app/ui/MonthCard.tsx b/app/ui/MonthCard.tsx index 3861fb2..e9fea4e 100644 --- a/app/ui/MonthCard.tsx +++ b/app/ui/MonthCard.tsx @@ -30,7 +30,7 @@ export const MonthCard:FC = ({ yearMonth, children, monthlyExpen }, [expanded]); return( -
+
{`${formatYearMonth(yearMonth)}`} diff --git a/app/ui/MonthLocationList.tsx b/app/ui/MonthLocationList.tsx index 01b3584..e3bafa2 100644 --- a/app/ui/MonthLocationList.tsx +++ b/app/ui/MonthLocationList.tsx @@ -63,10 +63,10 @@ export const MonthLocationList:React.FC = ({ const handleMonthToggle = (yearMonth:YearMonth) => { // if the month is already expanded, collapse it if(expandedMonth === yearMonth.month) { - router.push(`/?year=${yearMonth.year}`); + // router.push(`/?year=${yearMonth.year}`); setExpandedMonth(-1); // no month is expanded } else { - router.push(`/?year=${yearMonth.year}&month=${yearMonth.month}`); + // router.push(`/?year=${yearMonth.year}&month=${yearMonth.month}`); setExpandedMonth(yearMonth.month); } } From 9609f7da5486edc280667e4be3c69fb1085001f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Fri, 9 Feb 2024 09:43:55 +0100 Subject: [PATCH 2/4] Location Edit/Add/Delete form migrated back to server-side component --- app/location/[id]/add/LocationAddPage.tsx | 4 +- app/location/[id]/add/page.tsx | 7 +-- .../[id]/delete/LocationDeletePage.tsx | 56 +++--------------- app/location/[id]/delete/page.tsx | 23 ++++---- app/location/[id]/edit/LocationEditPage.tsx | 57 ++++--------------- app/location/[id]/edit/page.tsx | 14 ++--- 6 files changed, 36 insertions(+), 125 deletions(-) diff --git a/app/location/[id]/add/LocationAddPage.tsx b/app/location/[id]/add/LocationAddPage.tsx index 865f06f..9556980 100644 --- a/app/location/[id]/add/LocationAddPage.tsx +++ b/app/location/[id]/add/LocationAddPage.tsx @@ -1,8 +1,6 @@ -"use client"; - import { LocationEditForm } from '@/app/ui/LocationEditForm'; import { YearMonth } from '@/app/lib/db-types'; -export default function LocationAddPage({ yearMonth }: { yearMonth:YearMonth }) { +export default async function LocationAddPage({ yearMonth }: { yearMonth:YearMonth }) { return (); } \ No newline at end of file diff --git a/app/location/[id]/add/page.tsx b/app/location/[id]/add/page.tsx index 87d0ddf..3f032fc 100644 --- a/app/location/[id]/add/page.tsx +++ b/app/location/[id]/add/page.tsx @@ -1,11 +1,6 @@ import { parseYearMonth } from '@/app/lib/format'; +import LocationAddPage from './LocationAddPage'; import { Main } from '@/app/ui/Main'; -import dynamic from 'next/dynamic' - -const LocationAddPage = dynamic( - () => import('./LocationAddPage'), - { ssr: false } - ) export default async function Page({ params:{ id } }: { params: { id:string } }) { return ( diff --git a/app/location/[id]/delete/LocationDeletePage.tsx b/app/location/[id]/delete/LocationDeletePage.tsx index ec8663c..7377733 100644 --- a/app/location/[id]/delete/LocationDeletePage.tsx +++ b/app/location/[id]/delete/LocationDeletePage.tsx @@ -1,54 +1,14 @@ -"use client"; - import { notFound } from 'next/navigation'; -import { LocationDeleteForm, LocationDeleteFormSkeleton } from '@/app/ui/LocationDeleteForm'; -import { WithId } from 'mongodb'; -import { BillingLocation } from '@/app/lib/db-types'; -import { useEffect, useState } from 'react'; +import { fetchLocationById } from '@/app/lib/actions/locationActions'; +import { LocationDeleteForm } from '@/app/ui/LocationDeleteForm'; -const fetchLocationById = async (locationId: string) => { - const response = await fetch(`/api/locations/by-id?id=${locationId}`); - const json = await response.json(); - return json.location as WithId; -} +export const LocationDeletePage = async ({ locationId }: { locationId:string }) => { -const LocationDeletePage = ({ locationId }: { locationId:string }) => { - - const [state, stateSet] = useState<{ - status: 'loading' | 'error' | 'success'; - location?: WithId; - error?: string; - }>({ status: 'loading' }); + const location = await fetchLocationById(locationId); - useEffect(() => { - - const fetchLocation = async () => { - try { - const location = await fetchLocationById(locationId); - stateSet({ location, status: 'success' }); - } catch(error:any) { - stateSet({ status: 'error', error: error.message }); - } - }; - - fetchLocation(); - - }, [locationId]); - - switch(state.status) { - case "error": - return(
Error: {state.error}
); - case "loading": - return(); - case "success": - if (!state.location) { - return(notFound()); - } - - return(); - default: - return(
Error: Unknown status
); + if (!location) { + return(notFound()); } -} -export default LocationDeletePage; \ No newline at end of file + return (); +} \ No newline at end of file diff --git a/app/location/[id]/delete/page.tsx b/app/location/[id]/delete/page.tsx index ca0db96..f4d8d44 100644 --- a/app/location/[id]/delete/page.tsx +++ b/app/location/[id]/delete/page.tsx @@ -1,17 +1,14 @@ -import { Main } from '@/app/ui/Main'; -import dynamic from 'next/dynamic' - -const LocationDeletePage = dynamic( - () => import('./LocationDeletePage'), - { ssr: false } - ) - +import { notFound } from 'next/navigation'; +import { fetchLocationById } from '@/app/lib/actions/locationActions'; +import { LocationDeleteForm } from '@/app/ui/LocationDeleteForm'; export default async function Page({ params:{ id } }: { params: { id:string } }) { - return ( -
- -
- ); + const location = await fetchLocationById(id); + + if (!location) { + return(notFound()); + } + + return (); } \ No newline at end of file diff --git a/app/location/[id]/edit/LocationEditPage.tsx b/app/location/[id]/edit/LocationEditPage.tsx index df97fad..505f634 100644 --- a/app/location/[id]/edit/LocationEditPage.tsx +++ b/app/location/[id]/edit/LocationEditPage.tsx @@ -1,53 +1,16 @@ -"use client"; - import { notFound } from 'next/navigation'; -import { LocationEditForm, LocationEditFormSkeleton } from '@/app/ui/LocationEditForm'; -import { useEffect, useState } from 'react'; -import { WithId } from 'mongodb'; -import { BillingLocation } from '@/app/lib/db-types'; +import { LocationEditForm } from '@/app/ui/LocationEditForm'; +import { fetchLocationById } from '@/app/lib/actions/locationActions'; +export default async function LocationEditPage({ locationId }: { locationId:string }) { -const fetchLocationById = async (locationId: string) => { - const response = await fetch(`/api/locations/by-id?id=${locationId}`); - const json = await response.json(); - return json.location as WithId; -} + const location = await fetchLocationById(locationId); -export default function LocationEditPage({ locationId }: { locationId:string }) { - - const [state, stateSet] = useState<{ - status: 'loading' | 'error' | 'success'; - location?: WithId; - error?: string; - }>({ status: 'loading' }); - - useEffect(() => { - - const fetchLocation = async () => { - try { - const location = await fetchLocationById(locationId); - stateSet({ location, status: 'success' }); - } catch(error:any) { - stateSet({ status: 'error', error: error.message }); - } - }; - - fetchLocation(); - - }, [locationId]); - - switch(state.status) { - case "error": - return(
Error: {state.error}
); - case "loading": - return(); - case "success": - if (!state.location) { - return(notFound()); - } - - return(); - default: - return(
Error: Unknown status
); + if (!location) { + return(notFound()); } + + const result = ; + + return (result); } \ No newline at end of file diff --git a/app/location/[id]/edit/page.tsx b/app/location/[id]/edit/page.tsx index 0fb2160..251f944 100644 --- a/app/location/[id]/edit/page.tsx +++ b/app/location/[id]/edit/page.tsx @@ -1,17 +1,15 @@ +import { Suspense } from 'react'; +import LocationEditPage from './LocationEditPage'; import { Main } from '@/app/ui/Main'; -import dynamic from 'next/dynamic' - -const LocationEditPage = dynamic( - () => import('./LocationEditPage'), - { ssr: false } - ) - +import { LocationEditFormSkeleton } from '@/app/ui/LocationEditForm'; export default async function Page({ params:{ id } }: { params: { id:string } }) { return (
- + }> + +
); } \ No newline at end of file From 432952f57c731d6de2394be94d62efcdd8347e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Fri, 9 Feb 2024 09:44:49 +0100 Subject: [PATCH 3/4] removed legacy API routes --- app/api/locations/available-years/route.ts | 9 --------- app/api/locations/by-id/route.ts | 12 ------------ app/api/locations/in-year/route.ts | 13 ------------- 3 files changed, 34 deletions(-) delete mode 100644 app/api/locations/available-years/route.ts delete mode 100644 app/api/locations/by-id/route.ts delete mode 100644 app/api/locations/in-year/route.ts diff --git a/app/api/locations/available-years/route.ts b/app/api/locations/available-years/route.ts deleted file mode 100644 index 310dfed..0000000 --- a/app/api/locations/available-years/route.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { fetchAvailableYears } from '@/app/lib/actions/monthActions'; -import { NextResponse } from 'next/server'; - -export async function GET(request: Request) { - - const availableYears = await fetchAvailableYears(); - - return NextResponse.json({ availableYears }); -} \ No newline at end of file diff --git a/app/api/locations/by-id/route.ts b/app/api/locations/by-id/route.ts deleted file mode 100644 index 0696906..0000000 --- a/app/api/locations/by-id/route.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { fetchLocationById } from '@/app/lib/actions/locationActions'; -import { NextResponse } from 'next/server'; - -export const GET = async ( - req: Request, -) => { - const url = new URL(req.url as string); - const locationId = url.searchParams.get('id'); - const location = await fetchLocationById(locationId as string); - - return NextResponse.json({ location }); -} diff --git a/app/api/locations/in-year/route.ts b/app/api/locations/in-year/route.ts deleted file mode 100644 index 3a68065..0000000 --- a/app/api/locations/in-year/route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { fetchAllLocations } from '@/app/lib/actions/locationActions'; -import { NextResponse } from 'next/server'; - -export const GET = async ( - req: Request, -) => { - // get year from query params - const url = new URL(req.url as string); - const year = parseInt(url.searchParams.get('year') as string, 10); - const locations = await fetchAllLocations(year); - - return NextResponse.json({ locations }); -} From fb17b52b8ef7a28c7b7000a819c6c95062925a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Fri, 9 Feb 2024 09:46:35 +0100 Subject: [PATCH 4/4] updated image version in compose file --- docker-compose-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-deploy.yml b/docker-compose-deploy.yml index d7964bd..a4c6a9e 100644 --- a/docker-compose-deploy.yml +++ b/docker-compose-deploy.yml @@ -9,7 +9,7 @@ networks: services: web-app: - image: utility-bills-tracker:1.10.1 + image: utility-bills-tracker:1.11.0 networks: - traefik-network - mongo-network