fix:user analytics

This commit is contained in:
Rama Priyanto 2025-06-27 21:17:17 +07:00
parent 34b6586dd2
commit 85778aaf59
6 changed files with 519 additions and 162 deletions

View File

@ -69,98 +69,98 @@ export default function Login() {
error("Username & Password Wajib Diisi !");
} else {
// login dengan otp
loading();
const response: any = await emailValidation(data);
if (response?.error) {
console.log("error", response);
if (response?.message?.messages[0]?.includes("failed to send mail")) {
error("Gagal Mengirim OTP");
return false;
}
// loading();
// const response: any = await emailValidation(data);
// if (response?.error) {
// console.log("error", response);
// if (response?.message?.messages[0]?.includes("failed to send mail")) {
// error("Gagal Mengirim OTP");
// return false;
// }
if (response?.message?.messages[0]?.includes("username")) {
error("Username / Password Tidak Sesuai");
return false;
}
error("Unknown Error");
return false;
}
close();
if (response?.data?.messages[0] === "Continue to setup email") {
setFirstLogin(true);
} else {
setNeedOtp(true);
}
// if (response?.message?.messages[0]?.includes("username")) {
// error("Username / Password Tidak Sesuai");
// return false;
// }
// error("Unknown Error");
// return false;
// }
// close();
// if (response?.data?.messages[0] === "Continue to setup email") {
// setFirstLogin(true);
// } else {
// setNeedOtp(true);
// }
// login tanpa otp
// loading();
// const response = await postSignIn(data);
// if (response?.error) {
// error("Username / Password Tidak Sesuai");
// } else {
// const profile = await getProfile(response?.data?.data?.access_token);
// const dateTime: any = new Date();
loading();
const response = await postSignIn(data);
if (response?.error) {
error("Username / Password Tidak Sesuai");
} else {
const profile = await getProfile(response?.data?.data?.access_token);
const dateTime: any = new Date();
// const newTime: any = dateTime.getTime() + 10 * 60 * 1000;
const newTime: any = dateTime.getTime() + 10 * 60 * 1000;
// Cookies.set("access_token", response?.data?.data?.access_token, {
// expires: 1,
// });
// Cookies.set("refresh_token", response?.data?.data?.refresh_token, {
// expires: 1,
// });
// Cookies.set("time_refresh", newTime, {
// expires: 1,
// });
// Cookies.set("is_first_login", "true", {
// secure: true,
// sameSite: "strict",
// });
// const resActivity = await saveActivity(
// {
// activityTypeId: 1,
// url: "https://kontenhumas.com/auth",
// userId: profile?.data?.data?.id,
// },
// accessData?.id_token
// );
// Cookies.set("profile_picture", profile?.data?.data?.profilePictureUrl, {
// expires: 1,
// });
// Cookies.set("uie", profile?.data?.data?.id, {
// expires: 1,
// });
// Cookies.set("ufne", profile?.data?.data?.fullname, {
// expires: 1,
// });
// Cookies.set("ulie", profile?.data?.data?.userLevelGroup, {
// expires: 1,
// });
// Cookies.set("username", profile?.data?.data?.username, {
// expires: 1,
// });
// Cookies.set("urie", profile?.data?.data?.userRoleId, {
// expires: 1,
// });
// Cookies.set("masterPoldaId", profile?.data?.data?.masterPoldaId, {
// expires: 1,
// });
// Cookies.set("ulne", profile?.data?.data?.userLevelId, {
// expires: 1,
// });
// // Cookies.set("urce", profile?.data?.data?.roleCode, {
// // expires: 1,
// // });
// Cookies.set("email", profile?.data?.data?.email, {
// expires: 1,
// });
// router.push("/admin/dashboard");
// Cookies.set("status", "login", {
// expires: 1,
// });
Cookies.set("access_token", response?.data?.data?.access_token, {
expires: 1,
});
Cookies.set("refresh_token", response?.data?.data?.refresh_token, {
expires: 1,
});
Cookies.set("time_refresh", newTime, {
expires: 1,
});
Cookies.set("is_first_login", "true", {
secure: true,
sameSite: "strict",
});
const resActivity = await saveActivity(
{
activityTypeId: 1,
url: "https://kontenhumas.com/auth",
userId: profile?.data?.data?.id,
},
accessData?.id_token
);
Cookies.set("profile_picture", profile?.data?.data?.profilePictureUrl, {
expires: 1,
});
Cookies.set("uie", profile?.data?.data?.id, {
expires: 1,
});
Cookies.set("ufne", profile?.data?.data?.fullname, {
expires: 1,
});
Cookies.set("ulie", profile?.data?.data?.userLevelGroup, {
expires: 1,
});
Cookies.set("username", profile?.data?.data?.username, {
expires: 1,
});
Cookies.set("urie", profile?.data?.data?.userRoleId, {
expires: 1,
});
Cookies.set("masterPoldaId", profile?.data?.data?.masterPoldaId, {
expires: 1,
});
Cookies.set("ulne", profile?.data?.data?.userLevelId, {
expires: 1,
});
// Cookies.set("urce", profile?.data?.data?.roleCode, {
// expires: 1,
// });
Cookies.set("email", profile?.data?.data?.email, {
expires: 1,
});
router.push("/admin/dashboard");
Cookies.set("status", "login", {
expires: 1,
});
// close();
// }
close();
}
}
};

View File

@ -581,11 +581,11 @@ const SidebarMobile: React.FC<SidebarProps> = ({ updateSidebarData }) => {
const roles = Cookies.get("ulie");
const rolesId = Cookies.get("urie");
useEffect(() => {
if (!token) {
onLogout();
}
}, [token]);
// useEffect(() => {
// if (!token) {
// onLogout();
// }
// }, [token]);
const onLogout = () => {
Object.keys(Cookies.get()).forEach((cookieName) => {

View File

@ -582,11 +582,11 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
const roles = Cookies.get("ulie");
const rolesId = Cookies.get("urie");
useEffect(() => {
if (!token) {
onLogout();
}
}, [token]);
// useEffect(() => {
// if (!token) {
// onLogout();
// }
// }, [token]);
const onLogout = () => {
Object.keys(Cookies.get()).forEach((cookieName) => {

View File

@ -0,0 +1,246 @@
"use client";
import { useEffect, useState } from "react";
import ReactApexChart from "react-apexcharts";
import dummy from "../../../../const/dummy.json";
function getRangeAcrossMonths(
data: any[],
startMonth: number,
startDay: number,
endMonth: number,
endDay: number
) {
const labels: string[] = [];
const users: { name: string; data: number[] }[] = [];
const sortedData = data.sort((a, b) => a.month - b.month);
console.log("sorted,data", sortedData);
for (const monthData of sortedData) {
const { month, users: u } = monthData;
if (month < startMonth || month > endMonth) continue;
console.log("uuu", month, startMonth, endMonth, u.length);
let startIndex = 0;
let endIndex = u[0].data.length - 1;
if (month === startMonth) startIndex = startDay - 1;
if (month === endMonth) endIndex = endDay - 1;
console.log("start,eend", startIndex, endIndex, month);
for (let j = 0; j < u.length; j++) {
const now = u[j].data;
// console.log("u.j", now);
const temp = [];
for (let i = startIndex; i <= endIndex; i++) {
temp.push(now[i]);
if (j == 0) {
const label = `${(i + 1).toString().padStart(2, "0")} - ${month
.toString()
.padStart(2, "0")}`;
labels.push(label);
}
}
const existing = users.find((item) => item.name === u[j].name);
if (existing) {
existing.data.push(...temp); // gabungkan data
} else {
users.push({ name: u[j].name, data: temp }); // tambahkan baru
}
}
}
console.log("users", users);
return { users, labels };
}
const ApexMultiLineChart = (props: {
type: string;
date: string;
range: { start: any; end: any };
}) => {
const { date, type, range } = props;
const [datas, setDatas] = useState<any>([]);
const [years, setYear] = useState("");
const [series, setSeries] = useState<any>([]);
const [categories, setCategories] = useState<string[]>([]);
useEffect(() => {
initFetch();
console.log("type", type);
}, [date, type, range.start, range.end]);
const initFetch = async () => {
const splitDate = date.split(" ");
const splitDateDaily = String(range.start.year);
console.log("split", splitDate);
console.log("daily", splitDateDaily);
let data: any = [];
if (
(type === "monthly" && splitDate[1] === years) ||
(type === "daily" && splitDateDaily === years)
) {
data = datas;
} else {
// const res = await getStatisticMonthly(
// type === "monthly" ? splitDate[1] : splitDateDaily
// );
// data = res?.data?.data;
data = dummy.data;
setDatas(data);
}
if (type === "daily") {
console.log(
"aaa",
range.start.month,
range.start.day,
range.end.month,
range.end.day
);
const mappedData = getRangeAcrossMonths(
data,
range.start.month,
range.start.day,
range.end.month,
range.end.day
);
setSeries(mappedData.users);
setCategories(mappedData.labels);
}
if (type === "monthly") {
console.log("daaa", data);
const getDatas = data?.find(
(a: any) =>
a.month == Number(splitDate[0]) && a.year === Number(splitDate[1])
);
if (getDatas) {
console.log("datanya", getDatas.users);
setSeries(getDatas.users);
} else {
setSeries([]);
}
}
setYear(type === "monthly" ? splitDate[1] : splitDateDaily);
};
return (
<div>
<div id="chart">
<ReactApexChart
series={series}
options={{
chart: {
height: 600,
type: "line",
dropShadow: {
enabled: true,
color: "#000",
top: 18,
left: 7,
blur: 10,
opacity: 0.5,
},
zoom: {
enabled: true,
},
toolbar: {
show: false,
},
},
colors: [
"#1f77b4",
"#2ca02c",
"#9467bd",
"#8c564b",
"#e377c2",
"#7f7f7f",
"#bcbd22",
"#17becf",
"#aec7e8",
"#98df8a",
"#c5b0d5",
"#c49c94",
"#9edae5",
"#393b79",
"#637939",
"#8c6d31",
"#843c39",
"#7b4173",
"#3182bd",
"#6baed6",
"#9ecae1",
"#31a354",
"#74c476",
"#a1d99b",
"#756bb1",
"#9e9ac8",
"#bcbddc",
"#636363",
"#969696",
"#bdbdbd",
"#17becf",
"#8da0cb",
"#66c2a5",
"#a6d854",
"#ffd92f",
"#b3b3b3",
"#80b1d3",
"#fdb462",
],
dataLabels: {
enabled: true,
},
stroke: {
curve: "smooth",
},
title: {
text: "Users",
align: "left",
},
grid: {
borderColor: "#e7e7e7",
row: {
colors: ["#f3f3f3", "transparent"],
opacity: 0.5,
},
},
markers: {
size: 0.5,
},
xaxis: {
categories: type == "daily" ? categories : [],
title: {
text: "Days",
},
},
yaxis: {
title: {
text: "Articles",
},
min: 0,
max: 50,
},
legend: {
position: "right",
horizontalAlign: "right",
floating: false,
offsetY: -25,
offsetX: -5,
},
}}
type="line"
height={600}
/>
</div>
<div id="html-dist"></div>
</div>
);
};
export default ApexMultiLineChart;

View File

@ -70,6 +70,7 @@ import { format } from "date-fns";
import ApexChartColumnVisitors from "./chart/visitor-chart";
import IndonesiaMap from "@/components/ui/maps-charts";
import ApexChartDynamic from "./chart/dynamic-bar-char";
import ApexMultiLineChart from "./chart/multiline-chart";
type ArticleData = Article & {
no: number;
@ -240,12 +241,29 @@ export default function DashboardContainer() {
end: parseDate(convertDateFormatNoTimeV2(today)),
});
const [typeDateUsers, setTypeDateUsers] = useState("daily");
const [usersYear, setUsersYear] = useState(today.getFullYear());
const [usersSelectedMonth, setUsersSelectedMonth] = useState<Date | null>(
today
);
const [usersDailyDate, setUsersDailyDate] = useState({
start: parseDate(
convertDateFormatNoTimeV2(
new Date(new Date().setDate(new Date().getDate() - 30))
)
),
end: parseDate(convertDateFormatNoTimeV2(today)),
});
const handleMonthClick = (monthIndex: number) => {
setSelectedMonth(new Date(year, monthIndex, 1));
};
const handleMonthClickVisitor = (monthIndex: number) => {
setVisitorSelectedMonth(new Date(year, monthIndex, 1));
setVisitorSelectedMonth(new Date(visitorYear, monthIndex, 1));
};
const handleMonthClickUsers = (monthIndex: number) => {
setUsersSelectedMonth(new Date(usersYear, monthIndex, 1));
};
useEffect(() => {
@ -760,8 +778,8 @@ export default function DashboardContainer() {
)}
</div>
</div>
<div className="w-full flex flex-col lg:flex-row gap-6 justify-center lg:h-[700px]">
<div className="border-1 shadow-sm w-screen rounded-lg lg:w-[50%] p-6 flex flex-col">
<div className="w-full flex flex-col gap-6 justify-center">
<div className="border-1 shadow-sm w-full rounded-lg p-6 flex flex-col">
<div className="flex justify-between mb-3">
<div className="font-semibold flex flex-col">
Engagement Analytics
@ -899,7 +917,7 @@ export default function DashboardContainer() {
</div>
</div>
</div>
<div className="border-1 shadow-sm w-screen rounded-lg md:w-[50%] p-6 flex flex-col h-[700px]">
<div className="border-1 shadow-sm w-full rounded-lg p-6 flex flex-col h-[700px]">
<div className="flex justify-between mb-3">
<div className="font-semibold flex flex-col">Users Analytics</div>
<div className="flex flex-col lg:flex-row gap-2">
@ -907,16 +925,16 @@ export default function DashboardContainer() {
className="w-full md:w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px]"
label=""
labelPlacement="outside"
selectedKeys={[typeDateVisitor]}
selectedKeys={[typeDateUsers]}
onChange={(e) =>
e.target.value !== "" && setTypeDateVisitor(e.target.value)
e.target.value !== "" && setTypeDateUsers(e.target.value)
}
>
<SelectItem key="monthly">Bulanan</SelectItem>
<SelectItem key="daily">Harian</SelectItem>
</Select>
{typeDateVisitor === "monthly" ? (
{typeDateUsers === "monthly" ? (
<Popover
placement="bottom"
showArrow={true}
@ -925,8 +943,8 @@ export default function DashboardContainer() {
<PopoverTrigger>
<Button className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg">
{" "}
{visitorSelectedMonth
? format(visitorSelectedMonth, "MMMM yyyy")
{usersSelectedMonth
? format(usersSelectedMonth, "MMMM yyyy")
: "Pilih Bulan"}
</Button>
</PopoverTrigger>
@ -934,7 +952,7 @@ export default function DashboardContainer() {
<div className="flex items-center justify-between mb-2 px-1 w-full">
<button
className="text-gray-500 hover:text-black"
onClick={() => setVisitorYear((prev) => prev - 1)}
onClick={() => setUsersYear((prev) => prev - 1)}
>
<ChevronLeftIcon />
</button>
@ -943,7 +961,7 @@ export default function DashboardContainer() {
</span>
<button
className="text-gray-500 hover:text-black"
onClick={() => setVisitorYear((prev) => prev + 1)}
onClick={() => setUsersYear((prev) => prev + 1)}
>
<ChevronRightIcon />
</button>
@ -953,11 +971,11 @@ export default function DashboardContainer() {
{months.map((month, idx) => (
<button
key={idx}
onClick={() => handleMonthClickVisitor(idx)}
onClick={() => handleMonthClickUsers(idx)}
className={`py-1 rounded-md text-sm transition-colors ${
visitorSelectedMonth &&
visitorSelectedMonth.getMonth() === idx &&
visitorSelectedMonth.getFullYear() === year
usersSelectedMonth &&
usersSelectedMonth.getMonth() === idx &&
usersSelectedMonth.getFullYear() === year
? "bg-blue-500 text-white"
: "hover:bg-gray-200 text-gray-700"
}`}
@ -972,8 +990,8 @@ export default function DashboardContainer() {
<div className="w-[220px]">
<DateRangePicker
className="h-[40px]"
value={visitorDailyDate}
onChange={(e) => e !== null && setVisitorDailyDate(e)}
value={usersDailyDate}
onChange={(e) => e !== null && setUsersDailyDate(e)}
label=""
/>
</div>
@ -982,27 +1000,18 @@ export default function DashboardContainer() {
</div>
<div className="flex flex-row w-full h-full">
<div className="w-full h-[30vh] lg:h-full text-black">
<ApexChartDynamic
key={`${
convertDateFormatNoTimeV2(
String(visitorSelectedMonth)
).split("-")[1]
} ${
convertDateFormatNoTimeV2(
String(visitorSelectedMonth)
).split("-")[0]
}-${typeDateVisitor}-${visitorDailyDate}`}
type={typeDateVisitor}
<ApexMultiLineChart
type={typeDateUsers}
date={`${
convertDateFormatNoTimeV2(
String(visitorSelectedMonth)
).split("-")[1]
convertDateFormatNoTimeV2(String(usersSelectedMonth)).split(
"-"
)[1]
} ${
convertDateFormatNoTimeV2(
String(visitorSelectedMonth)
).split("-")[0]
convertDateFormatNoTimeV2(String(usersSelectedMonth)).split(
"-"
)[0]
}`}
range={visitorDailyDate}
range={usersDailyDate}
/>
</div>
</div>

View File

@ -1,46 +1,148 @@
{
"data": [
{
"id": 1,
"year": 2024,
"month": 11,
"suggestions": [
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22
]
},
{
"id": 2,
"year": 2024,
"month": 12,
"suggestions": [
15, 23, 19, 14, 18, 20, 22, 17, 21, 19, 23, 16, 25, 20, 18, 19, 22, 24,
15, 18, 21, 26, 28, 23, 17, 20, 19, 22, 22, 42, 32
]
},
{
"id": 3,
"year": 2025,
"month": 1,
"suggestions": [
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 21
"month": 4,
"users": [
{
"name": "Polda Aceh",
"data": [
12, 7, 5, 19, 3, 8, 15, 4, 9, 6, 14, 11, 13, 10, 2, 18, 7, 5, 8, 17,
4, 6, 14, 15, 3, 10, 12, 9, 6, 13
]
},
{
"name": "Polda Sumatera Utara",
"data": [
10, 4, 6, 7, 9, 8, 14, 13, 5, 12, 11, 3, 17, 16, 7, 9, 10, 8, 5, 14,
11, 4, 6, 9, 15, 13, 3, 7, 10, 12
]
},
{
"name": "Polda Sumatera Barat",
"data": [
7, 6, 3, 8, 12, 4, 6, 10, 11, 14, 13, 5, 9, 10, 6, 7, 8, 13, 15, 6,
7, 5, 8, 14, 11, 13, 4, 6, 12, 10
]
},
{
"name": "Polda Metro Jaya",
"data": [
12, 7, 5, 19, 3, 8, 15, 4, 9, 6, 14, 11, 13, 10, 2, 18, 7, 5, 8, 17,
4, 6, 9, 6, 13, 5, 14, 15, 3, 10
]
},
{
"name": "Polda Jabar",
"data": [
10, 4, 6, 17, 16, 7, 9, 10, 8, 5, 14, 11, 4, 6, 9, 15, 13, 3, 7, 10,
12, 9, 8, 14, 13, 21, 5, 12, 11
]
},
{
"name": "Polda Jateng",
"data": [
7, 6, 3, 13, 5, 9, 10, 6, 7, 8, 13, 15, 6, 7, 5, 8, 14, 11, 13, 4,
4, 6, 10, 11, 14, 2, 6, 12, 10, 8
]
}
]
},
{
"id": 4,
"year": 2025,
"month": 2,
"suggestions": [
15, 23, 19, 14, 18, 20, 22, 17, 21, 19, 23, 16, 25, 20, 18, 19, 22, 24,
15, 18, 21, 26, 28, 23, 17, 20, 19, 22
"month": 5,
"users": [
{
"name": "Polda Aceh",
"data": [
12, 7, 5, 19, 3, 8, 15, 4, 9, 6, 14, 11, 13, 10, 2, 18, 7, 5, 8, 17,
4, 6, 14, 15, 3, 10, 12, 9, 6, 13, 5
]
},
{
"name": "Polda Sumatera Utara",
"data": [
10, 4, 6, 7, 5, 12, 11, 3, 17, 16, 7, 9, 10, 8, 5, 14, 11, 4, 6, 9,
15, 13, 3, 7, 10, 12, 9, 8, 14, 13, 4
]
},
{
"name": "Polda Sumatera Barat",
"data": [
7, 6, 3, 13, 5, 9, 10, 6, 7, 8, 13, 15, 6, 7, 5, 8, 14, 11, 13, 4,
6, 12, 10, 8, 12, 4, 6, 10, 11, 14, 2
]
},
{
"name": "Polda Metro Jaya",
"data": [
12, 7, 5, 19, 3, 8, 15, 4, 9, 6, 14, 11, 13, 10, 2, 18, 7, 5, 8, 17,
4, 6, 9, 6, 13, 5, 14, 15, 3, 10, 12
]
},
{
"name": "Polda Jabar",
"data": [
10, 4, 6, 17, 16, 7, 9, 10, 8, 5, 14, 11, 4, 6, 9, 15, 13, 3, 7, 10,
12, 9, 8, 14, 13, 2, 5, 12, 11, 3, 2
]
},
{
"name": "Polda Jateng",
"data": [
7, 6, 3, 13, 5, 9, 10, 6, 7, 8, 13, 15, 6, 7, 5, 8, 14, 11, 13, 4,
4, 6, 10, 11, 14, 2, 6, 12, 10, 8, 12
]
}
]
},
{
"id": 5,
"year": 2025,
"month": 3,
"suggestions": [14, 32, 10, 21, 15, 18]
"month": 6,
"users": [
{
"name": "Polda Aceh",
"data": [
12, 7, 5, 19, 3, 8, 15, 4, 9, 6, 14, 11, 13, 10, 2, 18, 7, 5, 8, 17,
4, 6, 14, 15, 3, 10, 12, 9, 6, 13, 5
]
},
{
"name": "Polda Sumatera Utara",
"data": [
10, 4, 6, 7, 5, 12, 11, 3, 17, 16, 7, 9, 10, 8, 5, 14, 11, 4, 6, 9,
15, 13, 3, 7, 10, 12, 9, 8, 14, 13, 4
]
},
{
"name": "Polda Sumatera Barat",
"data": [
7, 6, 3, 13, 5, 9, 10, 6, 7, 8, 13, 15, 6, 7, 5, 8, 14, 11, 13, 4,
6, 12, 10, 8, 12, 4, 6, 10, 11, 14, 2
]
},
{
"name": "Polda Metro Jaya",
"data": [
12, 7, 5, 19, 3, 8, 15, 4, 9, 6, 14, 11, 13, 10, 2, 18, 7, 5, 8, 17,
4, 6, 9, 6, 13, 5, 14, 15, 3, 10, 12
]
},
{
"name": "Polda Jabar",
"data": [
10, 4, 6, 17, 16, 7, 9, 10, 8, 5, 14, 11, 4, 6, 9, 15, 13, 3, 7, 10,
12, 9, 8, 14, 13, 20, 5, 12, 11, 3
]
},
{
"name": "Polda Jateng",
"data": [
7, 6, 3, 13, 5, 9, 10, 6, 7, 8, 13, 15, 6, 7, 5, 8, 14, 11, 13, 4,
4, 6, 10, 11, 14, 2, 6, 12, 10, 8, 12
]
}
]
}
]
}