client/src/containers/analysis-eudr-detail/deforestation-alerts/chart/index.tsx
import { UTCDate } from '@date-fns/utc';
import { format } from 'date-fns';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Dot, ResponsiveContainer } from 'recharts';
import { useParams } from 'next/navigation';
import { useCallback, useMemo, useRef, useState } from 'react';
import { EUDR_COLOR_RAMP } from '@/utils/colors';
import { useEUDRSupplier } from '@/hooks/eudr';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { eudrDetail } from '@/store/features/eudr-detail';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import { setBasemap, setPlanetCompareLayer, setPlanetLayer } from '@/store/features/eudr';
import type { DotProps } from 'recharts';
type DotPropsWithPayload = DotProps & { payload: { alertDate: number } };
const DeforestationAlertsChart = (): JSX.Element => {
const [selectedPlots, setSelectedPlots] = useState<string[]>([]);
const [selectedDate, setSelectedDate] = useState<number>(null);
const ticks = useRef<number[]>([]);
const { supplierId }: { supplierId: string } = useParams();
const dispatch = useAppDispatch();
const {
filters: { dates },
} = useAppSelector(eudrDetail);
const { data, isFetching } = useEUDRSupplier(
supplierId,
{
startAlertDate: dates.from,
endAlertDate: dates.to,
},
{
select: (data) => data?.alerts?.values,
},
);
const handleClickDot = useCallback(
(payload: DotPropsWithPayload['payload']) => {
if (payload.alertDate) {
if (selectedDate === payload.alertDate) {
setSelectedDate(null);
return dispatch(setPlanetCompareLayer({ active: false }));
}
const date = new UTCDate(payload.alertDate);
setSelectedDate(date.getTime());
dispatch(setBasemap('planet'));
dispatch(setPlanetLayer({ active: true }));
dispatch(
setPlanetCompareLayer({
active: true,
year: date.getUTCFullYear(),
month: date.getUTCMonth() + 1,
}),
);
}
},
[dispatch, selectedDate],
);
const parsedData = data
?.map((item) => {
return {
...item,
...Object.fromEntries(item.plots.map((plot) => [plot.plotName, plot.alertCount])),
alertDate: new UTCDate(item.alertDate).getTime(),
};
})
?.sort((a, b) => new UTCDate(a.alertDate).getTime() - new UTCDate(b.alertDate).getTime());
const plotConfig = useMemo(() => {
if (!parsedData?.[0]) return [];
return Array.from(
new Set(parsedData.map((item) => item.plots.map((plot) => plot.plotName)).flat()),
).map((key, index) => ({
name: key,
color: EUDR_COLOR_RAMP[index] || '#000',
}));
}, [parsedData]);
const xDomain = useMemo(() => {
if (!parsedData?.length) return [];
return [
new UTCDate(parsedData[0].alertDate).getTime(),
new UTCDate(parsedData[parsedData?.length - 1].alertDate).getTime(),
];
}, [parsedData]);
return (
<>
{!data?.length && isFetching && (
<div className="flex h-[175px] items-center justify-center text-gray-300">
Fetching data...
</div>
)}
{!data?.length && !isFetching && (
<div className="flex h-[175px] items-center justify-center text-gray-300">
No data available
</div>
)}
{data?.length > 0 && (
<>
<div className="flex flex-wrap gap-2">
{plotConfig.map(({ name, color }) => (
<Badge
key={name}
variant="secondary"
className={cn(
'flex cursor-pointer items-center space-x-1 rounded border border-gray-200 bg-white p-1 text-gray-900',
{
'bg-secondary/80': selectedPlots.includes(name),
},
)}
onClick={() => {
setSelectedPlots((prev) => {
if (prev.includes(name)) {
return prev.filter((item) => item !== name);
}
return [...prev, name];
});
}}
>
<span
className="inline-block h-[12px] w-[5px] rounded-[18px]"
style={{
background: color,
}}
/>
<span>{name}</span>
</Badge>
))}
</div>
<ResponsiveContainer width="100%" height={285}>
<LineChart
data={parsedData}
margin={{
top: 20,
bottom: 15,
right: 20,
}}
>
<CartesianGrid strokeDasharray="3 3" vertical={false} />
<XAxis
type="number"
scale="time"
dataKey="alertDate"
domain={xDomain}
minTickGap={25}
tickFormatter={(value: number, index) => {
ticks.current[index] = value;
const tickDate = new UTCDate(value);
const tickYear = tickDate.getUTCFullYear();
if (!ticks.current[index - 1]) {
return format(tickDate, 'LLL yyyy');
}
const prevTickDate = new UTCDate(ticks.current[index - 1]);
const prevTickYear = prevTickDate.getUTCFullYear();
if (prevTickYear !== tickYear) {
return format(tickDate, 'LLL yyyy');
}
return format(tickDate, 'LLL');
}}
tickLine={false}
padding={{ left: 20, right: 20 }}
axisLine={false}
className="text-xs"
tickMargin={15}
/>
<YAxis tickLine={false} axisLine={false} label="(nÂș)" className="text-xs" />
{/* <Tooltip labelFormatter={(v) => format(new UTCDate(v), 'dd/MM/yyyy')} /> */}
{plotConfig?.map(({ name, color }) => {
return (
<Line
key={name}
dataKey={name}
stroke={color}
strokeWidth={3}
strokeOpacity={
selectedPlots.length ? (selectedPlots.includes(name) ? 1 : 0.2) : 1
}
connectNulls
dot={(props: DotPropsWithPayload) => {
const { payload } = props;
return (
<Dot
{...props}
onClick={() => handleClickDot(payload)}
className="cursor-pointer stroke-[3px]"
/>
);
}}
activeDot={(props: DotPropsWithPayload) => {
const { payload } = props;
return (
<Dot
{...props}
r={5}
onClick={() => handleClickDot(payload)}
className="cursor-pointer"
/>
);
}}
/>
);
})}
</LineChart>
</ResponsiveContainer>
</>
)}
</>
);
};
export default DeforestationAlertsChart;