525 lines
19 KiB
TypeScript
525 lines
19 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
DashboardCommentIcon,
|
|
DashboardConnectIcon,
|
|
DashboardShareIcon,
|
|
DashboardSpeecIcon,
|
|
DashboardUserIcon,
|
|
} from "@/components/icons/dashboard-icon";
|
|
import Cookies from "js-cookie";
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
import { Article } from "@/types/globals";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/components/ui/popover";
|
|
import {
|
|
getListArticle,
|
|
getStatisticSummary,
|
|
getTopArticles,
|
|
getUserLevelDataStat,
|
|
} from "@/service/article";
|
|
import { Button } from "@/components/ui/button";
|
|
import Image from "next/image";
|
|
import {
|
|
convertDateFormat,
|
|
convertDateFormatNoTime,
|
|
textEllipsis,
|
|
} from "@/utils/global";
|
|
import DatePicker from "react-datepicker";
|
|
import "react-datepicker/dist/react-datepicker.css";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Calendar } from "@/components/ui/calendar";
|
|
import ApexChartColumn from "@/components/main/dashboard/chart/column-chart";
|
|
import CustomPagination from "@/components/layout/custom-pagination";
|
|
|
|
type ArticleData = Article & {
|
|
no: number;
|
|
createdAt: string;
|
|
};
|
|
|
|
interface TopPages {
|
|
id: number;
|
|
no: number;
|
|
title: string;
|
|
viewCount: number;
|
|
}
|
|
|
|
interface PostCount {
|
|
userLevelId: number;
|
|
no: number;
|
|
userLevelName: string;
|
|
totalArticle: number;
|
|
}
|
|
|
|
export default function DashboardContainer() {
|
|
const username = Cookies.get("username");
|
|
const fullname = Cookies.get("ufne");
|
|
const [page, setPage] = useState(1);
|
|
const [totalPage, setTotalPage] = useState(1);
|
|
const [topPagesTotalPage, setTopPagesTotalPage] = useState(1);
|
|
const [article, setArticle] = useState<ArticleData[]>([]);
|
|
// const [analyticsView, setAnalyticView] = useState<string[]>(["comment", "view", "share"]);
|
|
// const [startDateValue, setStartDateValue] = useState(parseDate(convertDateFormatNoTimeV2(new Date())));
|
|
// const [postContentDate, setPostContentDate] = useState({
|
|
// startDate: parseDate(convertDateFormatNoTimeV2(new Date(new Date().setDate(new Date().getDate() - 7)))),
|
|
// endDate: parseDate(convertDateFormatNoTimeV2(new Date())),
|
|
// });
|
|
|
|
const [startDateValue, setStartDateValue] = useState(new Date());
|
|
const [analyticsView, setAnalyticView] = useState<string[]>([]);
|
|
const options = [
|
|
{ label: "Comment", value: "comment" },
|
|
{ label: "View", value: "view" },
|
|
{ label: "Share", value: "share" },
|
|
];
|
|
const handleChange = (value: string, checked: boolean) => {
|
|
if (checked) {
|
|
setAnalyticView([...analyticsView, value]);
|
|
} else {
|
|
setAnalyticView(analyticsView.filter((v) => v !== value));
|
|
}
|
|
};
|
|
const [postContentDate, setPostContentDate] = useState({
|
|
startDate: new Date(new Date().setDate(new Date().getDate() - 7)),
|
|
endDate: new Date(),
|
|
});
|
|
|
|
const [typeDate, setTypeDate] = useState("monthly");
|
|
const [summary, setSummary] = useState<any>();
|
|
|
|
const [topPages, setTopPages] = useState<TopPages[]>([]);
|
|
const [postCount, setPostCount] = useState<PostCount[]>([]);
|
|
|
|
useEffect(() => {
|
|
fetchSummary();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
initState();
|
|
}, [page]);
|
|
|
|
async function initState() {
|
|
const req = {
|
|
limit: "4",
|
|
page: page,
|
|
search: "",
|
|
};
|
|
const res = await getListArticle(req);
|
|
setArticle(res.data?.data);
|
|
setTotalPage(res?.data?.meta?.totalPage);
|
|
}
|
|
|
|
async function fetchSummary() {
|
|
const res = await getStatisticSummary();
|
|
setSummary(res?.data?.data);
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchTopPages();
|
|
}, [page]);
|
|
|
|
async function fetchTopPages() {
|
|
const req = {
|
|
limit: "10",
|
|
page: page,
|
|
search: "",
|
|
};
|
|
const res = await getTopArticles(req);
|
|
setTopPages(getTableNumber(10, res.data?.data));
|
|
setTopPagesTotalPage(res?.data?.meta?.totalPage);
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchPostCount();
|
|
}, [postContentDate]);
|
|
async function fetchPostCount() {
|
|
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 getUserLevelDataStat(
|
|
getDate(postContentDate.startDate),
|
|
getDate(postContentDate.endDate)
|
|
);
|
|
setPostCount(getTableNumber(10, res?.data?.data));
|
|
}
|
|
|
|
const getTableNumber = (limit: number, data: any) => {
|
|
if (data) {
|
|
const startIndex = limit * (page - 1);
|
|
let iterate = 0;
|
|
const newData = data.map((value: any) => {
|
|
iterate++;
|
|
value.no = startIndex + iterate;
|
|
return value;
|
|
});
|
|
return newData;
|
|
}
|
|
};
|
|
|
|
const getMonthYear = (date: any) => {
|
|
return date.month + " " + date.year;
|
|
};
|
|
const getMonthYearName = (date: any) => {
|
|
const newDate = new Date(date);
|
|
|
|
const months = [
|
|
"Januari",
|
|
"Februari",
|
|
"Maret",
|
|
"April",
|
|
"Mei",
|
|
"Juni",
|
|
"Juli",
|
|
"Agustus",
|
|
"September",
|
|
"Oktober",
|
|
"November",
|
|
"Desember",
|
|
];
|
|
const year = newDate.getFullYear();
|
|
|
|
const month = months[newDate.getMonth()];
|
|
return month + " " + year;
|
|
};
|
|
|
|
return (
|
|
<div className="px-2 lg:px-4 py-4 flex justify-center">
|
|
<div className="w-full flex flex-col gap-6">
|
|
{/* <div className="flex flex-row justify-between border-b-2">
|
|
<div className="flex flex-col gap-1 py-2">
|
|
<h1 className="font-bold text-[25px]">Dashboard</h1>
|
|
<p className="text-[14px]">Dashboard</p>
|
|
</div>
|
|
<span className="flex items-center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24">
|
|
<path fill="currentColor" d="M13 9V3h8v6zM3 13V3h8v10zm10 8V11h8v10zM3 21v-6h8v6z" />
|
|
</svg>
|
|
</span>
|
|
</div> */}
|
|
<div className="w-full flex flex-col lg:flex-row gap-6 justify-center">
|
|
<div className="px-4 lg:px-8 py-4 justify-between w-full lg:w-[35%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col rounded-lg">
|
|
<div className="flex justify-between w-full items-center">
|
|
<div className="flex flex-col gap-2">
|
|
<p className="font-bold text-xl ">{fullname}</p>
|
|
<p>{username}</p>
|
|
</div>
|
|
<DashboardUserIcon size={78} />
|
|
</div>
|
|
<div className="flex flex-row gap-5">
|
|
<p className="text-lg font-semibold">
|
|
{summary?.totalToday} Post{" "}
|
|
<span className="text-sm font-normal">Hari ini</span>
|
|
</p>
|
|
<p className="text-lg font-semibold">
|
|
{summary?.totalThisWeek} Post{" "}
|
|
<span className="text-sm font-normal">Minggu ini </span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="lg:w-[20%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
|
|
<div className="h-1/2 flex items-center justify-center">
|
|
<DashboardSpeecIcon />
|
|
</div>
|
|
<div className="">Total post</div>
|
|
<div className="font-semibold text-lg">{summary?.totalAll}</div>
|
|
</div>
|
|
<div className="w-full lg:w-[15%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
|
|
<div className="h-1/2 flex items-center justify-center">
|
|
<DashboardConnectIcon />
|
|
</div>
|
|
<div className="">Total views</div>
|
|
<div className="font-semibold text-lg">{summary?.totalViews}</div>
|
|
</div>
|
|
<div className="w-full lg:w-[15%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
|
|
<div className="h-1/2 flex items-center justify-center">
|
|
<DashboardShareIcon />
|
|
</div>
|
|
<div className="">Total share</div>
|
|
<div className="font-semibold text-lg">{summary?.totalShares}</div>
|
|
</div>
|
|
<div className="w-full lg:w-[15%] h-[160px] shadow-md bg-white dark:bg-[#18181b] flex flex-col justify-center items-center rounded-lg">
|
|
<div className="h-1/2 flex items-center justify-center">
|
|
<DashboardCommentIcon size={50} />
|
|
</div>
|
|
<div className="">Total comment</div>
|
|
<div className="font-semibold text-lg">
|
|
{summary?.totalComments}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="w-full flex flex-col lg:flex-row gap-6 justify-center ">
|
|
<div className="border-1 shadow-sm w-screen rounded-lg lg:w-[55%] p-6 flex flex-col text-xs lg:text-sm">
|
|
<div className="flex justify-between mb-4 items-center">
|
|
<p className="font-semibold">
|
|
Rekapitulasi Post Berita Polda/Polres Pada Website
|
|
</p>
|
|
<div className="w-[220px] flex flex-row gap-2 justify-between font-semibold">
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<a className="cursor-pointer text-sm font-medium">
|
|
{convertDateFormatNoTime(postContentDate.startDate)}
|
|
</a>
|
|
</PopoverTrigger>
|
|
|
|
<PopoverContent className="w-auto p-0 bg-transparent border-none shadow-none">
|
|
<DatePicker
|
|
selected={postContentDate.startDate}
|
|
onChange={(date: Date | null) => {
|
|
if (date) {
|
|
setPostContentDate((prev) => ({
|
|
...prev,
|
|
startDate: date,
|
|
}));
|
|
}
|
|
}}
|
|
maxDate={postContentDate.endDate}
|
|
inline
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
-
|
|
<Popover>
|
|
<PopoverTrigger>
|
|
<a className="cursor-pointer ">
|
|
{convertDateFormatNoTime(postContentDate.endDate)}
|
|
</a>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="bg-transparent">
|
|
<DatePicker
|
|
selected={postContentDate.endDate}
|
|
onChange={(date: Date | null) => {
|
|
if (date) {
|
|
setPostContentDate((prev) => ({
|
|
...prev,
|
|
endDateDate: date,
|
|
}));
|
|
}
|
|
}}
|
|
maxDate={postContentDate.endDate}
|
|
inline
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-row border-b-1 gap-1 py-1">
|
|
<div className="w-[5%]">NO</div>
|
|
<div className="w-[50%] lg:w-[70%]">POLDA/POLRES</div>
|
|
<div className="w-[45%] lg:w-[25%] text-right">
|
|
JUMLAH POST BERITA
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col gap-1 lg:h-[500px] overflow-y-auto">
|
|
{postCount?.map((list) => (
|
|
<div
|
|
key={list.userLevelId}
|
|
className="flex flex-row border-b-1 gap-1 py-1"
|
|
>
|
|
<div className="w-[5%]">{list?.no}</div>
|
|
<div className="w-[85%]">{list?.userLevelName}</div>
|
|
<div
|
|
className={`w-[10%] text-center ${
|
|
list?.totalArticle === 0 && "bg-red-600 text-white"
|
|
}`}
|
|
>
|
|
{list?.totalArticle}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col w-full lg:w-[45%] gap-6 shadow-md bg-white dark:bg-[#18181b] rounded-lg p-8 text-sm">
|
|
<div className="flex justify-between font-semibold">
|
|
<p>Recent Article</p>
|
|
<Link href="/admin/article/create">
|
|
<Button className="border border-black">Buat Article</Button>
|
|
</Link>
|
|
</div>
|
|
{article?.map((list: any) => (
|
|
<div
|
|
key={list?.id}
|
|
className="flex flex-row gap-2 items-center border-b-2 py-2"
|
|
>
|
|
<Image
|
|
alt="thumbnail"
|
|
src={list?.thumbnailUrl || `/no-image.jpg`}
|
|
width={1920}
|
|
height={1080}
|
|
className="h-[70px] w-[70px] object-cover rounded-lg"
|
|
/>
|
|
<div className="flex flex-col gap-2">
|
|
<p>{textEllipsis(list?.title, 78)}</p>
|
|
<p className="text-xs">
|
|
{convertDateFormat(list?.createdAt)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
<div className="my-2 w-full flex justify-center">
|
|
{/* <Pagination>
|
|
<PaginationContent>
|
|
<PaginationItem>
|
|
<PaginationPrevious onClick={handlePrevious} />
|
|
</PaginationItem>
|
|
|
|
{Array.from({ length: totalPage }).map((_, i) => (
|
|
<PaginationItem key={i}>
|
|
<PaginationLink isActive={page === i + 1} onClick={() => setPage(i + 1)}>
|
|
{i + 1}
|
|
</PaginationLink>
|
|
</PaginationItem>
|
|
))}
|
|
|
|
<PaginationItem>
|
|
<PaginationNext onClick={handleNext} />
|
|
</PaginationItem>
|
|
</PaginationContent>
|
|
</Pagination> */}
|
|
<CustomPagination
|
|
totalPage={totalPage}
|
|
onPageChange={(data) => setPage(data)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="w-full flex flex-col lg:flex-row gap-6 justify-center ">
|
|
<div className="border-1 shadow-sm w-screen rounded-lg lg:w-[55%] p-6 flex flex-col">
|
|
<div className="flex justify-between mb-3">
|
|
<div className="font-semibold flex flex-col">
|
|
Analytics
|
|
<div className="font-normal text-xs text-gray-600 flex flex-row gap-2">
|
|
{/* Checkbox Group */}
|
|
<div className="flex flex-col gap-2 lg:flex-row lg:items-center">
|
|
{options.map((option) => (
|
|
<div
|
|
key={option.value}
|
|
className="flex items-center space-x-2"
|
|
>
|
|
<Checkbox
|
|
id={option.value}
|
|
checked={analyticsView.includes(option.value)}
|
|
onCheckedChange={(checked) =>
|
|
handleChange(option.value, Boolean(checked))
|
|
}
|
|
/>
|
|
<Label htmlFor={option.value}>{option.label}</Label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col lg:flex-row gap-2">
|
|
<Button
|
|
variant={typeDate === "monthly" ? "default" : "outline"}
|
|
onClick={() => setTypeDate("monthly")}
|
|
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg"
|
|
>
|
|
Bulanan
|
|
</Button>
|
|
|
|
<Button
|
|
onClick={() => setTypeDate("weekly")}
|
|
variant={typeDate === "weekly" ? "default" : "outline"}
|
|
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg"
|
|
>
|
|
Mingguan
|
|
</Button>
|
|
|
|
<div className="w-[140px]">
|
|
{/* <Datepicker
|
|
value={startDateValue}
|
|
displayFormat="DD/MM/YYYY"
|
|
asSingle={true}
|
|
useRange={false}
|
|
onChange={(e: any) => setStartDateValue(e)}
|
|
inputClassName="z-50 w-full text-xs lg:text-sm bg-transparent border-1 border-gray-200 px-2 py-[6px] rounded-sm lg:rounded-lg h-[30px] lg:h-[40px] text-gray-600 dark:text-gray-300"
|
|
/> */}
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button className="w-full">
|
|
{getMonthYearName(startDateValue)}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-0 bg-transparent">
|
|
<Calendar
|
|
selected={startDateValue}
|
|
onSelect={(day) => {
|
|
if (day) setStartDateValue(day);
|
|
}}
|
|
mode="single"
|
|
className="rounded-md border"
|
|
/>{" "}
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-row w-full h-full">
|
|
<div className="w-full h-[30vh] lg:h-full text-black">
|
|
<ApexChartColumn
|
|
type={typeDate}
|
|
date={startDateValue.toLocaleString("default", {
|
|
month: "long",
|
|
year: "numeric",
|
|
})}
|
|
view={analyticsView}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col w-full lg:w-[45%] gap-6 shadow-md bg-white dark:bg-[#18181b] rounded-lg p-8 text-xs lg:text-sm">
|
|
<div className="flex justify-between font-semibold">
|
|
<p>Top Pages</p>
|
|
</div>
|
|
<div className="flex flex-row border-b-1">
|
|
<div className="w-[5%]">No</div>
|
|
<div className="w-[85%]">Title</div>
|
|
<div className="w-[10%] text-center">Visits</div>
|
|
</div>
|
|
{topPages?.map((list) => (
|
|
<div key={list.id} className="flex flex-row border-b-1">
|
|
<div className="w-[5%]">{list?.no}</div>
|
|
<div className="w-[85%]">{list?.title}</div>
|
|
<div className="w-[10%] text-center">{list?.viewCount}</div>
|
|
</div>
|
|
))}
|
|
<div className="my-2 w-full flex justify-center">
|
|
{/* <Pagination>
|
|
<PaginationContent>
|
|
<PaginationItem>
|
|
<PaginationPrevious onClick={handlePrevious} />
|
|
</PaginationItem>
|
|
|
|
{Array.from({ length: totalPage }).map((_, i) => (
|
|
<PaginationItem key={i}>
|
|
<PaginationLink isActive={page === i + 1} onClick={() => setPage(i + 1)}>
|
|
{i + 1}
|
|
</PaginationLink>
|
|
</PaginationItem>
|
|
))}
|
|
|
|
<PaginationItem>
|
|
<PaginationNext onClick={handleNext} />
|
|
</PaginationItem>
|
|
</PaginationContent>
|
|
</Pagination> */}
|
|
<CustomPagination
|
|
totalPage={topPagesTotalPage}
|
|
onPageChange={(data) => setPage(data)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|