370 lines
9.3 KiB
TypeScript
370 lines
9.3 KiB
TypeScript
import React, { useState, useCallback, useEffect, useRef } from "react";
|
|
import ReactApexChart from "react-apexcharts";
|
|
import ApexCharts from "apexcharts";
|
|
import { init } from "next/dist/compiled/webpack/webpack";
|
|
|
|
const wilayah = ["sumut", "bali", "jateng", "jabar", "metro", "papua", "riau"];
|
|
|
|
type TypeItem = {
|
|
year: number;
|
|
month: number;
|
|
user: {
|
|
list: { name: string; count: number }[];
|
|
}[];
|
|
};
|
|
|
|
interface MappedData {
|
|
result: { x: string; y: number }[] | [];
|
|
details: { name: string; count: number }[][];
|
|
}
|
|
|
|
const generateDummyData = () => {
|
|
const getDaysInMonth = (year: number, month: number) => {
|
|
return new Date(year, month, 0).getDate();
|
|
};
|
|
|
|
const dummy = [];
|
|
|
|
for (let month = 1; month <= 7; month++) {
|
|
const userData = [];
|
|
const daysInMonth = getDaysInMonth(2025, month);
|
|
|
|
for (let day = 1; day <= daysInMonth; day++) {
|
|
// Buat 2-4 wilayah acak per hari
|
|
const regionCount = Math.floor(Math.random() * 3) + 2;
|
|
const usedRegions = wilayah
|
|
.sort(() => 0.5 - Math.random())
|
|
.slice(0, regionCount);
|
|
|
|
const list = usedRegions.map((name) => ({
|
|
name,
|
|
count: Math.floor(Math.random() * 10) + 1,
|
|
}));
|
|
|
|
userData.push({ list });
|
|
}
|
|
|
|
dummy.push({
|
|
year: 2025,
|
|
month,
|
|
user: userData,
|
|
});
|
|
}
|
|
|
|
return dummy;
|
|
};
|
|
|
|
const dummy = generateDummyData();
|
|
|
|
const colors = ["#008FFB"];
|
|
|
|
export const makeDataByMonth = (
|
|
data: TypeItem[],
|
|
month: number
|
|
): MappedData => {
|
|
const result: { x: string; y: number }[] = [];
|
|
const details: { name: string; count: number }[][] = [];
|
|
|
|
const filtered = data.find((entry) => entry.month === month);
|
|
|
|
if (!filtered) return { result: [], details: [] };
|
|
|
|
filtered.user.forEach((u, idx) => {
|
|
const total = u.list.reduce((sum, item) => sum + item.count, 0);
|
|
details.push(u.list);
|
|
console.log("u.list", u.list);
|
|
result.push({
|
|
x: (idx + 1).toString(),
|
|
y: total,
|
|
});
|
|
});
|
|
|
|
return { result, details };
|
|
};
|
|
|
|
export const makeDataByRange = (
|
|
data: TypeItem[],
|
|
startMonth: number,
|
|
startDay: number,
|
|
endMonth: number,
|
|
endDay: number
|
|
) => {
|
|
const user: number[] = [];
|
|
const labels: string[] = [];
|
|
const details = [];
|
|
const result: { x: string; y: number }[] = [];
|
|
const sortedData = data.sort((a, b) => a.month - b.month);
|
|
for (const monthData of sortedData) {
|
|
const { month, user: u } = monthData;
|
|
|
|
if (month < startMonth || month > endMonth) continue;
|
|
|
|
let startIndex = 0;
|
|
let endIndex = u.length - 1;
|
|
|
|
if (month === startMonth) startIndex = startDay - 1;
|
|
if (month === endMonth) endIndex = endDay - 1;
|
|
|
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
const userEntry = u[i];
|
|
|
|
if (!userEntry) continue;
|
|
|
|
const total = userEntry.list.reduce((sum, item) => sum + item.count, 0);
|
|
user.push(total);
|
|
details.push(userEntry.list);
|
|
const label = `${(i + 1).toString().padStart(2, "0")} - ${month
|
|
.toString()
|
|
.padStart(2, "0")}`;
|
|
labels.push(label);
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < user.length; i++) {
|
|
result.push({ x: labels[i], y: user[i] });
|
|
}
|
|
|
|
return { result, details };
|
|
};
|
|
|
|
const ApexChartDynamic = (props: {
|
|
type: string;
|
|
date: string;
|
|
range: { start: any; end: any };
|
|
}) => {
|
|
const { date, type, range } = props;
|
|
const [state, setState] = useState<{
|
|
series: ApexAxisChartSeries;
|
|
options: ApexCharts.ApexOptions;
|
|
seriesQuarter: ApexAxisChartSeries;
|
|
}>({
|
|
series: [{ data: [] }],
|
|
options: {
|
|
chart: {
|
|
id: "barYear",
|
|
height: 600,
|
|
width: "100%",
|
|
type: "bar",
|
|
events: {
|
|
dataPointSelection: function (e, chart, opts) {
|
|
const quarterChartEl = document.querySelector("#chart-quarter");
|
|
const yearChartEl = document.querySelector("#chart-year");
|
|
|
|
if (!quarterChartEl || !yearChartEl) return;
|
|
|
|
if (opts.selectedDataPoints[0].length === 1) {
|
|
if (quarterChartEl.classList.contains("active")) {
|
|
updateQuarterChart(chart, "barQuarter");
|
|
} else {
|
|
yearChartEl.classList.add("chart-quarter-activated");
|
|
quarterChartEl.classList.add("active");
|
|
updateQuarterChart(chart, "barQuarter");
|
|
}
|
|
} else {
|
|
updateQuarterChart(chart, "barQuarter");
|
|
}
|
|
|
|
if (opts.selectedDataPoints[0].length === 0) {
|
|
yearChartEl.classList.remove("chart-quarter-activated");
|
|
quarterChartEl.classList.remove("active");
|
|
}
|
|
},
|
|
updated: function (chart) {
|
|
updateQuarterChart(chart, "barQuarter");
|
|
},
|
|
},
|
|
},
|
|
plotOptions: {
|
|
bar: {
|
|
distributed: true,
|
|
horizontal: true,
|
|
barHeight: "100%",
|
|
dataLabels: {
|
|
position: "bottom",
|
|
},
|
|
},
|
|
},
|
|
dataLabels: {
|
|
enabled: true,
|
|
textAnchor: "start",
|
|
style: {
|
|
colors: ["#fff"],
|
|
},
|
|
formatter: function (_val, opt) {
|
|
return opt.w.globals.labels[opt.dataPointIndex];
|
|
},
|
|
offsetX: 0,
|
|
dropShadow: {
|
|
enabled: true,
|
|
},
|
|
},
|
|
colors: colors,
|
|
states: {
|
|
normal: {
|
|
filter: {
|
|
type: "desaturate",
|
|
},
|
|
},
|
|
active: {
|
|
allowMultipleDataPointsSelection: false,
|
|
filter: {
|
|
type: "darken",
|
|
value: 1,
|
|
},
|
|
},
|
|
},
|
|
legend: {
|
|
show: false,
|
|
},
|
|
tooltip: {
|
|
x: { show: false },
|
|
y: {
|
|
title: {
|
|
formatter: ((_seriesName: string, opts: any) =>
|
|
opts.w.globals.labels[opts.dataPointIndex]) as (
|
|
seriesName: string
|
|
) => string,
|
|
},
|
|
},
|
|
},
|
|
|
|
yaxis: {
|
|
labels: {
|
|
show: false,
|
|
},
|
|
},
|
|
},
|
|
seriesQuarter: [{ data: [] }],
|
|
});
|
|
|
|
const [years, setYear] = useState("");
|
|
const [datas, setDatas] = useState<any>([]);
|
|
const [details, setDetails] = useState<{ name: string; count: number }[][]>(
|
|
[]
|
|
);
|
|
const detailsRef = useRef(details);
|
|
|
|
useEffect(() => {
|
|
detailsRef.current = details;
|
|
}, [details]);
|
|
|
|
useEffect(() => {
|
|
initFetch();
|
|
}, [date, range.start, range.end, type]);
|
|
|
|
const initFetch = async () => {
|
|
const splitDate = date.split(" ");
|
|
const splitDateDaily = String(range.start.year);
|
|
const currentYear = type === "monthly" ? splitDate[1] : splitDateDaily;
|
|
|
|
let data = [];
|
|
|
|
if (currentYear === years) {
|
|
console.log("if", datas);
|
|
data = datas;
|
|
} else {
|
|
// const res = await getStatisticMonthly(
|
|
// type === "monthly" ? splitDate[1] : splitDateDaily
|
|
// );
|
|
// data = res?.data?.data;
|
|
data = dummy;
|
|
console.log("dataaa", data);
|
|
setDatas(data);
|
|
setYear(currentYear);
|
|
}
|
|
// console.log("datas", data);
|
|
if (data) {
|
|
if (type == "monthly") {
|
|
const mappedData: MappedData = makeDataByMonth(
|
|
data,
|
|
Number(splitDate[0])
|
|
);
|
|
console.log("mapped month", mappedData);
|
|
setDetails(mappedData.details);
|
|
setState((prev) => ({
|
|
...prev,
|
|
series: [{ data: mappedData.result }],
|
|
}));
|
|
}
|
|
|
|
if (type == "daily") {
|
|
const mappedData = makeDataByRange(
|
|
data,
|
|
range.start.month,
|
|
range.start.day,
|
|
range.end.month,
|
|
range.end.day
|
|
);
|
|
console.log("mmapped,", mappedData.details);
|
|
setDetails(mappedData.details);
|
|
setState((prev) => ({
|
|
...prev,
|
|
series: [{ data: mappedData.result }],
|
|
}));
|
|
}
|
|
} else {
|
|
setState((prev) => ({
|
|
...prev,
|
|
series: [{ data: [] }],
|
|
}));
|
|
}
|
|
};
|
|
|
|
const updateQuarterChart = useCallback(
|
|
(chart: any, id: string) => {
|
|
const selectedIndex = chart?.w?.config?.series[0]?.data?.findIndex(
|
|
(d: any, i: number) => {
|
|
return chart.w.globals.selectedDataPoints[0]?.includes(i);
|
|
}
|
|
);
|
|
|
|
if (selectedIndex !== -1) {
|
|
const counts = detailsRef.current[selectedIndex];
|
|
console.log("countres", counts, detailsRef);
|
|
|
|
const quarterData = [
|
|
{
|
|
name: `${selectedIndex + 1}`,
|
|
data: counts,
|
|
},
|
|
];
|
|
ApexCharts.exec(id, "updateSeries", quarterData);
|
|
|
|
setState((prev: any) => ({
|
|
...prev,
|
|
seriesQuarter: quarterData,
|
|
}));
|
|
}
|
|
},
|
|
[detailsRef]
|
|
);
|
|
|
|
return (
|
|
<div className="lg:h-[600px]">
|
|
<div id="wrap" className="flex flex-col lg:flex-row gap-2">
|
|
<div id="chart-year" className="lg:w-[80%] h-[600px]">
|
|
<ReactApexChart
|
|
options={state.options}
|
|
series={state.series}
|
|
type="bar"
|
|
height={600}
|
|
/>
|
|
</div>
|
|
|
|
<div id="chart-quarter" className="w-full lg:w-[20%]">
|
|
<div className="flex flex-col gap-1">
|
|
{state.seriesQuarter[0].data.map((list: any, index) => (
|
|
<div key={index} className="flex flex-row gap-2">
|
|
<p className="font-semibold capitalize">{list?.name} : </p>
|
|
<p>{list?.count}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ApexChartDynamic;
|