2025-12-27 10:57:56 +01:00

159 lines
4.2 KiB
TypeScript

"use client";
import { useState } from "react";
import {
Box,
Card,
CardContent,
Typography,
IconButton,
Alert,
CircularProgress,
} 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 { useDebouncedDateRange } from "@/app/hooks/useDebouncedDateRange";
import { dashboardService } from "@/app/services/dashboardService";
import { normalizeDateRangeForAPI } from "@/app/utils/formatDate";
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 [healthData, setHealthData] = useState<IHealthData | null>(
initialHealthData
);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
/**
* Fetch health data for a given range
*/
const fetchHealthData = async (range: Range[]) => {
if (!range || !range[0]) return;
const startDate = range[0]?.startDate;
const endDate = range[0]?.endDate;
if (!startDate || !endDate) return;
setError(null);
setIsLoading(true);
try {
// Normalize dates to ensure full day coverage
const { dateStart, dateEnd } = normalizeDateRangeForAPI(
startDate,
endDate
);
// This will update the service and notify all subscribers
const { healthData } = await dashboardService.fetchDashboardData({
dateStart,
dateEnd,
});
setHealthData(healthData);
} catch (err) {
const message =
err instanceof Error ? err.message : "Failed to fetch health data";
setError(message);
setHealthData(null);
} finally {
setIsLoading(false);
}
};
const { dateRange, handleDateRangeChange } = useDebouncedDateRange({
initialDateRange: initialDateRange ?? DEFAULT_DATE_RANGE,
debounceMs: DEBOUNCE_MS,
onDateRangeChange: fetchHealthData,
skipInitialFetch: !!initialHealthData,
});
/**
* 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>
)}
{isLoading ? (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minHeight: 100,
}}
>
<CircularProgress />
</Box>
) : (
!error && (
<Box className="general-health-card__stat-items">
{stats.map((item, i) => (
<StatItem key={`${item.label}-${i}`} {...item} />
))}
</Box>
)
)}
</CardContent>
</Card>
);
};