Scaffold charts & graphs

This commit is contained in:
2024-08-01 13:55:40 +08:00
parent d6212f6c77
commit fc8ccb0eea
5 changed files with 253 additions and 20 deletions

View File

@@ -0,0 +1,106 @@
import { useEffect, useState } from "react";
import instance from "../security/http";
import config from "../config";
import { Line } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
ChartOptions,
} from "chart.js";
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
export default function UserCountGraph() {
const [userInformationCollection, setUserInformationCollection] = useState<
any[]
>([]);
const [chartData, setChartData] = useState<any>(null);
const retrieveUserInformationCollection = () => {
instance.get(`${config.serverAddress}/users/all`).then((values) => {
setUserInformationCollection(values.data);
});
};
useEffect(() => {
retrieveUserInformationCollection();
}, []);
useEffect(() => {
if (userInformationCollection.length > 0) {
const lastMonth = new Date();
lastMonth.setMonth(lastMonth.getMonth() - 1);
const daysCount = new Array(31).fill(0); // 记录过去一个月每天的用户注册数量
userInformationCollection.forEach((user) => {
const createdAt = new Date(user.createdAt);
if (createdAt > lastMonth) {
const dayIndex = Math.floor(
(createdAt.getTime() - lastMonth.getTime()) / (1000 * 60 * 60 * 24)
);
daysCount[dayIndex]++;
}
});
const labels = Array.from({ length: 31 }, (_, i) => {
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - (30 - i));
return currentDate.toISOString().split("T")[0]; // 返回日期字符串 YYYY-MM-DD
});
setChartData({
labels: labels,
datasets: [
{
label: "User Onboard",
data: daysCount,
fill: false,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
},
],
});
}
}, [userInformationCollection]);
// 定义图表选项
const options: ChartOptions<"line"> = {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: Math.max(
10,
Math.ceil(Math.max(...(chartData?.datasets[0]?.data || [10])))
), // 动态设置最大值最低为10
ticks: {
stepSize: 1, // 设置步长为1
},
},
},
};
return (
<div>
{chartData ? (
<Line data={chartData} options={options} />
) : (
<p>Loading...</p>
)}
</div>
);
}

View File

@@ -0,0 +1,106 @@
import { useEffect, useState } from "react";
import instance from "../security/http";
import config from "../config";
import { Pie } from "react-chartjs-2";
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
// Register Chart.js components
ChartJS.register(ArcElement, Tooltip, Legend);
export default function UserTownCouncilDistributionChart() {
const [userInformationCollection, setUserInformationCollection] = useState<
any[]
>([]);
const [townCouncilInformation, setTownCouncilInformation] = useState<
string[]
>([]);
const [chartData, setChartData] = useState<any>(null);
const retrieveInformation = () => {
instance.get(`${config.serverAddress}/users/all`).then((values) => {
setUserInformationCollection(values.data);
});
instance
.get(`${config.serverAddress}/users/town-councils-metadata`)
.then((values) => {
setTownCouncilInformation(JSON.parse(values.data).townCouncils);
});
};
const generateChartData = () => {
const townCouncilCounts: { [key: string]: number } = {};
userInformationCollection.forEach((user: any) => {
const townCouncil = user.townCouncil || "Unknown";
if (townCouncilCounts[townCouncil]) {
townCouncilCounts[townCouncil]++;
} else {
townCouncilCounts[townCouncil] = 1;
}
});
const labels = Object.keys(townCouncilCounts);
const data = Object.values(townCouncilCounts);
const chartData = {
labels,
datasets: [
{
data,
backgroundColor: labels.map(
(label, index) => `hsl(${index * (360 / labels.length)}, 70%, 50%)`
),
},
],
};
setChartData(chartData);
};
useEffect(() => {
retrieveInformation();
}, []);
useEffect(() => {
if (
userInformationCollection.length > 0 &&
townCouncilInformation.length > 0
) {
generateChartData();
}
}, [userInformationCollection, townCouncilInformation]);
const options = {
responsive: true,
plugins: {
legend: {
position: "left" as const,
labels: {
font: {
size: 20,
},
color: "black",
},
},
tooltip: {
callbacks: {
label: function (tooltipItem: any) {
const dataset = tooltipItem.dataset;
const dataIndex = tooltipItem.dataIndex;
const value = dataset.data[dataIndex];
return `${chartData.labels[dataIndex]}: ${value}`;
},
},
},
},
};
return (
<div>
{chartData ? (
<Pie data={chartData} options={options} />
) : (
<p>Loading...</p>
)}
</div>
);
}

View File

@@ -6,6 +6,8 @@ import UserProfilePicture from "../components/UserProfilePicture";
import { Button, Card } from "@nextui-org/react";
import { PencilSquareIcon } from "../icons";
import EcoconnectFullLogo from "../components/EcoconnectFullLogo";
import UserCountGraph from "../components/UserCountGraph";
import UserTownCouncilDistributionChart from "../components/UserTownCouncilDistributionChart";
export default function AdministratorSpringboard() {
const navigate = useNavigate();
@@ -75,28 +77,18 @@ export default function AdministratorSpringboard() {
<Card className="w-full bg-primary-500 p-8 text-white rounded-r-none">
<div className="flex flex-col gap-4">
<p className="font-bold text-4xl">Statistics Overview</p>
<div className="h-[500px] w-full bg-primary-600 rounded-2xl flex flex-row p-6">
<div className="w-60 flex flex-col justify-between">
<div className="flex flex-col">
<p className="text-2xl">User Count</p>
<p className="opacity-70">(past 30 days)</p>
</div>
<p className="text-lg">
Total: <span className="font-bold">2139</span> users
</p>
</div>
<div className="w-full h-full bg-white rounded-xl">
{/* TODO: Graph */}
<p className="text-black">GRAPH HERE</p>
<div className="w-full h-full bg-primary-600 rounded-2xl flex flex-col gap-4 p-6">
<p className="text-3xl">Users Onboard</p>
<div className="w-full p-4 bg-white rounded-xl">
<UserCountGraph />
</div>
</div>
<div className="h-[500px] w-full bg-primary-600 rounded-2xl flex flex-row p-6">
<div className="w-60 flex flex-col justify-between">
<p className="text-2xl">Population Distribution</p>
</div>
<div className="w-full h-full bg-white rounded-xl">
{/* TODO: Graph */}
<p className="text-black">GRAPH HERE</p>
<div className="w-full h-full bg-primary-600 rounded-2xl flex flex-col gap-4 p-6">
<p className="text-3xl">Population Distribution</p>
<div className="w-full bg-white rounded-xl">
<div className="w-[600px] mx-auto">
<UserTownCouncilDistributionChart />
</div>
</div>
</div>
</div>