fix:article search by category, feat:piechart & visitors table
This commit is contained in:
commit
41d0303bc1
|
|
@ -66,7 +66,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
<Providers themeProps={{ attribute: "class", defaultTheme: "light" }}>
|
<Providers themeProps={{ attribute: "class", defaultTheme: "light" }}>
|
||||||
<main className="">{children}</main>
|
<main className="">{children}</main>
|
||||||
</Providers>
|
</Providers>
|
||||||
<LoadTawk />
|
{/* <LoadTawk /> */}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -97,11 +97,21 @@ const ApexChartColumn = (props: {
|
||||||
const [seriesShare, setSeriesShare] = useState<number[]>([]);
|
const [seriesShare, setSeriesShare] = useState<number[]>([]);
|
||||||
const [years, setYear] = useState("");
|
const [years, setYear] = useState("");
|
||||||
const [datas, setDatas] = useState<any>([]);
|
const [datas, setDatas] = useState<any>([]);
|
||||||
|
const [totalChart, setTotalChart] = useState({
|
||||||
|
views: 0,
|
||||||
|
comment: 0,
|
||||||
|
share: 0,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initFetch();
|
initFetch();
|
||||||
}, [date, type, view, range]);
|
}, [date, type, view, range]);
|
||||||
|
|
||||||
|
const counting = (data: number[]) => {
|
||||||
|
const total = data.reduce((a: number, b: number) => a + b, 0);
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
|
||||||
const initFetch = async () => {
|
const initFetch = async () => {
|
||||||
const splitDate = date.split(" ");
|
const splitDate = date.split(" ");
|
||||||
const splitDateDaily = String(range.start.year);
|
const splitDateDaily = String(range.start.year);
|
||||||
|
|
@ -149,6 +159,13 @@ const ApexChartColumn = (props: {
|
||||||
setSeriesComment(getDatas.comment);
|
setSeriesComment(getDatas.comment);
|
||||||
setSeriesView(getDatas.view);
|
setSeriesView(getDatas.view);
|
||||||
setSeriesShare(getDatas.share);
|
setSeriesShare(getDatas.share);
|
||||||
|
|
||||||
|
const totalChartNow = {
|
||||||
|
views: counting(getDatas.view),
|
||||||
|
share: counting(getDatas.share),
|
||||||
|
comment: counting(getDatas.comment),
|
||||||
|
};
|
||||||
|
setTotalChart(totalChartNow);
|
||||||
}
|
}
|
||||||
if (type === "daily") {
|
if (type === "daily") {
|
||||||
const mappedData = getRangeAcrossMonths(
|
const mappedData = getRangeAcrossMonths(
|
||||||
|
|
@ -163,6 +180,12 @@ const ApexChartColumn = (props: {
|
||||||
setSeriesView(mappedData.view);
|
setSeriesView(mappedData.view);
|
||||||
setSeriesShare(mappedData.share);
|
setSeriesShare(mappedData.share);
|
||||||
setCategories(mappedData.labels);
|
setCategories(mappedData.labels);
|
||||||
|
const totalChartNow = {
|
||||||
|
views: counting(mappedData.view),
|
||||||
|
share: counting(mappedData.share),
|
||||||
|
comment: counting(mappedData.comment),
|
||||||
|
};
|
||||||
|
setTotalChart(totalChartNow);
|
||||||
}
|
}
|
||||||
// if (type === "weekly") {
|
// if (type === "weekly") {
|
||||||
// const category = [];
|
// const category = [];
|
||||||
|
|
@ -198,7 +221,7 @@ const ApexChartColumn = (props: {
|
||||||
}, [view, seriesShare, seriesView, seriesComment]);
|
}, [view, seriesShare, seriesView, seriesComment]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full flex flex-col">
|
||||||
<div id="chart" className="h-full">
|
<div id="chart" className="h-full">
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={{
|
options={{
|
||||||
|
|
@ -222,6 +245,14 @@ const ApexChartColumn = (props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="html-dist"></div>
|
<div id="html-dist"></div>
|
||||||
|
<div className="flex flex-row gap-2 mt-5 border-1 p-5 rounded-lg">
|
||||||
|
<div className="w-1/5 text-center">Chart Total</div>
|
||||||
|
<div className="w-4/5 text-center">
|
||||||
|
{totalChart.views} {totalChart.views > 1 ? "Views" : "View"}{" "}
|
||||||
|
{totalChart.comment} {totalChart.comment > 1 ? "Comments" : "Comment"}{" "}
|
||||||
|
{totalChart.share} {totalChart.share > 1 ? "Shares" : "Share"}{" "}
|
||||||
|
</div>
|
||||||
|
</div>{" "}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
const dummy = [
|
||||||
|
{ name: "Chrome", value: 42 },
|
||||||
|
{ name: "Firefox", value: 23 },
|
||||||
|
{ name: "Edge", value: 15 },
|
||||||
|
{ name: "Safari", value: 37 },
|
||||||
|
{ name: "Opera", value: 8 },
|
||||||
|
{ name: "Brave", value: 29 },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface BrowserVisitor {
|
||||||
|
browserName: string;
|
||||||
|
totalVisitor: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
import { getStatisticVisitorsBrowser } from "@/services/article";
|
||||||
|
import {
|
||||||
|
convertDateFormatNoTime,
|
||||||
|
convertDateFormatNoTimeV2,
|
||||||
|
} from "@/utils/global";
|
||||||
|
import {
|
||||||
|
Calendar,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@heroui/react";
|
||||||
|
import { parseDate } from "@internationalized/date";
|
||||||
|
import { Fragment, useEffect, useState } from "react";
|
||||||
|
import ReactApexChart from "react-apexcharts";
|
||||||
|
|
||||||
|
export default function PieChartLoginBrowser() {
|
||||||
|
const [series, setSeries] = useState<number[]>([]);
|
||||||
|
const [labels, setLabels] = useState<string[]>([]);
|
||||||
|
const [data, setData] = useState<BrowserVisitor[]>([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [selectedLabel, setSelectedLabel] = useState<string>("");
|
||||||
|
|
||||||
|
const [visitorBrowserDate, setVisitorBrowserDate] = useState({
|
||||||
|
startDate: parseDate(
|
||||||
|
convertDateFormatNoTimeV2(
|
||||||
|
new Date(new Date().setDate(new Date().getDate() - 7))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
endDate: parseDate(convertDateFormatNoTimeV2(new Date())),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [visitorBrowserDate.startDate, visitorBrowserDate.endDate]);
|
||||||
|
|
||||||
|
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 getStatisticVisitorsBrowser(
|
||||||
|
getDate(visitorBrowserDate.startDate),
|
||||||
|
getDate(visitorBrowserDate.endDate)
|
||||||
|
);
|
||||||
|
const data: BrowserVisitor[] = res?.data?.data;
|
||||||
|
setData(data);
|
||||||
|
const label = data.map((a) => a.browserName);
|
||||||
|
const seriesNow = data.map((a) => a.totalVisitor);
|
||||||
|
const totalNow = seriesNow.reduce((a, c) => a + c, 0);
|
||||||
|
setTotal(totalNow);
|
||||||
|
setLabels(label);
|
||||||
|
setSeries(seriesNow);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPersentage = (value: number) => {
|
||||||
|
const count = ((value / total) * 100).toFixed(1);
|
||||||
|
return `${count}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col lg:flex-row w-full mt-5 gap-4">
|
||||||
|
<div className="w-full lg:w-1/2 border-1 rounded-lg p-4">
|
||||||
|
<p className="font-bold">Browser Statistics</p>
|
||||||
|
<ReactApexChart
|
||||||
|
options={{
|
||||||
|
chart: {
|
||||||
|
width: 600,
|
||||||
|
type: "pie" as const,
|
||||||
|
events: {
|
||||||
|
dataPointSelection: function (
|
||||||
|
event: any,
|
||||||
|
chartContext: any,
|
||||||
|
config: any
|
||||||
|
) {
|
||||||
|
const selectedIndex = config.dataPointIndex;
|
||||||
|
const selected = labels[selectedIndex];
|
||||||
|
setSelectedLabel(selected);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
labels: labels,
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 480,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
series={series}
|
||||||
|
type="pie"
|
||||||
|
width={600}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full lg:w-1/2 flex flex-col border-1 rounded-lg p-4">
|
||||||
|
<div className="flex flex-row gap-1 justify-center">
|
||||||
|
<p> Browser Statistics from</p>
|
||||||
|
<Popover
|
||||||
|
placement="bottom"
|
||||||
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
||||||
|
>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<a className="cursor-pointer underline">
|
||||||
|
{convertDateFormatNoTime(visitorBrowserDate.startDate)}
|
||||||
|
</a>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="bg-transparent">
|
||||||
|
<Calendar
|
||||||
|
value={visitorBrowserDate.startDate}
|
||||||
|
onChange={(e) =>
|
||||||
|
setVisitorBrowserDate({
|
||||||
|
startDate: e,
|
||||||
|
endDate: visitorBrowserDate.endDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
maxValue={visitorBrowserDate.endDate}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<p>to</p>
|
||||||
|
<Popover
|
||||||
|
placement="bottom"
|
||||||
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
||||||
|
>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<a className="cursor-pointer underline">
|
||||||
|
{convertDateFormatNoTime(visitorBrowserDate.endDate)}
|
||||||
|
</a>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="bg-transparent">
|
||||||
|
<Calendar
|
||||||
|
value={visitorBrowserDate.endDate}
|
||||||
|
onChange={(e) =>
|
||||||
|
setVisitorBrowserDate({
|
||||||
|
startDate: visitorBrowserDate.startDate,
|
||||||
|
endDate: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
minValue={visitorBrowserDate.startDate}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col mt-5 overflow-auto gap-3 p-4">
|
||||||
|
<div className="grid grid-cols-3 p-4">
|
||||||
|
<p>Browser</p>
|
||||||
|
<p>Visitors</p>
|
||||||
|
<p>Persentage</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{data &&
|
||||||
|
data?.map((list, index) => (
|
||||||
|
<div
|
||||||
|
key={list.browserName}
|
||||||
|
className={`grid grid-cols-3 p-4 ${
|
||||||
|
selectedLabel === list.browserName
|
||||||
|
? "bg-slate-600 text-white"
|
||||||
|
: index % 2 == 0
|
||||||
|
? "bg-gray-200"
|
||||||
|
: "bg-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p className="font-semibold">{list.browserName}</p>
|
||||||
|
<p>{list.totalVisitor}</p>
|
||||||
|
<p>{getPersentage(list.totalVisitor)}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -72,6 +72,8 @@ import ApexChartColumnVisitors from "./chart/visitor-chart";
|
||||||
import IndonesiaMap from "@/components/ui/maps-charts";
|
import IndonesiaMap from "@/components/ui/maps-charts";
|
||||||
import ApexChartDynamic from "./chart/dynamic-bar-char";
|
import ApexChartDynamic from "./chart/dynamic-bar-char";
|
||||||
import ApexMultiLineChart from "./chart/multiline-chart";
|
import ApexMultiLineChart from "./chart/multiline-chart";
|
||||||
|
import PieChartLoginBrowser from "./chart/pie-chart-browser";
|
||||||
|
import DashboardVisitorsTable from "@/components/table/dashboard-visitors-table";
|
||||||
|
|
||||||
type ArticleData = Article & {
|
type ArticleData = Article & {
|
||||||
no: number;
|
no: number;
|
||||||
|
|
@ -117,52 +119,6 @@ const months = [
|
||||||
"Dec",
|
"Dec",
|
||||||
];
|
];
|
||||||
|
|
||||||
const rawData = [
|
|
||||||
{ name: "Aceh", value: 32 },
|
|
||||||
{ name: "Sumatera Utara", value: 78 },
|
|
||||||
{ name: "Sumatera Barat", value: 45 },
|
|
||||||
{ name: "Riau", value: 63 },
|
|
||||||
{ name: "Jambi", value: 21 },
|
|
||||||
{ name: "Sumatera Selatan", value: 56 },
|
|
||||||
{ name: "Bengkulu", value: 11 },
|
|
||||||
{ name: "Lampung", value: 49 },
|
|
||||||
{ name: "Kepulauan Bangka Belitung", value: 27 },
|
|
||||||
{ name: "Kepulauan Riau", value: 19 },
|
|
||||||
{ name: "DKI Jakarta", value: 97 },
|
|
||||||
{ name: "Jawa Barat", value: 84 },
|
|
||||||
{ name: "Jawa Tengah", value: 88 },
|
|
||||||
{ name: "Daerah Istimewa Yogyakarta", value: 36 },
|
|
||||||
{ name: "Jawa Timur", value: 91 },
|
|
||||||
{ name: "Banten", value: 52 },
|
|
||||||
{ name: "Bali", value: 66 },
|
|
||||||
{ name: "Nusa Tenggara Barat", value: 40 },
|
|
||||||
{ name: "Nusa Tenggara Timur", value: 59 },
|
|
||||||
{ name: "Kalimantan Barat", value: 61 },
|
|
||||||
{ name: "Kalimantan Tengah", value: 29 },
|
|
||||||
{ name: "Kalimantan Selatan", value: 71 },
|
|
||||||
{ name: "Kalimantan Timur", value: 76 },
|
|
||||||
{ name: "Kalimantan Utara", value: 18 },
|
|
||||||
{ name: "Sulawesi Utara", value: 55 },
|
|
||||||
{ name: "Sulawesi Tengah", value: 31 },
|
|
||||||
{ name: "Sulawesi Selatan", value: 80 },
|
|
||||||
{ name: "Sulawesi Tenggara", value: 34 },
|
|
||||||
{ name: "Gorontalo", value: 23 },
|
|
||||||
{ name: "Sulawesi Barat", value: 12 },
|
|
||||||
{ name: "Maluku", value: 25 },
|
|
||||||
{ name: "Maluku Utara", value: 30 },
|
|
||||||
{ name: "Papua", value: 37 },
|
|
||||||
{ name: "Papua Barat", value: 33 },
|
|
||||||
{ name: "Papua Tengah", value: 16 },
|
|
||||||
{ name: "Papua Pegunungan", value: 8 },
|
|
||||||
{ name: "Papua Selatan", value: 14 },
|
|
||||||
{ name: "Papua Barat Daya", value: 6 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const getTotalData: number[] = [];
|
|
||||||
rawData.map((list) => {
|
|
||||||
getTotalData.push(list.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function DashboardContainer() {
|
export default function DashboardContainer() {
|
||||||
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||||
|
|
||||||
|
|
@ -1137,9 +1093,13 @@ export default function DashboardContainer() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-2 mt-5 border-1 p-5 rounded-lg">
|
<div className="flex flex-row gap-2 mt-5 border-1 p-5 rounded-lg">
|
||||||
<div className="w-1/5 text-center">Chart Total</div>
|
<div className="w-1/5 text-center">Chart Total</div>
|
||||||
<div className="w-4/5 text-center">{chartVisitorTotal}</div>
|
<div className="w-4/5 text-center">
|
||||||
|
{chartVisitorTotal} Visitors
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<PieChartLoginBrowser />
|
||||||
|
<DashboardVisitorsTable />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -129,14 +129,15 @@ export default function ArticleTable() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initState();
|
initState();
|
||||||
}, [
|
}, [page, showData]);
|
||||||
page,
|
|
||||||
showData,
|
useEffect(() => {
|
||||||
startDateValue,
|
if (page == 1) {
|
||||||
selectedCategories,
|
initState();
|
||||||
articleDate,
|
} else {
|
||||||
selectedUsers,
|
setPage(1);
|
||||||
]);
|
}
|
||||||
|
}, [selectedCategories, selectedUsers, articleDate, startDateValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getCategories();
|
getCategories();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { getVisitorLog } from "@/services/activity-log";
|
||||||
|
import {
|
||||||
|
convertDateFormat,
|
||||||
|
convertDateFormatNoTime,
|
||||||
|
convertDateFormatNoTimeV2,
|
||||||
|
} from "@/utils/global";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Calendar,
|
||||||
|
Pagination,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
Select,
|
||||||
|
SelectItem,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableColumn,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@heroui/react";
|
||||||
|
import { Key, useCallback, useEffect, useState } from "react";
|
||||||
|
import { parseDate } from "@internationalized/date";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { close, loading } from "@/config/swal";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: "No", uid: "no" },
|
||||||
|
{ name: "Browser", uid: "browserName" },
|
||||||
|
{ name: "Date & Time ", uid: "createdAt" },
|
||||||
|
{ name: "IP Visitors", uid: "visitorIp" },
|
||||||
|
{ name: "URL", uid: "url" },
|
||||||
|
{ name: "City", uid: "visitorCity" },
|
||||||
|
{ name: "Region", uid: "visitorRegion" },
|
||||||
|
// { name: "Country", uid: "visitorCountry" },
|
||||||
|
];
|
||||||
|
|
||||||
|
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: "BANGKA BELITUNG 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 findRegion = (name: string): string => {
|
||||||
|
const find = provinces.find((a) => a.engName === name);
|
||||||
|
return find ? find.inName : "";
|
||||||
|
};
|
||||||
|
export default function DashboardVisitorsTable() {
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
const [totalPage, setTotalPage] = useState(1);
|
||||||
|
const [showData, setShowData] = useState("10");
|
||||||
|
const [tableVisitorData, setTableVisitorData] = useState<any>([]);
|
||||||
|
const [visitorBrowserDate, setVisitorBrowserDate] = useState({
|
||||||
|
startDate: parseDate(
|
||||||
|
convertDateFormatNoTimeV2(
|
||||||
|
new Date(new Date().setDate(new Date().getDate() - 7))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
endDate: parseDate(convertDateFormatNoTimeV2(new Date())),
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
initFetch();
|
||||||
|
}, [page, showData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (page == 0) {
|
||||||
|
initFetch();
|
||||||
|
} else {
|
||||||
|
setPage(0);
|
||||||
|
}
|
||||||
|
}, [visitorBrowserDate.startDate, visitorBrowserDate.endDate]);
|
||||||
|
|
||||||
|
const initFetch = async () => {
|
||||||
|
const getDate = (data: any) => {
|
||||||
|
return `${data.year}-${data.month < 10 ? `0${data.month}` : data.month}-${
|
||||||
|
data.day < 10 ? `0${data.day}` : data.day
|
||||||
|
}`;
|
||||||
|
};
|
||||||
|
loading();
|
||||||
|
const res = await getVisitorLog({
|
||||||
|
page: page,
|
||||||
|
limit: showData,
|
||||||
|
startDate: getDate(visitorBrowserDate.startDate),
|
||||||
|
endDate: getDate(visitorBrowserDate.endDate),
|
||||||
|
});
|
||||||
|
getTableNumber(Number(showData), res?.data?.data);
|
||||||
|
setTotalPage(res?.data?.meta?.totalPage);
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTableNumber = (limit: number, data: any) => {
|
||||||
|
if (data) {
|
||||||
|
const startIndex = limit * page;
|
||||||
|
let iterate = 0;
|
||||||
|
const newData = data.map((value: any) => {
|
||||||
|
iterate++;
|
||||||
|
value.no = startIndex + iterate;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
setTableVisitorData(newData);
|
||||||
|
} else {
|
||||||
|
setTableVisitorData([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCell = useCallback(
|
||||||
|
(visitor: any, columnKey: Key) => {
|
||||||
|
const cellValue = visitor[columnKey as keyof any];
|
||||||
|
|
||||||
|
switch (columnKey) {
|
||||||
|
case "url":
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={cellValue.replace("new-", "")}
|
||||||
|
target="_blank"
|
||||||
|
className="hover:text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{cellValue.replace("new-", "")}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
case "browserName":
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{cellValue}{" "}
|
||||||
|
{cellValue !== null ? "Ver " + visitor.browserVersion : ""}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "visitorRegion":
|
||||||
|
return (
|
||||||
|
<p className="capitalize">
|
||||||
|
{cellValue
|
||||||
|
? findRegion(cellValue.toUpperCase()).toLowerCase()
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
case "createdAt":
|
||||||
|
return <p>{convertDateFormat(visitor.createdAt)}</p>;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return cellValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[tableVisitorData]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="flex w-full mt-5 gap-4">
|
||||||
|
<div className="border p-3 rounded-lg flex flex-col gap-2 w-full">
|
||||||
|
<p className="font-bold">Visitors Table</p>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<div className="flex flex-col gap-1 w-full lg:w-[72px]">
|
||||||
|
<p className="font-semibold text-sm">Show Data</p>
|
||||||
|
<Select
|
||||||
|
label=""
|
||||||
|
variant="bordered"
|
||||||
|
labelPlacement="outside"
|
||||||
|
placeholder="Select"
|
||||||
|
selectedKeys={[showData]}
|
||||||
|
className="w-full"
|
||||||
|
classNames={{ trigger: "border-1" }}
|
||||||
|
onChange={(e) =>
|
||||||
|
e.target.value === "" ? "" : setShowData(e.target.value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectItem key="5">5</SelectItem>
|
||||||
|
<SelectItem key="10">10</SelectItem>
|
||||||
|
<SelectItem key="25">25</SelectItem>
|
||||||
|
<SelectItem key="50">50</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="font-semibold text-sm">Start Date</p>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
placement="bottom"
|
||||||
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
||||||
|
>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button className="border-1" variant="bordered">
|
||||||
|
{convertDateFormatNoTime(visitorBrowserDate.startDate)}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="bg-transparent">
|
||||||
|
<Calendar
|
||||||
|
value={visitorBrowserDate.startDate}
|
||||||
|
onChange={(e) =>
|
||||||
|
setVisitorBrowserDate({
|
||||||
|
startDate: e,
|
||||||
|
endDate: visitorBrowserDate.endDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
maxValue={visitorBrowserDate.endDate}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="font-semibold text-sm">End Date</p>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
placement="bottom"
|
||||||
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
||||||
|
>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button className="border-1" variant="bordered">
|
||||||
|
{convertDateFormatNoTime(visitorBrowserDate.endDate)}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="bg-transparent">
|
||||||
|
<Calendar
|
||||||
|
value={visitorBrowserDate.endDate}
|
||||||
|
onChange={(e) =>
|
||||||
|
setVisitorBrowserDate({
|
||||||
|
startDate: visitorBrowserDate.startDate,
|
||||||
|
endDate: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
minValue={visitorBrowserDate.startDate}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-start rounded-2xl gap-3">
|
||||||
|
<Table
|
||||||
|
aria-label="micro issue table"
|
||||||
|
className="rounded-3xl"
|
||||||
|
classNames={{
|
||||||
|
th: "bg-white dark:bg-black text-black dark:text-white border-b-1 text-md",
|
||||||
|
base: "bg-white dark:bg-black border",
|
||||||
|
wrapper:
|
||||||
|
"min-h-[50px] bg-transpararent text-black dark:text-white ",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableHeader columns={columns}>
|
||||||
|
{(column) => (
|
||||||
|
<TableColumn key={column.uid}>{column.name}</TableColumn>
|
||||||
|
)}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody
|
||||||
|
items={tableVisitorData}
|
||||||
|
emptyContent={"No data to display."}
|
||||||
|
loadingContent={<Spinner label="Loading..." />}
|
||||||
|
>
|
||||||
|
{(item: any) => (
|
||||||
|
<TableRow key={item.id}>
|
||||||
|
{(columnKey) => (
|
||||||
|
<TableCell>{renderCell(item, columnKey)}</TableCell>
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<div className="my-2 w-full flex justify-center">
|
||||||
|
<Pagination
|
||||||
|
isCompact
|
||||||
|
showControls
|
||||||
|
showShadow
|
||||||
|
color="primary"
|
||||||
|
classNames={{
|
||||||
|
base: "bg-transparent",
|
||||||
|
wrapper: "bg-transparent",
|
||||||
|
item: "w-fit px-3",
|
||||||
|
cursor: "w-fit px-3",
|
||||||
|
}}
|
||||||
|
page={page}
|
||||||
|
total={totalPage}
|
||||||
|
onChange={(page) => setPage(page)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
"use-client";
|
||||||
|
|
||||||
|
export default function VisitorsTable() {
|
||||||
|
return <div className="flex flex-col gap-2">aaa</div>;
|
||||||
|
}
|
||||||
|
|
@ -19,3 +19,12 @@ export async function getActivity() {
|
||||||
const pathUrl = `/activity-logs/statistics`;
|
const pathUrl = `/activity-logs/statistics`;
|
||||||
return await httpGet(pathUrl, headers);
|
return await httpGet(pathUrl, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getVisitorLog(data: any) {
|
||||||
|
const { page, limit, startDate, endDate } = data;
|
||||||
|
const headers = { "content-type": "application/json" };
|
||||||
|
const pathUrl = `/activity-logs?purpose=visitor-summary&page=${
|
||||||
|
page || 0
|
||||||
|
}&limit=${limit || 10}&startDate=${startDate || ""}&endDate=${endDate || ""}`;
|
||||||
|
return await httpGet(pathUrl, headers);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,20 @@ export async function getStatisticForMaps(startDate: string, endDate: string) {
|
||||||
headers
|
headers
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getStatisticVisitorsBrowser(
|
||||||
|
startDate: string,
|
||||||
|
endDate: string
|
||||||
|
) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
// Authorization: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
return await httpGet(
|
||||||
|
`/activity-logs/visitors-by-browser-stats?startDate=${startDate}&endDate=${endDate}`,
|
||||||
|
headers
|
||||||
|
);
|
||||||
|
}
|
||||||
export async function getStatisticMonthly(year: string, timeStamp: number) {
|
export async function getStatisticMonthly(year: string, timeStamp: number) {
|
||||||
const headers = {
|
const headers = {
|
||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue