651 lines
22 KiB
TypeScript
651 lines
22 KiB
TypeScript
"use client";
|
||
import Cookies from "js-cookie";
|
||
import { useEffect, useState } from "react";
|
||
import { Article } from "@/types/globals";
|
||
import {
|
||
getListArticle,
|
||
getStatisticSummary,
|
||
getTopArticles,
|
||
getUserLevelDataStat,
|
||
} from "@/service/article";
|
||
import { Button } from "@/components/ui/button";
|
||
import { motion } from "framer-motion";
|
||
import {
|
||
Blocks,
|
||
Check,
|
||
CheckCircle,
|
||
CheckCircle2,
|
||
Eye,
|
||
Info,
|
||
TimerIcon,
|
||
Upload,
|
||
} from "lucide-react";
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/components/ui/table";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import {
|
||
Dialog,
|
||
DialogClose,
|
||
DialogContent,
|
||
DialogFooter,
|
||
DialogTitle,
|
||
} from "@/components/ui/dialog";
|
||
|
||
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 [dialogOpen, setDialogOpen] = useState(false);
|
||
const [selectedItem, setSelectedItem] = useState<any>(null);
|
||
const options = [
|
||
{ label: "Comment", value: "comment" },
|
||
{ label: "View", value: "view" },
|
||
{ label: "Share", value: "share" },
|
||
];
|
||
|
||
const activities = [
|
||
{
|
||
no: 1,
|
||
tanggal: "10/11/2024",
|
||
jenis: "Banner",
|
||
judul: "New Promo JAECOO",
|
||
status: "Menunggu",
|
||
},
|
||
{
|
||
no: 2,
|
||
tanggal: "10/11/2024",
|
||
jenis: "Agen",
|
||
judul: "Foto Budi Santoso",
|
||
status: "Disetujui",
|
||
},
|
||
{
|
||
no: 3,
|
||
tanggal: "09/11/2024",
|
||
jenis: "Produk",
|
||
judul: "JAECOO J7 AWD Update",
|
||
status: "Disetujui",
|
||
},
|
||
{
|
||
no: 4,
|
||
tanggal: "09/11/2024",
|
||
jenis: "Dealer",
|
||
judul: "Dealer Jakarta Selatan",
|
||
status: "Disetujui",
|
||
},
|
||
{
|
||
no: 5,
|
||
tanggal: "08/11/2024",
|
||
jenis: "Dokumen",
|
||
judul: "Brosur JAECOO J8",
|
||
status: "Ditolak",
|
||
},
|
||
{
|
||
no: 6,
|
||
tanggal: "08/11/2024",
|
||
jenis: "Banner",
|
||
judul: "Hero Banner Akhir Tahun",
|
||
status: "Menunggu",
|
||
},
|
||
];
|
||
|
||
const notifications = [
|
||
{
|
||
icon: "✅",
|
||
text: 'Upload "JAECOO J7 AWD Update" telah disetujui oleh Admin Manager',
|
||
time: "2 jam yang lalu",
|
||
},
|
||
{
|
||
icon: "❌",
|
||
text: 'Upload "Brosur JAECOO J8" ditolak. Alasan: Resolusi gambar terlalu rendah.',
|
||
time: "2 jam yang lalu",
|
||
},
|
||
{
|
||
icon: "✅",
|
||
text: 'Update "Dealer Jakarta Selatan" telah disetujui',
|
||
time: "1 hari yang lalu",
|
||
},
|
||
{
|
||
icon: "✅",
|
||
text: 'Upload "Foto Budi Santoso" telah disetujui',
|
||
time: "1 hari yang lalu",
|
||
},
|
||
{
|
||
icon: "ℹ️",
|
||
text: "Sistem akan maintenance pada Minggu, 12 November 2024 pukul 00.00 - 04.00 WIB",
|
||
time: "1 hari yang lalu",
|
||
},
|
||
];
|
||
|
||
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="space-y-8">
|
||
<div className="pl-3">
|
||
<h1 className="text-[#1F6779] text-2xl font-semibold">
|
||
Dashboard Utama
|
||
</h1>
|
||
<p>Ringkasan status aktivitas dan upload anda</p>
|
||
</div>
|
||
{/* Stats Cards */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-8 gap-6">
|
||
{/* User Profile Card */}
|
||
<motion.div
|
||
className="col-span-1 md:col-span-2 bg-white rounded-2xl shadow-lg border border-slate-200/60 p-6 hover:shadow-xl transition-all duration-300"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.1 }}
|
||
>
|
||
<div className="flex justify-between items-start">
|
||
<div className="space-y-2">
|
||
<h3 className="text-xl font-bold text-slate-800">
|
||
Total Upload Hari ini
|
||
</h3>
|
||
<p className="text-slate-600 text-lg">2</p>
|
||
{/* <div className="flex space-x-6 pt-2">
|
||
<div className="text-center">
|
||
<p className="text-2xl font-bold text-blue-600">
|
||
{summary?.totalToday}
|
||
</p>
|
||
<p className="text-sm text-slate-500">2</p>
|
||
</div>
|
||
</div> */}
|
||
</div>
|
||
<div className="p-3 bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl">
|
||
<Upload size={50} className="text-black" />
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
<motion.div
|
||
className="col-span-1 md:col-span-2 bg-white rounded-2xl shadow-lg border border-slate-200/60 p-6 hover:shadow-xl transition-all duration-300"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.1 }}
|
||
>
|
||
<div className="flex justify-between items-start">
|
||
<div className="space-y-2">
|
||
<h3 className="text-xl font-bold text-slate-800">
|
||
Menunggu Persetujuan
|
||
</h3>
|
||
<p className="text-slate-600 text-lg">2</p>
|
||
{/* <div className="flex space-x-6 pt-2">
|
||
<div className="text-center">
|
||
<p className="text-2xl font-bold text-blue-600">
|
||
{summary?.totalToday}
|
||
</p>
|
||
<p className="text-sm text-slate-500">2</p>
|
||
</div>
|
||
</div> */}
|
||
</div>
|
||
<div className="p-3 bg-gradient-to-br from-yellow-100 to-yellow-100 rounded-xl">
|
||
<TimerIcon size={50} className="text-black" />
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
<motion.div
|
||
className="col-span-1 md:col-span-2 bg-white rounded-2xl shadow-lg border border-slate-200/60 p-6 hover:shadow-xl transition-all duration-300"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.1 }}
|
||
>
|
||
<div className="flex justify-between items-start">
|
||
<div className="space-y-2">
|
||
<h3 className="text-xl font-bold text-slate-800">Disetujui</h3>
|
||
<p className="text-slate-600 text-lg">2</p>
|
||
{/* <div className="flex space-x-6 pt-2">
|
||
<div className="text-center">
|
||
<p className="text-2xl font-bold text-blue-600">
|
||
{summary?.totalToday}
|
||
</p>
|
||
<p className="text-sm text-slate-500">2</p>
|
||
</div>
|
||
</div> */}
|
||
</div>
|
||
<div className="p-3 bg-gradient-to-br from-green-100 to-green-100 rounded-xl">
|
||
<CheckCircle size={50} className="text-black" />
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
<motion.div
|
||
className="col-span-1 md:col-span-2 bg-white rounded-2xl shadow-lg border border-slate-200/60 p-6 hover:shadow-xl transition-all duration-300"
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.1 }}
|
||
>
|
||
<div className="flex justify-between items-start">
|
||
<div className="space-y-2">
|
||
<h3 className="text-xl font-bold text-slate-800">Ditolak</h3>
|
||
<p className="text-slate-600 text-lg">2</p>
|
||
{/* <div className="flex space-x-6 pt-2">
|
||
<div className="text-center">
|
||
<p className="text-2xl font-bold text-blue-600">
|
||
{summary?.totalToday}
|
||
</p>
|
||
<p className="text-sm text-slate-500">2</p>
|
||
</div>
|
||
</div> */}
|
||
</div>
|
||
<div className="p-3 bg-gradient-to-br from-red-100 to-red-100 rounded-xl">
|
||
<Blocks size={50} className="text-black" />
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
|
||
{/* Content Section */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-[2fr_1fr] gap-8">
|
||
{/* Aktivitas Terakhir */}
|
||
<motion.div
|
||
initial={{ opacity: 0, x: -20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
transition={{ delay: 0.5 }}
|
||
>
|
||
<div className="bg-white shadow-xl ">
|
||
<p className=" text-lg p-3 bg-cyan-900 text-white rounded-t-lg">
|
||
Aktivitas Terakhir
|
||
</p>
|
||
|
||
<Table className="p-3">
|
||
<TableHeader className="bg-gradient-to-r from-[#BCD4DF] to-[#BCD4DF]">
|
||
<TableRow>
|
||
<TableHead className="text-[#008080] w-[40px]">No</TableHead>
|
||
<TableHead className="text-[#008080]">Tanggal</TableHead>
|
||
<TableHead className="text-[#008080]">Jenis Konten</TableHead>
|
||
<TableHead className="text-[#008080]">Judul / Nama</TableHead>
|
||
<TableHead className="text-[#008080]">Status</TableHead>
|
||
<TableHead className="text-[#008080] text-center">
|
||
Aksi
|
||
</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{activities.map((item) => (
|
||
<TableRow key={item.no}>
|
||
<TableCell>{item.no}</TableCell>
|
||
<TableCell>{item.tanggal}</TableCell>
|
||
<TableCell>
|
||
<Badge
|
||
variant="secondary"
|
||
className="bg-slate-100 text-slate-700"
|
||
>
|
||
{item.jenis}
|
||
</Badge>
|
||
</TableCell>
|
||
<TableCell className="font-medium">{item.judul}</TableCell>
|
||
<TableCell>
|
||
{item.status === "Disetujui" && (
|
||
<Badge className="bg-green-100 text-green-700">
|
||
Disetujui
|
||
</Badge>
|
||
)}
|
||
{item.status === "Menunggu" && (
|
||
<Badge className="bg-yellow-100 text-yellow-700">
|
||
Menunggu
|
||
</Badge>
|
||
)}
|
||
{item.status === "Ditolak" && (
|
||
<Badge className="bg-red-100 text-red-700">
|
||
Ditolak
|
||
</Badge>
|
||
)}
|
||
</TableCell>
|
||
<TableCell
|
||
onClick={() => {
|
||
setSelectedItem(item);
|
||
setDialogOpen(true);
|
||
}}
|
||
className="text-blue-600 font-medium text-center cursor-pointer hover:underline"
|
||
>
|
||
Lihat
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||
<DialogContent className="max-w-lg rounded-xl p-0 overflow-hidden">
|
||
{selectedItem && (
|
||
<>
|
||
{/* Header */}
|
||
<div className="bg-cyan-900 text-white p-4 flex flex-col items-start justify-between">
|
||
<DialogTitle className="text-lg font-semibold">
|
||
{selectedItem.judul}
|
||
</DialogTitle>
|
||
{selectedItem.status === "Disetujui" && (
|
||
<Badge className="bg-green-100 text-green-700">
|
||
Disetujui
|
||
</Badge>
|
||
)}
|
||
{selectedItem.status === "Menunggu" && (
|
||
<Badge className="bg-yellow-100 text-yellow-700">
|
||
Menunggu
|
||
</Badge>
|
||
)}
|
||
{selectedItem.status === "Ditolak" && (
|
||
<Badge className="bg-red-100 text-red-700">
|
||
Ditolak
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
|
||
{/* Body */}
|
||
<div className="p-5 space-y-4">
|
||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||
<div className="bg-slate-50 p-3 rounded-lg border">
|
||
<p className="text-slate-500">Tanggal Upload</p>
|
||
<p className="font-medium">{selectedItem.tanggal}</p>
|
||
</div>
|
||
<div className="bg-slate-50 p-3 rounded-lg border">
|
||
<p className="text-slate-500">Ukuran File</p>
|
||
<p className="font-medium">2.4 MB</p>
|
||
</div>
|
||
<div className="bg-slate-50 p-3 rounded-lg border">
|
||
<p className="text-slate-500">Diupload Oleh</p>
|
||
<p className="font-medium">Operator1</p>
|
||
</div>
|
||
<div className="bg-slate-50 p-3 rounded-lg border">
|
||
<p className="text-slate-500">Waktu Upload</p>
|
||
<p className="font-medium">14:32 WIB</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="font-medium text-slate-700 mb-2">
|
||
Preview Konten
|
||
</p>
|
||
<div className="border rounded-lg p-6 flex flex-col items-center justify-center bg-slate-50">
|
||
<Eye className="w-10 h-10 text-cyan-800 mb-2" />
|
||
<p className="font-medium text-cyan-900">
|
||
Preview File
|
||
</p>
|
||
<p className="text-sm text-slate-500">
|
||
File: {selectedItem.judul}.jpg
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="font-medium text-slate-700 mb-1">
|
||
Deskripsi
|
||
</p>
|
||
<div className="border rounded-lg p-3 bg-slate-50">
|
||
<p className="text-slate-700">
|
||
Upload {selectedItem.judul}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<p className="font-medium text-slate-700 mb-2">
|
||
Status Timeline
|
||
</p>
|
||
<div className="space-y-2">
|
||
<div className="flex items-start space-x-2">
|
||
<CheckCircle2 className="w-5 h-5 text-green-500 mt-0.5" />
|
||
<div>
|
||
<p className="text-sm font-medium text-slate-800">
|
||
Upload Berhasil
|
||
</p>
|
||
<p className="text-xs text-slate-500">
|
||
10/11/2024 • 14:32 WIB
|
||
</p>
|
||
</div>
|
||
</div>
|
||
{selectedItem.status === "Disetujui" && (
|
||
<div className="flex items-start space-x-2">
|
||
<CheckCircle2 className="w-5 h-5 text-green-500 mt-0.5" />
|
||
<div>
|
||
<p className="text-sm font-medium text-slate-800">
|
||
Disetujui oleh Approver
|
||
</p>
|
||
<p className="text-xs text-slate-500">
|
||
10/11/2024 • 16:45 WIB
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Footer */}
|
||
<DialogFooter className="bg-slate-100 p-4">
|
||
<DialogClose asChild>
|
||
<Button className="bg-slate-300 text-slate-700 hover:bg-slate-400">
|
||
Tutup
|
||
</Button>
|
||
</DialogClose>
|
||
</DialogFooter>
|
||
</>
|
||
)}
|
||
</DialogContent>
|
||
</Dialog>
|
||
<div className="mt-4 text-right p-3">
|
||
<Button
|
||
variant="link"
|
||
className="text-blue-600 font-medium p-3 h-auto"
|
||
>
|
||
Lihat Semua Aktivitas →
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* Notifikasi */}
|
||
<motion.div
|
||
initial={{ opacity: 0, x: 20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
transition={{ delay: 0.6 }}
|
||
>
|
||
<div>
|
||
<p className=" text-lg bg-cyan-900 p-3 rounded-t-lg text-white">
|
||
Notifikasi
|
||
</p>
|
||
<ScrollArea className="h-96 pr-2">
|
||
<div className="">
|
||
{notifications.map((notif, i) => (
|
||
<div
|
||
key={i}
|
||
className="flex items-start space-x-3 border p-2 hover:bg-slate-50 transition"
|
||
>
|
||
<div className="text-xl">{notif.icon}</div>
|
||
<div className="flex-1">
|
||
<p className="text-sm text-slate-700 leading-snug">
|
||
{notif.text}
|
||
</p>
|
||
<p className="text-xs text-slate-400 mt-1">
|
||
{notif.time}
|
||
</p>
|
||
</div>
|
||
<span className="w-2 h-2 bg-blue-500 rounded-full mt-2"></span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</ScrollArea>
|
||
<div className="mt-2 text-right">
|
||
<Button
|
||
variant="link"
|
||
className="text-blue-600 font-medium p-0 h-auto"
|
||
>
|
||
Tandai Semua Dibaca
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* Informasi Penting */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.7 }}
|
||
className="lg:col-span-2"
|
||
>
|
||
<Card className="bg-blue-50 border border-blue-200">
|
||
<CardContent className="flex items-start space-x-3 p-4">
|
||
<Info className="text-blue-600 w-5 h-5 mt-0.5" />
|
||
<div>
|
||
<h4 className="text-blue-800 font-semibold text-sm mb-1">
|
||
Informasi Penting
|
||
</h4>
|
||
<p className="text-blue-700 text-sm">
|
||
Upload yang berstatus <b>"Menunggu"</b> akan direview oleh
|
||
Approver. Pastikan semua konten sudah sesuai panduan sebelum
|
||
upload untuk mempercepat proses approval.
|
||
</p>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|