jaecoo-cihampelas/components/main/dashboard/dashboard-container.tsx

652 lines
22 KiB
TypeScript
Raw Normal View History

2025-07-13 07:48:15 +00:00
"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 "react-datepicker/dist/react-datepicker.css";
import { motion } from "framer-motion";
2026-01-07 08:06:07 +00:00
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";
2025-07-13 07:48:15 +00:00
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[]>([]);
2026-01-07 08:06:07 +00:00
const [dialogOpen, setDialogOpen] = useState(false);
const [selectedItem, setSelectedItem] = useState<any>(null);
2025-07-13 07:48:15 +00:00
const options = [
{ label: "Comment", value: "comment" },
{ label: "View", value: "view" },
{ label: "Share", value: "share" },
];
2026-01-07 08:06:07 +00:00
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",
},
];
2025-07-13 07:48:15 +00:00
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">
2026-01-07 08:06:07 +00:00
<div className="pl-3">
<h1 className="text-[#1F6779] text-2xl font-semibold">
Dashboard Utama
</h1>
<p>Ringkasan status aktivitas dan upload anda</p>
</div>
2025-07-13 07:48:15 +00:00
{/* Stats Cards */}
2026-01-07 08:06:07 +00:00
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-8 gap-6">
2025-07-13 07:48:15 +00:00
{/* 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">
2026-01-07 08:06:07 +00:00
<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">
2025-07-13 07:48:15 +00:00
<div className="text-center">
<p className="text-2xl font-bold text-blue-600">
{summary?.totalToday}
</p>
2026-01-07 08:06:07 +00:00
<p className="text-sm text-slate-500">2</p>
2025-07-13 07:48:15 +00:00
</div>
2026-01-07 08:06:07 +00:00
</div> */}
2025-07-13 07:48:15 +00:00
</div>
<div className="p-3 bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl">
2026-01-07 08:06:07 +00:00
<Upload size={50} className="text-black" />
2025-07-13 07:48:15 +00:00
</div>
</div>
</motion.div>
<motion.div
2026-01-07 08:06:07 +00:00
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"
2025-07-13 07:48:15 +00:00
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
2026-01-07 08:06:07 +00:00
transition={{ delay: 0.1 }}
2025-07-13 07:48:15 +00:00
>
2026-01-07 08:06:07 +00:00
<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> */}
2025-07-13 07:48:15 +00:00
</div>
2026-01-07 08:06:07 +00:00
<div className="p-3 bg-gradient-to-br from-yellow-100 to-yellow-100 rounded-xl">
<TimerIcon size={50} className="text-black" />
2025-07-13 07:48:15 +00:00
</div>
</div>
</motion.div>
<motion.div
2026-01-07 08:06:07 +00:00
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"
2025-07-13 07:48:15 +00:00
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
2026-01-07 08:06:07 +00:00
transition={{ delay: 0.1 }}
2025-07-13 07:48:15 +00:00
>
2026-01-07 08:06:07 +00:00
<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> */}
2025-07-13 07:48:15 +00:00
</div>
2026-01-07 08:06:07 +00:00
<div className="p-3 bg-gradient-to-br from-green-100 to-green-100 rounded-xl">
<CheckCircle size={50} className="text-black" />
2025-07-13 07:48:15 +00:00
</div>
</div>
</motion.div>
<motion.div
2026-01-07 08:06:07 +00:00
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"
2025-07-13 07:48:15 +00:00
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
2026-01-07 08:06:07 +00:00
transition={{ delay: 0.1 }}
2025-07-13 07:48:15 +00:00
>
2026-01-07 08:06:07 +00:00
<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> */}
2025-07-13 07:48:15 +00:00
</div>
2026-01-07 08:06:07 +00:00
<div className="p-3 bg-gradient-to-br from-red-100 to-red-100 rounded-xl">
<Blocks size={50} className="text-black" />
2025-07-13 07:48:15 +00:00
</div>
</div>
</motion.div>
2026-01-07 08:06:07 +00:00
</div>
2025-07-13 07:48:15 +00:00
2026-01-07 08:06:07 +00:00
{/* Content Section */}
<div className="grid grid-cols-1 lg:grid-cols-[2fr_1fr] gap-8">
{/* Aktivitas Terakhir */}
2025-07-13 07:48:15 +00:00
<motion.div
2026-01-07 08:06:07 +00:00
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
2025-07-13 07:48:15 +00:00
transition={{ delay: 0.5 }}
>
2026-01-07 08:06:07 +00:00
<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>
2025-07-13 07:48:15 +00:00
</div>
</div>
</motion.div>
2026-01-07 08:06:07 +00:00
{/* Notifikasi */}
2025-07-13 07:48:15 +00:00
<motion.div
2026-01-07 08:06:07 +00:00
initial={{ opacity: 0, x: 20 }}
2025-07-13 07:48:15 +00:00
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.6 }}
>
2026-01-07 08:06:07 +00:00
<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>
2025-07-13 07:48:15 +00:00
</div>
</div>
</motion.div>
2026-01-07 08:06:07 +00:00
{/* Informasi Penting */}
2025-07-13 07:48:15 +00:00
<motion.div
2026-01-07 08:06:07 +00:00
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
2025-07-13 07:48:15 +00:00
transition={{ delay: 0.7 }}
2026-01-07 08:06:07 +00:00
className="lg:col-span-2"
2025-07-13 07:48:15 +00:00
>
2026-01-07 08:06:07 +00:00
<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>
2025-07-13 07:48:15 +00:00
</motion.div>
</div>
</div>
);
}