1193 lines
43 KiB
TypeScript
1193 lines
43 KiB
TypeScript
"use client";
|
|
import {
|
|
DashboardCommentIcon,
|
|
DashboardConnectIcon,
|
|
DashboardShareIcon,
|
|
DashboardSpeecIcon,
|
|
DashboardTopLeftPointIcon,
|
|
DashboardUserIcon,
|
|
} from "@/components/icons/dashboard-icon";
|
|
import { Submenu1Icon } from "@/components/icons/sidebar-icon";
|
|
import {
|
|
Accordion,
|
|
AccordionItem,
|
|
Button,
|
|
Calendar,
|
|
Checkbox,
|
|
CheckboxGroup,
|
|
CheckboxIcon,
|
|
DateRangePicker,
|
|
Image,
|
|
Modal,
|
|
ModalBody,
|
|
ModalContent,
|
|
ModalHeader,
|
|
Pagination,
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
Select,
|
|
SelectItem,
|
|
SelectSection,
|
|
Skeleton,
|
|
useDisclosure,
|
|
} from "@heroui/react";
|
|
import ApexChartColumn from "./chart/column-chart";
|
|
import ApexChartDonut from "./chart/donut-chart";
|
|
import ApexChartLineArea from "./chart/line-area-chart";
|
|
import Cookies from "js-cookie";
|
|
import Link from "next/link";
|
|
import { Fragment, useEffect, useState } from "react";
|
|
import {
|
|
getListArticle,
|
|
getListArticleAdminPage,
|
|
getRecapArticleData,
|
|
getStatisticSummary,
|
|
getTopArticles,
|
|
getUserLevelDataStat,
|
|
} from "@/services/article";
|
|
import { Article } from "@/types/globals";
|
|
import {
|
|
convertDateFormat,
|
|
convertDateFormatNoTime,
|
|
convertDateFormatNoTimeV2,
|
|
textEllipsis,
|
|
} from "@/utils/global";
|
|
import {
|
|
parseDate,
|
|
getLocalTimeZone,
|
|
parseZonedDateTime,
|
|
parseAbsoluteToLocal,
|
|
} from "@internationalized/date";
|
|
import { Input } from "@heroui/input";
|
|
import {
|
|
ChevronLeftIcon,
|
|
ChevronRightIcon,
|
|
EyeIconMdi,
|
|
SearchIcons,
|
|
} from "@/components/icons";
|
|
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;
|
|
createdAt: string;
|
|
};
|
|
|
|
interface TopPages {
|
|
id: number;
|
|
no: number;
|
|
title: string;
|
|
viewCount: number;
|
|
}
|
|
|
|
const colors = [
|
|
"#38bdf8",
|
|
"#155e75",
|
|
"#0e7490",
|
|
"#0284c7",
|
|
"#0ea5e9",
|
|
"#38bdf8",
|
|
"#7dd3fc",
|
|
];
|
|
|
|
interface PostCount {
|
|
userLevelId: number;
|
|
no: number;
|
|
userLevelName: string;
|
|
totalArticle: number;
|
|
}
|
|
|
|
const months = [
|
|
"Jan",
|
|
"Feb",
|
|
"Mar",
|
|
"Apr",
|
|
"May",
|
|
"Jun",
|
|
"Jul",
|
|
"Aug",
|
|
"Sep",
|
|
"Oct",
|
|
"Nov",
|
|
"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();
|
|
|
|
const username = Cookies.get("username");
|
|
const fullname = Cookies.get("ufne");
|
|
const [page, setPage] = useState(1);
|
|
const [totalPage, setTotalPage] = useState(1);
|
|
const [topPagespage, setTopPagesPage] = useState(1);
|
|
const [topPagesTotalPage, setTopPagesTotalPage] = useState(1);
|
|
const [article, setArticle] = useState<ArticleData[]>([]);
|
|
const [selectedCategory, setSelectedCategory] = useState("polda");
|
|
const [analyticsView, setAnalyticView] = useState<string[]>([
|
|
"comment",
|
|
"view",
|
|
"share",
|
|
]);
|
|
const [startDateValue, setStartDateValue] = useState(
|
|
parseDate(convertDateFormatNoTimeV2(new Date()))
|
|
);
|
|
|
|
const [postContentDate, setPostContentDate] = useState({
|
|
start: parseZonedDateTime(
|
|
`${convertDateFormatNoTimeV2(
|
|
new Date(new Date().setDate(new Date().getDate() - 7))
|
|
)}T00:00[Asia/Jakarta]`
|
|
),
|
|
end: parseZonedDateTime(
|
|
`${convertDateFormatNoTimeV2(new Date())}T23:59[Asia/Jakarta]`
|
|
),
|
|
});
|
|
|
|
const [topContentDate, setTopContentDate] = useState({
|
|
startDate: parseDate(
|
|
convertDateFormatNoTimeV2(
|
|
new Date(new Date().setDate(new Date().getDate() - 7))
|
|
)
|
|
),
|
|
endDate: parseDate(convertDateFormatNoTimeV2(new Date())),
|
|
});
|
|
|
|
const [summary, setSummary] = useState<any>();
|
|
|
|
const [topPages, setTopPages] = useState<TopPages[]>([]);
|
|
const [postCount, setPostCount] = useState<PostCount[]>([]);
|
|
const [polresData, setPolresData] = useState<any>({});
|
|
const [selectedAccordion, setSelectedAccordion] = useState<any>(new Set([]));
|
|
const [recapArticleList, setRecapArticleList] = useState<any>([]);
|
|
const [selectedUnit, setSelectedUnit] = useState<{
|
|
id: number;
|
|
name: string;
|
|
}>();
|
|
const roleId = Cookies.get("urie");
|
|
const today = new Date();
|
|
|
|
const [recapArticlePage, setRecapArticlePage] = useState(1);
|
|
const [recapArticleTotalPage, setRecapArticleTotalPage] = useState(1);
|
|
|
|
const [typeDate, setTypeDate] = useState("daily");
|
|
const [year, setYear] = useState(today.getFullYear());
|
|
const [selectedMonth, setSelectedMonth] = useState<Date | null>(today);
|
|
const [viewsDailyDate, setViewsDailyDate] = useState({
|
|
start: parseDate(
|
|
convertDateFormatNoTimeV2(
|
|
new Date(new Date().setDate(new Date().getDate() - 7))
|
|
)
|
|
),
|
|
end: parseDate(convertDateFormatNoTimeV2(today)),
|
|
});
|
|
|
|
const [typeDateVisitor, setTypeDateVisitor] = useState("daily");
|
|
const [visitorYear, setVisitorYear] = useState(today.getFullYear());
|
|
const [visitorSelectedMonth, setVisitorSelectedMonth] = useState<Date | null>(
|
|
today
|
|
);
|
|
const [visitorDailyDate, setVisitorDailyDate] = useState({
|
|
start: parseDate(
|
|
convertDateFormatNoTimeV2(
|
|
new Date(new Date().setDate(new Date().getDate() - 7))
|
|
)
|
|
),
|
|
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() - 7))
|
|
)
|
|
),
|
|
end: parseDate(convertDateFormatNoTimeV2(today)),
|
|
});
|
|
|
|
const handleMonthClick = (monthIndex: number) => {
|
|
setSelectedMonth(new Date(year, monthIndex, 1));
|
|
};
|
|
|
|
const handleMonthClickVisitor = (monthIndex: number) => {
|
|
setVisitorSelectedMonth(new Date(visitorYear, monthIndex, 1));
|
|
};
|
|
const handleMonthClickUsers = (monthIndex: number) => {
|
|
setUsersSelectedMonth(new Date(usersYear, monthIndex, 1));
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchSummary();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
initState();
|
|
}, [page]);
|
|
|
|
async function initState() {
|
|
const req = {
|
|
limit: "4",
|
|
page: page,
|
|
search: "",
|
|
sort: "desc",
|
|
};
|
|
const res = await getListArticleAdminPage(req);
|
|
setArticle(res.data?.data);
|
|
setTotalPage(res?.data?.meta?.totalPage);
|
|
}
|
|
|
|
async function fetchSummary() {
|
|
const res = await getStatisticSummary();
|
|
setSummary(res?.data?.data);
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchTopPages();
|
|
}, [topPagespage, topContentDate]);
|
|
|
|
const getDate = (data: any) => {
|
|
if (data === null) {
|
|
return "";
|
|
} else {
|
|
return `${data.year}-${data.month < 10 ? `0${data.month}` : data.month}-${
|
|
data.day < 10 ? `0${data.day}` : data.day
|
|
}`;
|
|
}
|
|
};
|
|
|
|
async function fetchTopPages() {
|
|
const req = {
|
|
limit: "10",
|
|
page: topPagespage,
|
|
search: "",
|
|
sort: "desc",
|
|
startDate: getDate(topContentDate.startDate),
|
|
endDate: getDate(topContentDate.endDate),
|
|
};
|
|
const res = await getTopArticles(req);
|
|
setTopPages(getTableNumber(10, res.data?.data, "topPages"));
|
|
setTopPagesTotalPage(res?.data?.meta?.totalPage);
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchPostCount();
|
|
}, [selectedCategory]);
|
|
|
|
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.start),
|
|
getDate(postContentDate.end),
|
|
`${getTime(postContentDate.start.hour)}:${getTime(
|
|
postContentDate.start.minute
|
|
)}:00`,
|
|
`${getTime(postContentDate.end.hour)}:${getTime(
|
|
postContentDate.end.minute
|
|
)}:00`,
|
|
selectedCategory === "polda" ? "" : selectedCategory
|
|
);
|
|
setPostCount(getTableNumberStats(res?.data?.data));
|
|
}
|
|
|
|
const getTableNumber = (limit: number, data: any, type: string) => {
|
|
if (data) {
|
|
const startIndex =
|
|
limit * (type == "topPages" ? topPagespage - 1 : recapArticlePage - 1);
|
|
let iterate = 0;
|
|
const newData = data.map((value: any) => {
|
|
iterate++;
|
|
value.no = startIndex + iterate;
|
|
return value;
|
|
});
|
|
return newData;
|
|
}
|
|
};
|
|
|
|
const getTableNumberStats = (data: any) => {
|
|
if (data) {
|
|
let iterate = 0;
|
|
const newData = data
|
|
.filter(
|
|
(value: any) =>
|
|
value.userLevelName !== "SATWIL" && value.userLevelName !== "SATKER"
|
|
)
|
|
.map((value: any) => {
|
|
iterate++;
|
|
value.no = iterate;
|
|
return value;
|
|
});
|
|
return newData;
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
const temp = Array.from(selectedAccordion);
|
|
console.log("selecette", temp);
|
|
if (temp.length > 0) {
|
|
for (const element of temp) {
|
|
getPolresData(Number(element));
|
|
}
|
|
}
|
|
}, [postContentDate]);
|
|
|
|
const getTime = (time: number) => {
|
|
return time < 10 ? `0${time}` : String(time);
|
|
};
|
|
|
|
const getPolresData = async (id: number) => {
|
|
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.start),
|
|
getDate(postContentDate.end),
|
|
`${getTime(postContentDate.start.hour)}:${getTime(
|
|
postContentDate.start.minute
|
|
)}:00`,
|
|
`${getTime(postContentDate.end.hour)}:${getTime(
|
|
postContentDate.end.minute
|
|
)}:00`,
|
|
"",
|
|
id
|
|
);
|
|
const polresNowData = getTableNumberStats(res?.data?.data);
|
|
|
|
setPolresData((prev: any) => ({
|
|
...prev,
|
|
[id]: polresNowData,
|
|
}));
|
|
};
|
|
|
|
const handleSelectionChange = (e: any) => {
|
|
const prev = selectedAccordion;
|
|
const current = e;
|
|
|
|
const currentArray = Array.from(current);
|
|
|
|
const added = Number(currentArray.filter((item) => !prev.has(item))[0]);
|
|
|
|
setSelectedAccordion(current);
|
|
|
|
if (added) {
|
|
getPolresData(added);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
getRecapArticle();
|
|
}, [recapArticlePage]);
|
|
|
|
const getRecapArticle = async (userLevelId?: number) => {
|
|
const req = {
|
|
id: userLevelId || selectedUnit?.id,
|
|
page: recapArticlePage,
|
|
startDate: getDate(postContentDate.start),
|
|
endDate: getDate(postContentDate.end),
|
|
startTime: `${getTime(postContentDate.start.hour)}:${getTime(
|
|
postContentDate.start.minute
|
|
)}:00`,
|
|
endTime: `${getTime(postContentDate.end.hour)}:${getTime(
|
|
postContentDate.end.minute
|
|
)}:00`,
|
|
};
|
|
const res = await getRecapArticleData(req);
|
|
setRecapArticleList(getTableNumber(10, res.data?.data, "recapArticle"));
|
|
setRecapArticleTotalPage(res?.data?.meta?.totalPage);
|
|
};
|
|
|
|
const openModalArticle = async (userLevelId: number) => {
|
|
setRecapArticlePage(1);
|
|
getRecapArticle(userLevelId);
|
|
onOpen();
|
|
};
|
|
|
|
return (
|
|
<div className="px-2 lg:p-8 flex justify-center">
|
|
<div className="w-full flex flex-col gap-6">
|
|
<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-[37%] 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 flex-col md:flex-row md:justify-between mb-4 items-center gap-2 md:gap-0">
|
|
<p className="font-semibold self-start">
|
|
Rekapitulasi Post Berita Polda/Polres Pada Website
|
|
</p>
|
|
<DateRangePicker
|
|
hideTimeZone
|
|
selectorButtonPlacement="start"
|
|
value={postContentDate}
|
|
onChange={(e) => {
|
|
e && setPostContentDate(e);
|
|
}}
|
|
label="Tanggal dan Waktu"
|
|
visibleMonths={2}
|
|
hourCycle={24}
|
|
className="w-fit text-sm self-end"
|
|
classNames={{
|
|
timeInputLabel: "text-xs",
|
|
innerWrapper: "text-xs",
|
|
inputWrapper: "border-1",
|
|
}}
|
|
size="sm"
|
|
variant="bordered"
|
|
endContent={
|
|
<a
|
|
className="cursor-pointer hover:text-black"
|
|
onClick={() => fetchPostCount()}
|
|
>
|
|
<SearchIcons />
|
|
</a>
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-row border-b-1 gap-1 py-1 items-center">
|
|
<div className="w-[5%]">NO</div>
|
|
<div className="w-[50%] lg:w-[70%]">
|
|
{Number(roleId) < 3 ? (
|
|
<Select
|
|
variant="underlined"
|
|
className="max-w-xs border-none"
|
|
label=""
|
|
selectedKeys={[selectedCategory]}
|
|
classNames={{
|
|
innerWrapper: "!border-none",
|
|
mainWrapper: "!border-none",
|
|
trigger: "border-none",
|
|
}}
|
|
onChange={(e) =>
|
|
e.target.value !== ""
|
|
? setSelectedCategory(e.target.value)
|
|
: ""
|
|
}
|
|
>
|
|
<SelectItem key="polda">POLDA</SelectItem>
|
|
<SelectItem key="polres">POLRES</SelectItem>
|
|
<SelectItem key="satker">SATKER MABES</SelectItem>
|
|
</Select>
|
|
) : (
|
|
"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">
|
|
{roleId && Number(roleId) < 3 && selectedCategory == "polda" ? (
|
|
<Accordion
|
|
selectedKeys={selectedAccordion}
|
|
onSelectionChange={(e) => handleSelectionChange(e)}
|
|
selectionMode="multiple"
|
|
className="w-full"
|
|
>
|
|
{postCount?.map((list) => (
|
|
<AccordionItem
|
|
key={list.userLevelId}
|
|
aria-label={list.userLevelName}
|
|
title={
|
|
<div
|
|
key={list.userLevelId}
|
|
className="flex flex-row 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>
|
|
}
|
|
>
|
|
{polresData[list?.userLevelId] ? (
|
|
polresData[list?.userLevelId]?.map(
|
|
(child: any, index: number) => (
|
|
<div
|
|
key={child.userLevelId}
|
|
className={`flex flex-row gap-1 py-1 ${
|
|
index ===
|
|
polresData[list?.userLevelId].length - 1
|
|
? ""
|
|
: "border-b-1"
|
|
}`}
|
|
>
|
|
<div className="w-[5%]"></div>
|
|
<div className="w-[85%]">
|
|
{child?.userLevelName}
|
|
</div>
|
|
<a
|
|
onClick={() => {
|
|
if (list?.totalArticle !== 0) {
|
|
setSelectedUnit({
|
|
name: child?.userLevelName,
|
|
id: child.userLevelId,
|
|
});
|
|
openModalArticle(child.userLevelId);
|
|
}
|
|
}}
|
|
className={`w-[10%] text-start cursor-pointer flex flex-row gap-1 items-center justify-end ${
|
|
list?.totalArticle === 0 &&
|
|
"bg-red-600 text-white cursor-default "
|
|
}`}
|
|
>
|
|
{child?.totalArticle}
|
|
<EyeIconMdi size={12} />
|
|
</a>
|
|
</div>
|
|
)
|
|
)
|
|
) : (
|
|
<Skeleton className="h-3 w-full rounded-lg" />
|
|
)}
|
|
</AccordionItem>
|
|
))}
|
|
</Accordion>
|
|
) : (
|
|
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"
|
|
}`}
|
|
>
|
|
<a
|
|
onClick={() => {
|
|
if (list?.totalArticle !== 0) {
|
|
setSelectedUnit({
|
|
name: list?.userLevelName,
|
|
id: list.userLevelId,
|
|
});
|
|
openModalArticle(list.userLevelId);
|
|
}
|
|
}}
|
|
className={`w-[10%] text-start cursor-pointer flex flex-row gap-1 items-center justify-end ${
|
|
list?.totalArticle === 0 &&
|
|
"bg-red-600 text-white cursor-default "
|
|
}`}
|
|
>
|
|
{list?.totalArticle}
|
|
<EyeIconMdi />
|
|
</a>
|
|
</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-xs lg:text-sm">
|
|
<div className="flex justify-between font-semibold">
|
|
<p>Top Pages</p>
|
|
<div className="w-[220px] flex flex-row gap-2 justify-between font-semibold">
|
|
<Popover
|
|
placement="bottom"
|
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
|
>
|
|
<PopoverTrigger>
|
|
<a className="cursor-pointer">
|
|
{convertDateFormatNoTime(topContentDate.startDate)}
|
|
</a>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="bg-transparent">
|
|
<Calendar
|
|
value={topContentDate.startDate}
|
|
onChange={(e) =>
|
|
setTopContentDate({
|
|
startDate: e,
|
|
endDate: topContentDate.endDate,
|
|
})
|
|
}
|
|
maxValue={topContentDate.endDate}
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
-
|
|
<Popover
|
|
placement="bottom"
|
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
|
>
|
|
<PopoverTrigger>
|
|
<a className="cursor-pointer ">
|
|
{convertDateFormatNoTime(topContentDate.endDate)}
|
|
</a>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="bg-transparent">
|
|
<Calendar
|
|
value={topContentDate.endDate}
|
|
onChange={(e) =>
|
|
setTopContentDate({
|
|
startDate: topContentDate.startDate,
|
|
endDate: e,
|
|
})
|
|
}
|
|
minValue={topContentDate.startDate}
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
</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 || topPages?.length < 1) && (
|
|
<div className="flex justify-center items-center">
|
|
Tidak ada Data
|
|
</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>
|
|
))}
|
|
{topPages?.length > 0 && (
|
|
<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={topPagespage}
|
|
total={topPagesTotalPage}
|
|
onChange={(page) => setTopPagesPage(page)}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<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
|
|
<div className="font-normal text-xs text-gray-600 flex flex-row gap-2">
|
|
<CheckboxGroup
|
|
label=""
|
|
value={analyticsView}
|
|
orientation="vertical"
|
|
onValueChange={setAnalyticView}
|
|
className="lg:hidden"
|
|
>
|
|
<Checkbox size="sm" value="comment">
|
|
Comment
|
|
</Checkbox>
|
|
<Checkbox size="sm" value="view" color="success">
|
|
View
|
|
</Checkbox>
|
|
<Checkbox size="sm" value="share" color="warning">
|
|
Share
|
|
</Checkbox>
|
|
</CheckboxGroup>
|
|
<CheckboxGroup
|
|
label=""
|
|
value={analyticsView}
|
|
orientation="horizontal"
|
|
onValueChange={setAnalyticView}
|
|
className="hidden lg:block"
|
|
>
|
|
<Checkbox size="sm" value="comment">
|
|
Comment
|
|
</Checkbox>
|
|
<Checkbox size="sm" value="view" color="success">
|
|
View
|
|
</Checkbox>
|
|
<Checkbox size="sm" value="share" color="warning">
|
|
Share
|
|
</Checkbox>
|
|
</CheckboxGroup>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col lg:flex-row gap-2">
|
|
<Select
|
|
className="w-full md:w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px]"
|
|
label=""
|
|
labelPlacement="outside"
|
|
selectedKeys={[typeDate]}
|
|
onChange={(e) =>
|
|
e.target.value !== "" && setTypeDate(e.target.value)
|
|
}
|
|
>
|
|
<SelectItem key="monthly">Bulanan</SelectItem>
|
|
<SelectItem key="daily">Harian</SelectItem>
|
|
</Select>
|
|
|
|
{typeDate === "monthly" ? (
|
|
<Popover
|
|
placement="bottom"
|
|
showArrow={true}
|
|
className="w-full"
|
|
>
|
|
<PopoverTrigger>
|
|
<Button className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg">
|
|
{" "}
|
|
{selectedMonth
|
|
? format(selectedMonth, "MMMM yyyy")
|
|
: "Pilih Bulan"}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-4 w-[200px]">
|
|
<div className="flex items-center justify-between mb-2 px-1 w-full">
|
|
<button
|
|
className="text-gray-500 hover:text-black"
|
|
onClick={() => setYear((prev) => prev - 1)}
|
|
>
|
|
<ChevronLeftIcon />
|
|
</button>
|
|
<span className="font-semibold text-center">
|
|
{year}
|
|
</span>
|
|
<button
|
|
className="text-gray-500 hover:text-black"
|
|
onClick={() => setYear((prev) => prev + 1)}
|
|
>
|
|
<ChevronRightIcon />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-2 w-full">
|
|
{months.map((month, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => handleMonthClick(idx)}
|
|
className={`py-1 rounded-md text-sm transition-colors ${
|
|
selectedMonth &&
|
|
selectedMonth.getMonth() === idx &&
|
|
selectedMonth.getFullYear() === year
|
|
? "bg-blue-500 text-white"
|
|
: "hover:bg-gray-200 text-gray-700"
|
|
}`}
|
|
>
|
|
{month}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
) : (
|
|
<div className="w-[220px]">
|
|
<DateRangePicker
|
|
className="h-[40px]"
|
|
value={viewsDailyDate}
|
|
onChange={(e) => e !== null && setViewsDailyDate(e)}
|
|
label=""
|
|
/>
|
|
</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={`${
|
|
convertDateFormatNoTimeV2(String(selectedMonth)).split(
|
|
"-"
|
|
)[1]
|
|
} ${
|
|
convertDateFormatNoTimeV2(String(selectedMonth)).split(
|
|
"-"
|
|
)[0]
|
|
}`}
|
|
view={analyticsView}
|
|
range={viewsDailyDate}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<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">
|
|
<Select
|
|
className="w-full md:w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px]"
|
|
label=""
|
|
labelPlacement="outside"
|
|
selectedKeys={[typeDateUsers]}
|
|
onChange={(e) =>
|
|
e.target.value !== "" && setTypeDateUsers(e.target.value)
|
|
}
|
|
>
|
|
<SelectItem key="monthly">Bulanan</SelectItem>
|
|
<SelectItem key="daily">Harian</SelectItem>
|
|
</Select>
|
|
|
|
{typeDateUsers === "monthly" ? (
|
|
<Popover
|
|
placement="bottom"
|
|
showArrow={true}
|
|
className="w-full"
|
|
>
|
|
<PopoverTrigger>
|
|
<Button className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg">
|
|
{" "}
|
|
{usersSelectedMonth
|
|
? format(usersSelectedMonth, "MMMM yyyy")
|
|
: "Pilih Bulan"}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-4 w-[200px]">
|
|
<div className="flex items-center justify-between mb-2 px-1 w-full">
|
|
<button
|
|
className="text-gray-500 hover:text-black"
|
|
onClick={() => setUsersYear((prev) => prev - 1)}
|
|
>
|
|
<ChevronLeftIcon />
|
|
</button>
|
|
<span className="font-semibold text-center">
|
|
{year}
|
|
</span>
|
|
<button
|
|
className="text-gray-500 hover:text-black"
|
|
onClick={() => setUsersYear((prev) => prev + 1)}
|
|
>
|
|
<ChevronRightIcon />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-2 w-full">
|
|
{months.map((month, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => handleMonthClickUsers(idx)}
|
|
className={`py-1 rounded-md text-sm transition-colors ${
|
|
usersSelectedMonth &&
|
|
usersSelectedMonth.getMonth() === idx &&
|
|
usersSelectedMonth.getFullYear() === year
|
|
? "bg-blue-500 text-white"
|
|
: "hover:bg-gray-200 text-gray-700"
|
|
}`}
|
|
>
|
|
{month}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
) : (
|
|
<div className="w-[220px]">
|
|
<DateRangePicker
|
|
className="h-[40px]"
|
|
value={usersDailyDate}
|
|
onChange={(e) => e !== null && setUsersDailyDate(e)}
|
|
label=""
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-row w-full h-full">
|
|
<div className="w-full h-[30vh] lg:h-full text-black">
|
|
<ApexMultiLineChart
|
|
type={typeDateUsers}
|
|
date={`${
|
|
convertDateFormatNoTimeV2(String(usersSelectedMonth)).split(
|
|
"-"
|
|
)[1]
|
|
} ${
|
|
convertDateFormatNoTimeV2(String(usersSelectedMonth)).split(
|
|
"-"
|
|
)[0]
|
|
}`}
|
|
range={usersDailyDate}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{roleId && Number(roleId) < 3 && (
|
|
<div>
|
|
<IndonesiaMap />
|
|
|
|
<div className="border-1 shadow-sm w-full rounded-lg p-6 flex flex-col mt-4 h-[600px]">
|
|
<div className="flex justify-between mb-3">
|
|
<div className="font-semibold flex flex-col">
|
|
Visitor Analytics
|
|
</div>
|
|
<div className="flex flex-col lg:flex-row gap-2">
|
|
<Select
|
|
className="w-full md:w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px]"
|
|
label=""
|
|
labelPlacement="outside"
|
|
selectedKeys={[typeDateVisitor]}
|
|
onChange={(e) =>
|
|
e.target.value !== "" &&
|
|
setTypeDateVisitor(e.target.value)
|
|
}
|
|
>
|
|
<SelectItem key="monthly">Bulanan</SelectItem>
|
|
<SelectItem key="daily">Harian</SelectItem>
|
|
</Select>
|
|
|
|
{typeDateVisitor === "monthly" ? (
|
|
<Popover
|
|
placement="bottom"
|
|
showArrow={true}
|
|
className="w-full"
|
|
>
|
|
<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")
|
|
: "Pilih Bulan"}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="p-4 w-[200px]">
|
|
<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)}
|
|
>
|
|
<ChevronLeftIcon />
|
|
</button>
|
|
<span className="font-semibold text-center">
|
|
{year}
|
|
</span>
|
|
<button
|
|
className="text-gray-500 hover:text-black"
|
|
onClick={() => setVisitorYear((prev) => prev + 1)}
|
|
>
|
|
<ChevronRightIcon />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-2 w-full">
|
|
{months.map((month, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => handleMonthClickVisitor(idx)}
|
|
className={`py-1 rounded-md text-sm transition-colors ${
|
|
visitorSelectedMonth &&
|
|
visitorSelectedMonth.getMonth() === idx &&
|
|
visitorSelectedMonth.getFullYear() === year
|
|
? "bg-blue-500 text-white"
|
|
: "hover:bg-gray-200 text-gray-700"
|
|
}`}
|
|
>
|
|
{month}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
) : (
|
|
<div className="w-[220px]">
|
|
<DateRangePicker
|
|
className="h-[40px]"
|
|
value={visitorDailyDate}
|
|
onChange={(e) => e !== null && setVisitorDailyDate(e)}
|
|
label=""
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-row w-full h-full">
|
|
<div className="w-full h-[30vh] lg:h-full text-black">
|
|
<ApexChartColumnVisitors
|
|
type={typeDateVisitor}
|
|
date={`${
|
|
convertDateFormatNoTimeV2(
|
|
String(visitorSelectedMonth)
|
|
).split("-")[1]
|
|
} ${
|
|
convertDateFormatNoTimeV2(
|
|
String(visitorSelectedMonth)
|
|
).split("-")[0]
|
|
}`}
|
|
range={visitorDailyDate}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="3xl">
|
|
<ModalContent>
|
|
{(onClose) => (
|
|
<>
|
|
<ModalHeader className="flex flex-col gap-1">
|
|
{selectedUnit?.name} List Content
|
|
</ModalHeader>
|
|
<ModalBody className="flex flex-col gap-2">
|
|
<div className="grid grid-cols-12 text-xs ">
|
|
<div className="col-span-1 font-bold">No</div>
|
|
<div className="col-span-9 text-center font-bold">Judul</div>
|
|
<div className="col-span-2 font-bold ">Dibuat</div>
|
|
<div className="col-span-12 grid grid-cols-12 max-h-[480px] overflow-auto">
|
|
{recapArticleList?.length > 0 &&
|
|
recapArticleList?.map((list: any) => (
|
|
<Fragment key={list.id}>
|
|
<div className="col-span-1 my-2">{list.no}</div>
|
|
<div className="col-span-9 my-2">
|
|
<Link
|
|
href={`/news/detail/${list.id}-${list.slug}`}
|
|
target="_blank"
|
|
className="hover:underline hover:text-primary"
|
|
>
|
|
{textEllipsis(list.title, 80)}
|
|
</Link>
|
|
</div>
|
|
<div className="my-2 col-span-2">
|
|
{convertDateFormat(list?.createdAt)}
|
|
</div>
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{recapArticleList?.length > 0 && (
|
|
<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={recapArticlePage}
|
|
total={recapArticleTotalPage}
|
|
onChange={(page) => setRecapArticlePage(page)}
|
|
/>
|
|
</div>
|
|
)}
|
|
</ModalBody>
|
|
</>
|
|
)}
|
|
</ModalContent>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
}
|