web-humas-fe/components/ui/maps-charts.tsx

313 lines
8.7 KiB
TypeScript

import React, { useEffect, useMemo, useRef, useState } from "react";
import * as echarts from "echarts";
import indonesiaGeo from "../../public/assets/geo/indonesia.json";
import { getStatisticForMaps } from "@/services/article";
import { convertDateFormatNoTimeV2 } from "@/utils/global";
import {
parseDate,
getLocalTimeZone,
parseZonedDateTime,
parseAbsoluteToLocal,
} from "@internationalized/date";
import {
Button,
DateRangePicker,
Popover,
PopoverContent,
PopoverTrigger,
Select,
SelectItem,
} from "@heroui/react";
import { format } from "date-fns";
import { ChevronLeftIcon, ChevronRightIcon } from "../icons";
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const IndonesiaMap = () => {
const chartRef = useRef<HTMLDivElement>(null);
const myChart = useRef<echarts.EChartsType | null>(null);
const [selectedProvince, setSelectedProvince] = useState("");
const selectedProvinceRef = useRef<string | null>(null);
const chartOptionRef = useRef<any>(null);
const today = new Date();
const [typeDate, setTypeDate] = useState("daily");
const [year, setYear] = useState(today.getFullYear());
const [selectedMonth, setSelectedMonth] = useState<Date | null>(today);
const [viewsDailyDate, setViewsDailyDate] = useState({
start: parseDate(
convertDateFormatNoTimeV2(
new Date(new Date().setDate(new Date().getDate() - 7))
)
),
end: parseDate(convertDateFormatNoTimeV2(today)),
});
const handleMonthClick = (monthIndex: number) => {
setSelectedMonth(new Date(year, monthIndex, 1));
};
const [minMax, setMinMax] = useState({ min: 0, max: 0 });
const [data, setData] = useState<{ name: string; value: number }[]>([]);
useEffect(() => {
fetchData();
}, [viewsDailyDate]);
const fetchData = async () => {
const getDate = (data: any) => {
return `${data.year}-${data.month < 10 ? `0${data.month}` : data.month}-${
data.day < 10 ? `0${data.day}` : data.day
}`;
};
const res = await getStatisticForMaps(
getDate(viewsDailyDate.start),
getDate(viewsDailyDate.end)
);
if (res?.data?.data) {
const temp = [];
let min = Infinity;
let max = -Infinity;
for (const element of res?.data?.data) {
const value = element.totalVisitor;
const now = {
name: element.regionName.toUpperCase(),
value,
};
if (value < min) min = value;
if (value > max) max = value;
temp.push(now);
}
setData(temp);
setMinMax({ min: min, max: max });
} else {
setData([]);
setMinMax({ min: 0, max: 0 });
}
};
const option = useMemo(
() => ({
backgroundColor: "#d3d3d3",
title: {
text: "Data Distribution",
left: "center",
textStyle: { color: "#000" },
},
visualMap: {
min: minMax.min,
max: minMax.max,
show: true,
seriesIndex: 0,
inRange: {
color: ["#ff0000", "#008000"],
},
},
series: [
{
type: "map",
map: "indonesia",
roam: true,
zoom: 1.2,
center: [118, -2],
label: {
show: false,
color: "#000",
},
itemStyle: {
borderColor: "#999",
},
emphasis: {
label: {
show: true,
color: "#000",
},
itemStyle: {
areaColor: "#E2E8F0",
},
},
data: data,
},
],
}),
[data, minMax]
);
useEffect(() => {
if (!chartRef.current) return;
(window as any).echarts = echarts;
const fixedGeo = {
...indonesiaGeo,
features: indonesiaGeo.features.map((f) => ({
...f,
properties: {
...f.properties,
name: f.properties.PROVINSI,
},
})),
};
echarts.registerMap("indonesia", fixedGeo as any);
const chart = echarts.init(chartRef.current);
myChart.current = chart;
chart.on("click", (params: any) => {
highlightProvince(params.name);
});
chartOptionRef.current = option;
chart.setOption(option);
return () => {
chart.dispose();
};
}, []);
useEffect(() => {
if (!myChart.current) return;
chartOptionRef.current = option;
myChart.current.setOption(option);
}, [option]);
const highlightProvince = (name: string) => {
if (!myChart.current || !chartOptionRef.current) return;
myChart.current.clear();
myChart.current.setOption(chartOptionRef.current);
myChart.current.dispatchAction({
type: "highlight",
name,
});
myChart.current.dispatchAction({
type: "showTip",
name,
});
selectedProvinceRef.current = name;
setSelectedProvince(name);
};
return (
<div className="flex flex-col lg:flex-row gap-6 w-full pr-2 md:pr-0">
<div className="w-screen md:w-[70%] h-[360px] lg:h-[600px]">
<div ref={chartRef} style={{ height: "100%", width: "100%" }} />
</div>
<div className="w-screen md:w-[30%] h-[360px] lg:h-[600px] overflow-auto flex flex-col">
<div className="flex flex-col lg:flex-row gap-2">
{/* <Select
className="w-full md:w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px]"
label=""
labelPlacement="outside"
selectedKeys={[typeDate]}
onChange={(e) =>
e.target.value !== "" && setTypeDate(e.target.value)
}
>
<SelectItem key="monthly">Bulanan</SelectItem>
<SelectItem key="daily">Harian</SelectItem>
</Select> */}
{typeDate === "monthly" ? (
<Popover placement="bottom" showArrow={true} className="w-full">
<PopoverTrigger>
<Button className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg">
{" "}
{selectedMonth
? format(selectedMonth, "MMMM yyyy")
: "Pilih Bulan"}
</Button>
</PopoverTrigger>
<PopoverContent className="p-4 w-[200px]">
<div className="flex items-center justify-between mb-2 px-1 w-full">
<button
className="text-gray-500 hover:text-black"
onClick={() => setYear((prev) => prev - 1)}
>
<ChevronLeftIcon />
</button>
<span className="font-semibold text-center">{year}</span>
<button
className="text-gray-500 hover:text-black"
onClick={() => setYear((prev) => prev + 1)}
>
<ChevronRightIcon />
</button>
</div>
<div className="grid grid-cols-3 gap-2 w-full">
{months.map((month, idx) => (
<button
key={idx}
onClick={() => handleMonthClick(idx)}
className={`py-1 rounded-md text-sm transition-colors ${
selectedMonth &&
selectedMonth.getMonth() === idx &&
selectedMonth.getFullYear() === year
? "bg-blue-500 text-white"
: "hover:bg-gray-200 text-gray-700"
}`}
>
{month}
</button>
))}
</div>
</PopoverContent>
</Popover>
) : (
<div className="w-full mb-3">
<DateRangePicker
className="h-[40px] self-end"
value={viewsDailyDate}
onChange={(e) => e !== null && setViewsDailyDate(e)}
label=""
/>
</div>
)}
</div>
{data?.length > 0 &&
data?.map((list: any) => (
<a
key={list.name}
onClick={() => highlightProvince(list.name)}
className={`w-full flex flex-row cursor-pointer gap-2 ${
selectedProvince === list.name ? "bg-slate-300" : ""
}`}
>
<p className="w-4/5">{list.name}</p>{" "}
<p
className={`w-1/5 ${
selectedProvince === list.name
? "bg-slate-300"
: "bg-[#a9d7e4]"
} text-black border-white text-center`}
>
{list.value}
</p>
</a>
))}
</div>
</div>
);
};
export default IndonesiaMap;