Files
evidencija-rezija/web-app/app/ui/MonthLocationList.tsx
Nikola Derežić 37f617683e (refactor + bugfix) Improve data structure and handle empty database edge cases
Refactored months data structure from object to array for better performance
and cleaner iteration. Fixed crash when availableYears array is empty by
adding proper guards and fallback to current year.

Changes:
- MonthLocationList: Changed months prop from object to array type
- HomePage: Refactored reduce logic to build array instead of object
- HomePage: Added empty database handling in year selection logic
- HomePage: Added early returns for invalid year params in empty DB

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-09 17:36:10 +01:00

146 lines
5.6 KiB
TypeScript

"use client";
import React from "react";
import { AddLocationButton } from "./AddLocationButton";
import { AddMonthButton } from "./AddMonthButton";
import { MonthCard } from "./MonthCard";
import Pagination from "./Pagination";
import { LocationCard } from "./LocationCard";
import { PrintButton } from "./PrintButton";
import { BillingLocation, UserSettings, YearMonth } from "../lib/db-types";
import { useRouter, useSearchParams } from "next/navigation";
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useTranslations } from "next-intl";
import { MultiBillEditButton } from "../[locale]/home/multi-bill-edit/[year]/[month]/MultiBillEditButton";
const getNextYearMonth = (yearMonth:YearMonth) => {
const {year, month} = yearMonth;
return({
year: month===12 ? year+1 : year,
month: month===12 ? 1 : month+1
} as YearMonth);
}
export type MonthArray = Array<{
yearMonth: YearMonth;
locations: BillingLocation[];
payedTotal: number;
unpaidTotal: number;
}>;
export interface MonthLocationListProps {
availableYears: number[];
months: MonthArray;
userSettings?: UserSettings | null;
}
export const MonthLocationList:React.FC<MonthLocationListProps > = ({
availableYears,
months: activeYearMonths,
userSettings,
}) => {
const router = useRouter();
const search = useSearchParams();
const t = useTranslations("home-page");
const initialMonth = search.get('month')
const [expandedMonth, setExpandedMonth] = React.useState<number>(
initialMonth ? parseInt(initialMonth as string) : -1 // no month is expanded
);
// Check for success messages
React.useEffect(() => {
const params = new URLSearchParams(search.toString());
let messageShown = false;
if (search.get('userSettingsSaved') === 'true') {
toast.success(t("user-settings-saved-message"), { theme: "dark" });
params.delete('userSettingsSaved');
messageShown = true;
}
if (search.get('billSaved') === 'true') {
toast.success(t("bill-saved-message"), { theme: "dark" });
params.delete('billSaved');
messageShown = true;
}
if (search.get('billDeleted') === 'true') {
toast.success(t("bill-deleted-message"), { theme: "dark" });
params.delete('billDeleted');
messageShown = true;
}
if (search.get('locationSaved') === 'true') {
toast.success(t("location-saved-message"), { theme: "dark" });
params.delete('locationSaved');
messageShown = true;
}
if (search.get('locationDeleted') === 'true') {
toast.success(t("location-deleted-message"), { theme: "dark" });
params.delete('locationDeleted');
messageShown = true;
}
if (search.get('bill-multi-edit-saved') === 'true') {
toast.success(t("bill-multi-edit-save-success-message"), { theme: "dark" });
params.delete('bill-multi-edit-saved');
messageShown = true;
}
}, [search, router, t]);
if(availableYears.length === 0 || activeYearMonths.length === 0) {
const currentYearMonth:YearMonth = {
year: new Date().getFullYear(),
month: new Date().getMonth() + 1
};
return(
<>
<MonthCard yearMonth={currentYearMonth} key={`month-${currentYearMonth}`} unpaidTotal={0} payedTotal={0} currency={userSettings?.currency} onToggle={() => {}} expanded={true} >
<AddLocationButton yearMonth={currentYearMonth} />
</MonthCard>
</>)
};
// when the month is toggled, update the URL
// and set the the new expandedMonth
const handleMonthToggle = (yearMonth:YearMonth) => {
// if the month is already expanded, collapse it
if(expandedMonth === yearMonth.month) {
// router.push(`/home?year=${yearMonth.year}`);
setExpandedMonth(-1); // no month is expanded
} else {
// router.push(`/home?year=${yearMonth.year}&month=${yearMonth.month}`);
setExpandedMonth(yearMonth.month);
}
}
return(<>
<AddMonthButton yearMonth={getNextYearMonth(activeYearMonths[0].locations[0].yearMonth)} />
{
activeYearMonths.map(({ yearMonth, locations, unpaidTotal, payedTotal }, monthIx) =>
<MonthCard yearMonth={yearMonth} key={`month-${yearMonth.year}-${yearMonth.month}`} unpaidTotal={unpaidTotal} payedTotal={payedTotal} currency={userSettings?.currency} expanded={ yearMonth.month === expandedMonth } onToggle={handleMonthToggle} >
{
yearMonth.month === expandedMonth ?
locations.map((location, ix) => <LocationCard key={`location-${location._id}`} location={location} currency={userSettings?.currency} />)
: null
}
<div className="flex flex-col sm:flex-row sm:gap-2 justify-center">
<AddLocationButton yearMonth={yearMonth} />
<PrintButton yearMonth={yearMonth} />
<MultiBillEditButton yearMonth={yearMonth} />
</div>
</MonthCard>
)
}
<div className="mt-5 flex w-full justify-center">
<Pagination availableYears={availableYears} />
</div>
<ToastContainer />
</>)
}