From 70e2fb006e5954cf9cc6d43567abf70e9c6880fb Mon Sep 17 00:00:00 2001 From: Rama Priyanto Date: Wed, 2 Jul 2025 17:20:43 +0700 Subject: [PATCH 1/3] feat:pie chart --- .../dashboard/chart/pie-chart-browser.tsx | 181 ++++++++++++++++++ .../main/dashboard/dashboard-container.tsx | 48 +---- components/table/visitors-table.tsx | 5 + 3 files changed, 188 insertions(+), 46 deletions(-) create mode 100644 components/main/dashboard/chart/pie-chart-browser.tsx create mode 100644 components/table/visitors-table.tsx diff --git a/components/main/dashboard/chart/pie-chart-browser.tsx b/components/main/dashboard/chart/pie-chart-browser.tsx new file mode 100644 index 0000000..edb7b1a --- /dev/null +++ b/components/main/dashboard/chart/pie-chart-browser.tsx @@ -0,0 +1,181 @@ +"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 }, +]; + +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([]); + const [labels, setLabels] = useState([]); + const [data, setData] = useState([]); + const [total, setTotal] = useState(0); + const [selectedLabel, setSelectedLabel] = useState(""); + + const [topContentDate, setTopContentDate] = useState({ + startDate: parseDate( + convertDateFormatNoTimeV2( + new Date(new Date().setDate(new Date().getDate() - 7)) + ) + ), + endDate: parseDate(convertDateFormatNoTimeV2(new Date())), + }); + + useEffect(() => { + fetchData(); + }, [topContentDate.startDate, topContentDate.endDate]); + + const fetchData = async () => { + const data = dummy; + setData(data); + const label = data.map((a) => a.name); + const seriesNow = data.map((a) => a.value); + 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 ( +
+
+

Browser Statistics

+ +
+
+
+

Browser Statistics from

+ + + + {convertDateFormatNoTime(topContentDate.startDate)} + + + + + setTopContentDate({ + startDate: e, + endDate: topContentDate.endDate, + }) + } + maxValue={topContentDate.endDate} + /> + + +

to

+ + + + {convertDateFormatNoTime(topContentDate.endDate)} + + + + + setTopContentDate({ + startDate: topContentDate.startDate, + endDate: e, + }) + } + minValue={topContentDate.startDate} + /> + + +
+
+
+

Browser

+

Visitors

+

Persentage

+
+ + {data && + data?.map((list: any, index: number) => ( +
+

{list.name}

+

{list.value}

+

{getPersentage(list.value)}

+
+ ))} +
+
+
+ ); +} diff --git a/components/main/dashboard/dashboard-container.tsx b/components/main/dashboard/dashboard-container.tsx index 0ba669d..008508c 100644 --- a/components/main/dashboard/dashboard-container.tsx +++ b/components/main/dashboard/dashboard-container.tsx @@ -71,6 +71,7 @@ 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"; +import PieChartLoginBrowser from "./chart/pie-chart-browser"; type ArticleData = Article & { no: number; @@ -116,52 +117,6 @@ const months = [ "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() { const { isOpen, onOpen, onOpenChange } = useDisclosure(); @@ -1132,6 +1087,7 @@ export default function DashboardContainer() {
{chartVisitorTotal}
+ )} diff --git a/components/table/visitors-table.tsx b/components/table/visitors-table.tsx new file mode 100644 index 0000000..1e0cfef --- /dev/null +++ b/components/table/visitors-table.tsx @@ -0,0 +1,5 @@ +"use-client"; + +export default function VisitorsTable() { + return
aaa
; +} From 8a4a3b1d6742cadfeebe8ee866e5455c2a75daf2 Mon Sep 17 00:00:00 2001 From: Rama Priyanto Date: Wed, 9 Jul 2025 12:02:51 +0700 Subject: [PATCH 2/3] feat:visitors table, fix:piee chart api, chart total views analytic --- app/layout.tsx | 2 +- .../main/dashboard/chart/column-chart.tsx | 33 +- .../dashboard/chart/pie-chart-browser.tsx | 66 ++-- .../main/dashboard/dashboard-container.tsx | 6 +- components/table/dashboard-visitors-table.tsx | 308 ++++++++++++++++++ services/activity-log.ts | 9 + services/article.ts | 14 + 7 files changed, 410 insertions(+), 28 deletions(-) create mode 100644 components/table/dashboard-visitors-table.tsx diff --git a/app/layout.tsx b/app/layout.tsx index ef41878..6e3a51c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -57,7 +57,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
{children}
- + {/* */} ); diff --git a/components/main/dashboard/chart/column-chart.tsx b/components/main/dashboard/chart/column-chart.tsx index 1ffbfcc..9e19dd0 100644 --- a/components/main/dashboard/chart/column-chart.tsx +++ b/components/main/dashboard/chart/column-chart.tsx @@ -96,11 +96,21 @@ const ApexChartColumn = (props: { const [seriesShare, setSeriesShare] = useState([]); const [years, setYear] = useState(""); const [datas, setDatas] = useState([]); + const [totalChart, setTotalChart] = useState({ + views: 0, + comment: 0, + share: 0, + }); useEffect(() => { initFetch(); }, [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 splitDate = date.split(" "); const splitDateDaily = String(range.start.year); @@ -147,6 +157,13 @@ const ApexChartColumn = (props: { setSeriesComment(getDatas.comment); setSeriesView(getDatas.view); setSeriesShare(getDatas.share); + + const totalChartNow = { + views: counting(getDatas.view), + share: counting(getDatas.share), + comment: counting(getDatas.comment), + }; + setTotalChart(totalChartNow); } if (type === "daily") { const mappedData = getRangeAcrossMonths( @@ -161,6 +178,12 @@ const ApexChartColumn = (props: { setSeriesView(mappedData.view); setSeriesShare(mappedData.share); setCategories(mappedData.labels); + const totalChartNow = { + views: counting(mappedData.view), + share: counting(mappedData.share), + comment: counting(mappedData.comment), + }; + setTotalChart(totalChartNow); } // if (type === "weekly") { // const category = []; @@ -196,7 +219,7 @@ const ApexChartColumn = (props: { }, [view, seriesShare, seriesView, seriesComment]); return ( -
+
+
+
Chart Total
+
+ {totalChart.views} {totalChart.views > 1 ? "Views" : "View"}{" "} + {totalChart.comment} {totalChart.comment > 1 ? "Comments" : "Comment"}{" "} + {totalChart.share} {totalChart.share > 1 ? "Shares" : "Share"}{" "} +
+
{" "}
); }; diff --git a/components/main/dashboard/chart/pie-chart-browser.tsx b/components/main/dashboard/chart/pie-chart-browser.tsx index edb7b1a..2d41765 100644 --- a/components/main/dashboard/chart/pie-chart-browser.tsx +++ b/components/main/dashboard/chart/pie-chart-browser.tsx @@ -9,6 +9,12 @@ const dummy = [ { name: "Brave", value: 29 }, ]; +interface BrowserVisitor { + browserName: string; + totalVisitor: number; +} + +import { getStatisticVisitorsBrowser } from "@/services/article"; import { convertDateFormatNoTime, convertDateFormatNoTimeV2, @@ -26,11 +32,11 @@ import ReactApexChart from "react-apexcharts"; export default function PieChartLoginBrowser() { const [series, setSeries] = useState([]); const [labels, setLabels] = useState([]); - const [data, setData] = useState([]); + const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [selectedLabel, setSelectedLabel] = useState(""); - const [topContentDate, setTopContentDate] = useState({ + const [visitorBrowserDate, setVisitorBrowserDate] = useState({ startDate: parseDate( convertDateFormatNoTimeV2( new Date(new Date().setDate(new Date().getDate() - 7)) @@ -41,13 +47,23 @@ export default function PieChartLoginBrowser() { useEffect(() => { fetchData(); - }, [topContentDate.startDate, topContentDate.endDate]); + }, [visitorBrowserDate.startDate, visitorBrowserDate.endDate]); const fetchData = async () => { - const data = dummy; + 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.name); - const seriesNow = data.map((a) => a.value); + 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); @@ -60,8 +76,8 @@ export default function PieChartLoginBrowser() { }; return ( -
-
+
+

Browser Statistics

-
+

Browser Statistics from

- {convertDateFormatNoTime(topContentDate.startDate)} + {convertDateFormatNoTime(visitorBrowserDate.startDate)} - setTopContentDate({ + setVisitorBrowserDate({ startDate: e, - endDate: topContentDate.endDate, + endDate: visitorBrowserDate.endDate, }) } - maxValue={topContentDate.endDate} + maxValue={visitorBrowserDate.endDate} /> @@ -133,19 +149,19 @@ export default function PieChartLoginBrowser() { > - {convertDateFormatNoTime(topContentDate.endDate)} + {convertDateFormatNoTime(visitorBrowserDate.endDate)} - setTopContentDate({ - startDate: topContentDate.startDate, + setVisitorBrowserDate({ + startDate: visitorBrowserDate.startDate, endDate: e, }) } - minValue={topContentDate.startDate} + minValue={visitorBrowserDate.startDate} /> @@ -158,20 +174,20 @@ export default function PieChartLoginBrowser() {
{data && - data?.map((list: any, index: number) => ( + data?.map((list, index) => (
-

{list.name}

-

{list.value}

-

{getPersentage(list.value)}

+

{list.browserName}

+

{list.totalVisitor}

+

{getPersentage(list.totalVisitor)}

))}
diff --git a/components/main/dashboard/dashboard-container.tsx b/components/main/dashboard/dashboard-container.tsx index 008508c..3d909e2 100644 --- a/components/main/dashboard/dashboard-container.tsx +++ b/components/main/dashboard/dashboard-container.tsx @@ -72,6 +72,7 @@ import IndonesiaMap from "@/components/ui/maps-charts"; import ApexChartDynamic from "./chart/dynamic-bar-char"; import ApexMultiLineChart from "./chart/multiline-chart"; import PieChartLoginBrowser from "./chart/pie-chart-browser"; +import DashboardVisitorsTable from "@/components/table/dashboard-visitors-table"; type ArticleData = Article & { no: number; @@ -1084,10 +1085,13 @@ export default function DashboardContainer() {
Chart Total
-
{chartVisitorTotal}
+
+ {chartVisitorTotal} Visitors +
+
)}
diff --git a/components/table/dashboard-visitors-table.tsx b/components/table/dashboard-visitors-table.tsx new file mode 100644 index 0000000..dd2f07a --- /dev/null +++ b/components/table/dashboard-visitors-table.tsx @@ -0,0 +1,308 @@ +"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"; + +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([]); + const [visitorBrowserDate, setVisitorBrowserDate] = useState({ + startDate: parseDate( + convertDateFormatNoTimeV2( + new Date(new Date().setDate(new Date().getDate() - 7)) + ) + ), + endDate: parseDate(convertDateFormatNoTimeV2(new Date())), + }); + useEffect(() => { + initFetch(); + }, [ + page, + visitorBrowserDate.startDate, + visitorBrowserDate.endDate, + showData, + ]); + + 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 + }`; + }; + const res = await getVisitorLog({ + page: page, + limit: showData, + startDate: getDate(visitorBrowserDate.startDate), + endDate: getDate(visitorBrowserDate.endDate), + }); + console.log("resssss", res?.data?.data); + getTableNumber(Number(showData), res?.data?.data); + setTotalPage(res?.data?.meta?.totalPage); + }; + + 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 ( + + {cellValue} + + ); + case "browserName": + return ( +
+ {cellValue}{" "} + {cellValue !== null ? "Ver " + visitor.browserVersion : ""} +
+ ); + case "visitorRegion": + return ( +

+ {cellValue + ? findRegion(cellValue.toUpperCase()).toLowerCase() + : ""} +

+ ); + case "createdAt": + return

{convertDateFormat(visitor.createdAt)}

; + + default: + return cellValue; + } + }, + [tableVisitorData] + ); + return ( +
+
+

Visitors Table

+
+
+

Show Data

+ +
+
+

Start Date

+ + + + + + + + setVisitorBrowserDate({ + startDate: e, + endDate: visitorBrowserDate.endDate, + }) + } + maxValue={visitorBrowserDate.endDate} + /> + + +
+
+

End Date

+ + + + + + + + setVisitorBrowserDate({ + startDate: visitorBrowserDate.startDate, + endDate: e, + }) + } + minValue={visitorBrowserDate.startDate} + /> + + +
+
+
+ + + {(column) => ( + {column.name} + )} + + } + > + {(item: any) => ( + + {(columnKey) => ( + {renderCell(item, columnKey)} + )} + + )} + +
+
+ setPage(page)} + /> +
+
+
+
+ ); +} diff --git a/services/activity-log.ts b/services/activity-log.ts index 2056d6f..3f685c8 100644 --- a/services/activity-log.ts +++ b/services/activity-log.ts @@ -19,3 +19,12 @@ export async function getActivity() { const pathUrl = `/activity-logs/statistics`; 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); +} diff --git a/services/article.ts b/services/article.ts index bbabf95..23e6f57 100644 --- a/services/article.ts +++ b/services/article.ts @@ -226,6 +226,20 @@ export async function getStatisticForMaps(startDate: string, endDate: string) { 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) { const headers = { "content-type": "application/json", From d747233721da7c476b78d8265f5dbf0dfeb2e391 Mon Sep 17 00:00:00 2001 From: Rama Priyanto Date: Wed, 9 Jul 2025 14:22:52 +0700 Subject: [PATCH 3/3] fix:article search by category --- components/table/article-table.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/components/table/article-table.tsx b/components/table/article-table.tsx index 4b44ca7..cae7cb7 100644 --- a/components/table/article-table.tsx +++ b/components/table/article-table.tsx @@ -129,14 +129,12 @@ export default function ArticleTable() { useEffect(() => { initState(); - }, [ - page, - showData, - startDateValue, - selectedCategories, - articleDate, - selectedUsers, - ]); + }, [page, showData, startDateValue]); + + useEffect(() => { + setPage(1); + initState(); + }, [selectedCategories, selectedUsers, articleDate]); useEffect(() => { getCategories();