693 lines
22 KiB
TypeScript
693 lines
22 KiB
TypeScript
"use client";
|
|
|
|
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 {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "@/components/ui/accordion";
|
|
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";
|
|
import { motion } from "framer-motion";
|
|
import { Input } from "@/components/ui/input";
|
|
import {
|
|
SelectTrigger,
|
|
SelectValue,
|
|
SelectContent,
|
|
SelectItem,
|
|
Select,
|
|
} from "@/components/ui/select";
|
|
import { Badge } from "lucide-react";
|
|
|
|
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 [levelName, setLevelName] = useState<string | undefined>();
|
|
useEffect(() => {
|
|
const levelId = Cookies.get("ulne");
|
|
setLevelName(levelId);
|
|
}, []);
|
|
|
|
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[]>([]);
|
|
|
|
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;
|
|
};
|
|
|
|
if (!levelName) return null;
|
|
const AdminDashboard = () => {
|
|
const tasks = [
|
|
{
|
|
id: "1",
|
|
title: "MediaHUB Content Aggregator",
|
|
author: "John Kontributor",
|
|
category: "Product",
|
|
date: "2026-02-13",
|
|
status: "OPEN",
|
|
},
|
|
{
|
|
id: "2",
|
|
title:
|
|
"Mudik Nyaman Bersama Pertamina: Layanan 24 Jam, Motoris, dan Fasilitas Lengkap",
|
|
author: "Jane Kontributor",
|
|
category: "Service",
|
|
date: "2026-02-13",
|
|
status: "OPEN",
|
|
},
|
|
{
|
|
id: "3",
|
|
title: "Artifintel Services Update",
|
|
author: "Alex Approver",
|
|
category: "Event",
|
|
date: "2026-02-13",
|
|
status: "CLOSED",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* HEADER */}
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-slate-800">Admin Dashboard</h1>
|
|
<p className="text-slate-500">
|
|
Review and manage content submissions
|
|
</p>
|
|
</div>
|
|
|
|
{/* STAT CARDS */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{[
|
|
{ title: "Open Tasks", value: 2, color: "bg-yellow-500" },
|
|
{ title: "Closed Tasks", value: 2, color: "bg-green-600" },
|
|
{ title: "Total Submissions", value: 4, color: "bg-blue-600" },
|
|
{ title: "Rejected", value: 7, color: "bg-red-600" },
|
|
].map((card, i) => (
|
|
<div
|
|
key={i}
|
|
className="bg-white rounded-2xl shadow border p-6 flex justify-between items-center"
|
|
>
|
|
<div>
|
|
<p className="text-sm text-slate-500">{card.title}</p>
|
|
<h2 className="text-3xl font-bold text-slate-800 mt-2">
|
|
{card.value}
|
|
</h2>
|
|
</div>
|
|
<div className={`w-12 h-12 rounded-xl ${card.color}`} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* TASK LIST */}
|
|
<div className="bg-white rounded-2xl shadow border p-6 space-y-6">
|
|
{/* Title */}
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-lg font-semibold">
|
|
Task List{" "}
|
|
<span className="ml-2 text-xs bg-amber-100 text-amber-600 px-3 py-1 rounded-full">
|
|
{tasks.length} Tasks
|
|
</span>
|
|
</h2>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<div className="flex flex-wrap gap-4">
|
|
<Input type="date" className="w-[180px]" />
|
|
<Input type="date" className="w-[180px]" />
|
|
<Select>
|
|
<SelectTrigger className="w-[150px]">
|
|
<SelectValue placeholder="Status" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="open">Open</SelectItem>
|
|
<SelectItem value="closed">Closed</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Accordion List */}
|
|
<Accordion type="single" collapsible className="space-y-4">
|
|
{tasks.map((task) => (
|
|
<AccordionItem
|
|
key={task.id}
|
|
value={task.id}
|
|
className="border rounded-xl px-4"
|
|
>
|
|
<AccordionTrigger className="hover:no-underline">
|
|
<div className="flex items-center justify-between w-full">
|
|
<div className="text-left">
|
|
<p className="font-medium text-slate-800">{task.title}</p>
|
|
<p className="text-sm text-slate-500 mt-1">
|
|
{task.author} • {task.category} • {task.date}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<Badge
|
|
className={
|
|
task.status === "OPEN"
|
|
? "bg-yellow-100 text-yellow-600"
|
|
: "bg-green-100 text-green-600"
|
|
}
|
|
>
|
|
{task.status}
|
|
</Badge>
|
|
|
|
{/* Mini Progress */}
|
|
<div className="flex gap-1">
|
|
<div className="w-6 h-1 bg-green-500 rounded" />
|
|
<div className="w-6 h-1 bg-yellow-500 rounded" />
|
|
<div className="w-6 h-1 bg-gray-300 rounded" />
|
|
<div className="w-6 h-1 bg-gray-300 rounded" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AccordionTrigger>
|
|
|
|
<AccordionContent>
|
|
<div className="mt-4 bg-slate-50 rounded-xl border p-6 space-y-6">
|
|
{/* Title */}
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-5 h-5 rounded-full border-2 border-amber-500 flex items-center justify-center">
|
|
<div className="w-2 h-2 bg-amber-500 rounded-full" />
|
|
</div>
|
|
<h3 className="font-semibold text-slate-700">
|
|
Document Flow Status
|
|
</h3>
|
|
</div>
|
|
|
|
{/* Stepper */}
|
|
<div className="relative">
|
|
{/* Line */}
|
|
<div className="absolute top-5 left-0 right-0 h-[2px] bg-slate-200" />
|
|
|
|
<div className="relative grid grid-cols-4 text-center">
|
|
{/* STEP 1 */}
|
|
<div className="flex flex-col items-center">
|
|
<div className="w-10 h-10 rounded-full bg-green-500 flex items-center justify-center text-white">
|
|
✓
|
|
</div>
|
|
<p className="mt-2 text-sm font-medium text-slate-700">
|
|
Submission
|
|
</p>
|
|
<p className="text-xs text-slate-500">
|
|
Feb 13, 2026 09:00
|
|
</p>
|
|
</div>
|
|
|
|
{/* STEP 2 */}
|
|
<div className="flex flex-col items-center">
|
|
<div className="w-10 h-10 rounded-full bg-amber-500 flex items-center justify-center text-white">
|
|
●
|
|
</div>
|
|
<p className="mt-2 text-sm font-medium text-slate-700">
|
|
Technical Review
|
|
</p>
|
|
<p className="text-xs text-slate-500">
|
|
Feb 13, 2026 11:00
|
|
</p>
|
|
</div>
|
|
|
|
{/* STEP 3 */}
|
|
<div className="flex flex-col items-center">
|
|
<div className="w-10 h-10 rounded-full border-2 border-slate-300 bg-white" />
|
|
<p className="mt-2 text-sm font-medium text-slate-400">
|
|
Admin Verification
|
|
</p>
|
|
<p className="text-xs text-slate-400">Waiting</p>
|
|
</div>
|
|
|
|
{/* STEP 4 */}
|
|
<div className="flex flex-col items-center">
|
|
<div className="w-10 h-10 rounded-full border-2 border-slate-300 bg-white" />
|
|
<p className="mt-2 text-sm font-medium text-slate-400">
|
|
Final Approval
|
|
</p>
|
|
<p className="text-xs text-slate-400">Waiting</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
))}
|
|
</Accordion>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ContributorDashboard = () => {
|
|
const stats = [
|
|
{
|
|
title: "Total Content",
|
|
value: 24,
|
|
growth: "+12%",
|
|
iconBg: "bg-blue-600",
|
|
},
|
|
{
|
|
title: "Pending Approval",
|
|
value: 8,
|
|
growth: "+3",
|
|
iconBg: "bg-yellow-500",
|
|
},
|
|
{
|
|
title: "Published",
|
|
value: 16,
|
|
growth: "+5",
|
|
iconBg: "bg-green-600",
|
|
},
|
|
{
|
|
title: "Rejected",
|
|
value: 2,
|
|
growth: "-1",
|
|
iconBg: "bg-red-600",
|
|
},
|
|
];
|
|
|
|
const contents = [
|
|
{
|
|
title: "MediaHUB Content Aggregator",
|
|
category: "Product",
|
|
time: "2 hours ago",
|
|
status: "Pending",
|
|
},
|
|
{
|
|
title:
|
|
"Mudik Nyaman Bersama Pertamina: Layanan 24 Jam, Motoris, dan Fasilitas Lengkap",
|
|
category: "News",
|
|
time: "5 hours ago",
|
|
status: "Approved",
|
|
},
|
|
{
|
|
title: "Artifintel Services Update",
|
|
category: "Service",
|
|
time: "1 day ago",
|
|
status: "Pending",
|
|
},
|
|
{
|
|
title:
|
|
"Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat Luar Biasa",
|
|
category: "Pop Up",
|
|
time: "1 day ago",
|
|
status: "Draft",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* Header */}
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-slate-800">Dashboard</h1>
|
|
</div>
|
|
|
|
{/* ================= STAT CARDS ================= */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{stats.map((card, i) => (
|
|
<div
|
|
key={i}
|
|
className="bg-white rounded-2xl shadow border p-6 flex justify-between items-start"
|
|
>
|
|
<div>
|
|
<p className="text-sm text-slate-500">{card.title}</p>
|
|
<h2 className="text-3xl font-bold text-slate-800 mt-2">
|
|
{card.value}
|
|
</h2>
|
|
</div>
|
|
|
|
<div className="text-right">
|
|
<p className="text-sm text-green-600 font-medium">
|
|
{card.growth}
|
|
</p>
|
|
<div className={`w-10 h-10 rounded-xl mt-3 ${card.iconBg}`} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* ================= CONTENT + QUICK ACTIONS ================= */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* LEFT - RECENT CONTENT */}
|
|
<div className="lg:col-span-2 bg-white rounded-2xl shadow border p-6">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h2 className="text-lg font-semibold text-slate-800">
|
|
Recent Content
|
|
</h2>
|
|
<button className="text-blue-600 text-sm font-medium">
|
|
View All
|
|
</button>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{contents.map((item, i) => (
|
|
<div
|
|
key={i}
|
|
className="border rounded-xl p-4 flex justify-between items-center hover:shadow-sm transition"
|
|
>
|
|
<div>
|
|
<h4 className="font-medium text-slate-800">{item.title}</h4>
|
|
<p className="text-sm text-slate-500 mt-1">
|
|
{item.category} • {item.time}
|
|
</p>
|
|
</div>
|
|
|
|
<span
|
|
className={`text-xs font-medium px-3 py-1 rounded-full
|
|
${
|
|
item.status === "Pending"
|
|
? "bg-yellow-100 text-yellow-600"
|
|
: item.status === "Approved"
|
|
? "bg-green-100 text-green-600"
|
|
: "bg-gray-200 text-gray-600"
|
|
}
|
|
`}
|
|
>
|
|
{item.status}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* RIGHT - QUICK ACTIONS */}
|
|
<div className="bg-[#966314] rounded-2xl shadow p-6 text-white space-y-4">
|
|
<h2 className="text-lg font-semibold">Quick Actions</h2>
|
|
|
|
<button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-xl text-sm font-medium">
|
|
+ Create New Article
|
|
</button>
|
|
|
|
<button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-xl text-sm font-medium">
|
|
+ Update Product
|
|
</button>
|
|
|
|
<button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-xl text-sm font-medium">
|
|
+ Upload Media
|
|
</button>
|
|
|
|
<button className="w-full bg-white text-amber-800 py-3 rounded-xl text-sm font-semibold">
|
|
View All Actions
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ApproverDashboard = () => {
|
|
const stats = [
|
|
{
|
|
title: "Pending Review",
|
|
value: 12,
|
|
growth: "+3",
|
|
color: "bg-yellow-500",
|
|
},
|
|
{
|
|
title: "Approved Today",
|
|
value: 8,
|
|
growth: "+5",
|
|
color: "bg-green-600",
|
|
},
|
|
{
|
|
title: "Total Published",
|
|
value: 156,
|
|
growth: "+12%",
|
|
color: "bg-blue-600",
|
|
},
|
|
{
|
|
title: "Rejected",
|
|
value: 5,
|
|
growth: "-1",
|
|
color: "bg-red-600",
|
|
},
|
|
];
|
|
|
|
const pendingList = [
|
|
{
|
|
title: "MediaHUB Content Aggregator",
|
|
author: "John Kontributor",
|
|
category: "Product",
|
|
time: "2 hours ago",
|
|
status: "Pending",
|
|
},
|
|
{
|
|
title: "Artifintel Services Update",
|
|
author: "John Kontributor",
|
|
category: "Service",
|
|
time: "2 hours ago",
|
|
status: "Pending",
|
|
},
|
|
];
|
|
|
|
const activities = [
|
|
{
|
|
status: "Approved",
|
|
title: "Technology Summit Event",
|
|
time: "10 mins ago",
|
|
},
|
|
{
|
|
status: "Rejected",
|
|
title: "Product Update Draft",
|
|
time: "25 mins ago",
|
|
},
|
|
{
|
|
status: "Approved",
|
|
title: "Partner Logo Update",
|
|
time: "1 hour ago",
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
{/* HEADER */}
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-slate-800">
|
|
Approver Dashboard
|
|
</h1>
|
|
<p className="text-slate-500">
|
|
Review and manage content submissions
|
|
</p>
|
|
</div>
|
|
|
|
{/* ================= STAT CARDS ================= */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
{stats.map((card, i) => (
|
|
<div
|
|
key={i}
|
|
className="bg-white rounded-2xl shadow border p-6 flex justify-between items-start"
|
|
>
|
|
<div>
|
|
<p className="text-sm text-slate-500">{card.title}</p>
|
|
<h2 className="text-3xl font-bold text-slate-800 mt-2">
|
|
{card.value}
|
|
</h2>
|
|
</div>
|
|
|
|
<div className="text-right">
|
|
<p className="text-sm text-green-600 font-medium">
|
|
{card.growth}
|
|
</p>
|
|
<div className={`w-10 h-10 rounded-xl mt-3 ${card.color}`} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* ================= CONTENT SECTION ================= */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* LEFT - Pending Review */}
|
|
<div className="lg:col-span-2 bg-white rounded-2xl shadow border p-6 space-y-6">
|
|
<div className="flex justify-between items-center">
|
|
<h2 className="text-lg font-semibold">
|
|
Pending Review{" "}
|
|
<span className="ml-2 text-xs bg-amber-100 text-amber-600 px-3 py-1 rounded-full">
|
|
{pendingList.length} Items
|
|
</span>
|
|
</h2>
|
|
<button className="text-blue-600 text-sm font-medium">
|
|
View All
|
|
</button>
|
|
</div>
|
|
|
|
{pendingList.map((item, i) => (
|
|
<div
|
|
key={i}
|
|
className="border border-amber-300 bg-amber-50 rounded-xl p-4 space-y-4"
|
|
>
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<h4 className="font-semibold text-slate-800">
|
|
{item.title}
|
|
</h4>
|
|
<p className="text-sm text-slate-500 mt-1">
|
|
{item.author} • {item.category} • {item.time}
|
|
</p>
|
|
</div>
|
|
|
|
<span className="text-xs bg-amber-200 text-amber-700 px-3 py-1 rounded-full">
|
|
{item.status}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex gap-4">
|
|
<button className="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 rounded-lg text-sm font-medium">
|
|
Approve
|
|
</button>
|
|
|
|
<button className="flex-1 bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg text-sm font-medium">
|
|
Reject
|
|
</button>
|
|
|
|
<button className="px-4 py-2 bg-gray-200 rounded-lg text-sm">
|
|
Review
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* RIGHT - Recent Activity */}
|
|
<div className="bg-white rounded-2xl shadow border p-6 space-y-6">
|
|
<h2 className="text-lg font-semibold">Recent Activity</h2>
|
|
|
|
<div className="space-y-4">
|
|
{activities.map((item, i) => (
|
|
<div
|
|
key={i}
|
|
className="border rounded-xl p-4 flex justify-between items-center"
|
|
>
|
|
<div>
|
|
<p
|
|
className={`text-sm font-medium ${
|
|
item.status === "Approved"
|
|
? "text-green-600"
|
|
: "text-red-600"
|
|
}`}
|
|
>
|
|
{item.status}
|
|
</p>
|
|
<p className="text-sm text-slate-700">{item.title}</p>
|
|
<p className="text-xs text-slate-500">{item.time}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<button className="w-full bg-[#966314] hover:bg-[#7a4f0f] text-white py-3 rounded-xl text-sm font-medium">
|
|
View All Activity
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{levelName === "1" && <AdminDashboard />}
|
|
{levelName === "3" && <ContributorDashboard />}
|
|
{levelName === "2" && <ApproverDashboard />}
|
|
</>
|
|
);
|
|
}
|