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

651 lines
22 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}