2025-12-26 13:10:38 +01:00

158 lines
4.1 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import {
Box,
Card,
CardContent,
Typography,
IconButton,
Alert,
} from "@mui/material";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
import { Range } from "react-date-range";
import { DateRangePicker } from "../DateRangePicker/DateRangePicker";
import { StatItem } from "./components/StatItem";
import { DEFAULT_DATE_RANGE } from "./constants";
import { IHealthData } from "@/app/services/health";
import { getHealthData } from "@/app/services/transactions";
import "./GeneralHealthCard.scss";
interface IGeneralHealthCardProps {
initialHealthData?: IHealthData | null;
initialStats?: Array<{
label: string;
value: string | number;
change: string;
}> | null;
initialDateRange?: Range[];
}
const DEBOUNCE_MS = 1000;
export const GeneralHealthCard = ({
initialHealthData = null,
initialStats = null,
initialDateRange,
}: IGeneralHealthCardProps) => {
const [dateRange, setDateRange] = useState<Range[]>(
initialDateRange ?? DEFAULT_DATE_RANGE
);
const [healthData, setHealthData] = useState<IHealthData | null>(
initialHealthData
);
const [error, setError] = useState<string | null>(null);
/**
* Fetch health data for a given range
*/
const fetchHealthData = async (range: Range[]) => {
const startDate = range[0]?.startDate;
const endDate = range[0]?.endDate;
if (!startDate || !endDate) return;
setError(null);
try {
const data = await getHealthData({
dateStart: startDate.toISOString(),
dateEnd: endDate.toISOString(),
});
setHealthData(data);
} catch (err) {
const message =
err instanceof Error ? err.message : "Failed to fetch health data";
setError(message);
setHealthData(null);
}
};
/**
* Date picker change handler
* (state only — side effects are debounced below)
*/
const handleDateRangeChange = (newRange: Range[]) => {
setDateRange(newRange);
};
/**
* Debounced fetch when date range changes
*/
useEffect(() => {
const currentRange = dateRange[0];
if (!currentRange?.startDate || !currentRange?.endDate) return;
const isInitialDateRange =
initialDateRange &&
currentRange.startDate.getTime() ===
initialDateRange[0]?.startDate?.getTime() &&
currentRange.endDate.getTime() ===
initialDateRange[0]?.endDate?.getTime();
// Skip fetch if we're still on SSR-provided data
if (initialHealthData && isInitialDateRange) return;
const timeout = setTimeout(() => {
fetchHealthData(dateRange);
}, DEBOUNCE_MS);
return () => clearTimeout(timeout);
}, [dateRange]);
/**
* Resolve stats source
*/
const stats = healthData?.stats ??
initialStats ?? [
{ label: "TOTAL", value: 0, change: "0%" },
{ label: "SUCCESSFUL", value: 0, change: "0%" },
{ label: "ACCEPTANCE RATE", value: "0%", change: "0%" },
{ label: "AMOUNT", value: "€0.00", change: "0%" },
{ label: "ATV", value: "€0.00", change: "0%" },
];
return (
<Card className="general-health-card">
<CardContent>
<Box className="general-health-card__header">
<Typography variant="h5" fontWeight="bold">
General Health
</Typography>
<Box className="general-health-card__right-side">
<CalendarTodayIcon fontSize="small" />
<Typography variant="body2">
<DateRangePicker
value={dateRange}
onChange={handleDateRangeChange}
/>
</Typography>
<IconButton size="small">
<MoreVertIcon fontSize="small" />
</IconButton>
</Box>
</Box>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{!error && (
<Box className="general-health-card__stat-items">
{stats.map((item, i) => (
<StatItem key={`${item.label}-${i}`} {...item} />
))}
</Box>
)}
</CardContent>
</Card>
);
};