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

359 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 provinces = [
{ engName: "ACEH", inName: "ACEH" },
{ engName: "NORTH SUMATRA", inName: "SUMATERA UTARA" },
{ engName: "WEST SUMATRA", inName: "SUMATERA BARAT" },
{ engName: "RIAU", inName: "RIAU" },
{ engName: "JAMBI", inName: "JAMBI" },
{ engName: "SOUTH SUMATRA", inName: "SUMATERA SELATAN" },
{ engName: "BENGKULU", inName: "BENGKULU" },
{ engName: "LAMPUNG", inName: "LAMPUNG" },
{ engName: "BANGKABELITUNG ISLANDS", inName: "KEPULAUAN BANGKA BELITUNG" },
{ engName: "RIAU ISLANDS", inName: "KEPULAUAN RIAU" },
{ engName: "JAKARTA", inName: "DKI JAKARTA" },
{ engName: "WEST JAVA", inName: "JAWA BARAT" },
{ engName: "CENTRAL JAVA", inName: "JAWA TENGAH" },
{ engName: "YOGYAKARTA", inName: "DI YOGYAKARTA" },
{ engName: "EAST JAVA", inName: "JAWA TIMUR" },
{ engName: "BANTEN", inName: "BANTEN" },
{ engName: "BALI", inName: "BALI" },
{ engName: "WEST NUSA TENGGARA", inName: "NUSA TENGGARA BARAT" },
{ engName: "EAST NUSA TENGGARA", inName: "NUSA TENGGARA TIMUR" },
{ engName: "WEST KALIMANTAN", inName: "KALIMANTAN BARAT" },
{ engName: "CENTRAL KALIMANTAN", inName: "KALIMANTAN TENGAH" },
{ engName: "SOUTH KALIMANTAN", inName: "KALIMANTAN SELATAN" },
{ engName: "EAST KALIMANTAN", inName: "KALIMANTAN TIMUR" },
{ engName: "NORTH KALIMANTAN", inName: "KALIMANTAN UTARA" },
{ engName: "NORTH SULAWESI", inName: "SULAWESI UTARA" },
{ engName: "CENTRAL SULAWESI", inName: "SULAWESI TENGAH" },
{ engName: "SOUTH SULAWESI", inName: "SULAWESI SELATAN" },
{ engName: "SOUTHEAST SULAWESI", inName: "SULAWESI TENGGARA" },
{ engName: "GORONTALO", inName: "GORONTALO" },
{ engName: "WEST SULAWESI", inName: "SULAWESI BARAT" },
{ engName: "MALUKU", inName: "MALUKU" },
{ engName: "NORTH MALUKU", inName: "MALUKU UTARA" },
{ engName: "PAPUA", inName: "PAPUA" },
{ engName: "WEST PAPUA", inName: "PAPUA BARAT" },
{ engName: "SOUTH PAPUA", inName: "PAPUA SELATAN" },
{ engName: "CENTRAL PAPUA", inName: "PAPUA TENGAH" },
{ engName: "HIGHLAND PAPUA", inName: "PAPUA PEGUNUNGAN" },
{ engName: "SOUTHWEST PAPUA", inName: "PAPUA BARAT DAYA" },
];
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 findRegion = (name: string): string => {
const find = provinces.find((a) => a.engName == name);
return find ? find.inName : name;
};
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: findRegion(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: "Visitor 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;