update ui landing, update dashboard
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Anang Yusman 2026-04-13 15:47:14 +08:00
parent e685799204
commit 30743105d4
20 changed files with 1037 additions and 154 deletions

View File

@ -1,12 +1,12 @@
import DashboardNavbar from "@/components/dashboard/dashboard-navbar"; import DashboardNavbar from "@/components/dashboard/dashboard-navbar";
import AdminTable from "@/components/table/admin-table"; import AdminDashboard from "@/components/table/admin-table";
export default function AdminPage() { export default function AdminPage() {
return ( return (
<div className="min-h-screen bg-[#f8f9fa]"> <div className="min-h-screen bg-[#f8f9fa]">
<DashboardNavbar /> <DashboardNavbar />
<div className="p-6"> <div className="p-6">
<AdminTable /> <AdminDashboard />
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,13 @@
import DashboardNavbar from "@/components/dashboard/dashboard-navbar";
import UserManagementTable from "@/components/table/user-management-table";
export default function UserManagementPage() {
return (
<div className="min-h-screen bg-[#f8f9fa]">
<DashboardNavbar />
<div className="p-6">
<UserManagementTable />
</div>
</div>
);
}

View File

@ -1,5 +1,6 @@
import Footer from "@/components/landing-page/footer"; import Footer from "@/components/landing-page/footer";
import Header from "@/components/landing-page/header"; import Header from "@/components/landing-page/header";
import MediaOptions from "@/components/landing-page/media-options";
import Navbar from "@/components/landing-page/navbar"; import Navbar from "@/components/landing-page/navbar";
export default function Home() { export default function Home() {
@ -8,6 +9,7 @@ export default function Home() {
<div className="bg-[#f2f2f2]"> <div className="bg-[#f2f2f2]">
<Navbar /> <Navbar />
<Header /> <Header />
<MediaOptions />
<Footer /> <Footer />
</div> </div>
</div> </div>

View File

@ -1,7 +1,13 @@
"use client"; "use client";
import Image from "next/image"; import Image from "next/image";
import { Bell, LogOut } from "lucide-react"; import {
Bell,
Images,
LayoutDashboardIcon,
LogOut,
UserCog2Icon,
} from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -30,7 +36,7 @@ export default function DashboardNavbar() {
const pathname = usePathname(); const pathname = usePathname();
const [role, setRole] = useState<string | null>(null); const [role, setRole] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState("Manajemen User"); const [activeTab, setActiveTab] = useState("Manajemen User");
const [approverTab, setApproverTab] = useState("Kurasi Konten"); // const [approverTab, setApproverTab] = useState("Kurasi Konten");
const segments = pathname.split("/").filter(Boolean); const segments = pathname.split("/").filter(Boolean);
@ -90,17 +96,26 @@ export default function DashboardNavbar() {
{role === "admin" && ( {role === "admin" && (
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
<button <button
onClick={() => { onClick={() => router.push("/dashboard/admin/dashboard")}
setActiveTab("Manajemen User");
router.push("/dashboard/admin/user-management");
}}
className={`flex flex-col items-center text-sm font-medium px-4 py-2 rounded-md transition ${ className={`flex flex-col items-center text-sm font-medium px-4 py-2 rounded-md transition ${
activeTab === "Manajemen User" pathname.includes("/dashboard/admin/dashboard")
? "text-black border-b-2 border-[#C4A663]" ? "text-black border-b-2 border-[#C4A663]"
: "text-gray-500 hover:text-gray-800" : "text-gray-500 hover:text-gray-800"
}`} }`}
> >
<span className="text-base">🧑💼</span> <LayoutDashboardIcon />
<span>Dashboard</span>
</button>
<button
onClick={() => router.push("/dashboard/admin/user-management")}
className={`flex flex-col items-center text-sm font-medium px-4 py-2 rounded-md transition ${
pathname.includes("/dashboard/admin/user-management")
? "text-black border-b-2 border-[#C4A663]"
: "text-gray-500 hover:text-gray-800"
}`}
>
<UserCog2Icon />
<span>Manajemen User</span> <span>Manajemen User</span>
</button> </button>
</div> </div>
@ -109,7 +124,7 @@ export default function DashboardNavbar() {
{/* APPROVER */} {/* APPROVER */}
{role === "approver" && ( {role === "approver" && (
<div className="flex items-center gap-12"> <div className="flex items-center gap-12">
{["Kurasi Konten", "Publish Konten"].map((tab) => ( {/* {["Kurasi Konten", "Publish Konten"].map((tab) => (
<button <button
key={tab} key={tab}
onClick={() => { onClick={() => {
@ -117,7 +132,7 @@ export default function DashboardNavbar() {
router.push( router.push(
`/dashboard/approver/${ `/dashboard/approver/${
tab === "Kurasi Konten" ? "kurasi" : "publish" tab === "Kurasi Konten" ? "kurasi" : "publish"
}` }`,
); );
}} }}
className={`flex flex-col items-center text-sm font-medium transition ${ className={`flex flex-col items-center text-sm font-medium transition ${
@ -133,22 +148,18 @@ export default function DashboardNavbar() {
)} )}
<span>{tab}</span> <span>{tab}</span>
</button> </button>
))} ))} */}
</div> </div>
)} )}
{/* SUPERVISOR */} {/* SUPERVISOR */}
{role === "supervisor" && ( {role === "supervisor" && (
<div className="text-lg font-semibold text-gray-700"> <div className="text-lg font-semibold text-gray-700"></div>
Supervisor Dashboard
</div>
)} )}
{/* USER */} {/* USER */}
{role === "user" && ( {role === "user" && (
<div className="text-lg font-semibold text-gray-700"> <div className="text-lg font-semibold text-gray-700"></div>
User Dashboard
</div>
)} )}
</div> </div>

View File

@ -47,7 +47,7 @@ export default function Login() {
e.preventDefault(); e.preventDefault();
const foundUser = users.find( const foundUser = users.find(
(u) => u.nrp === nrp && u.password === password (u) => u.nrp === nrp && u.password === password,
); );
if (!foundUser) { if (!foundUser) {
@ -61,7 +61,7 @@ export default function Login() {
// ✅ Redirect sesuai role // ✅ Redirect sesuai role
switch (foundUser.role) { switch (foundUser.role) {
case "admin": case "admin":
router.push("/dashboard/admin"); router.push("/dashboard/admin/user-management");
break; break;
case "user": case "user":
router.push("/dashboard/user"); router.push("/dashboard/user");

View File

@ -4,9 +4,9 @@ export default function Header() {
return ( return (
<section className=" text-white"> <section className=" text-white">
{/* Bagian Atas */} {/* Bagian Atas */}
<div className="container bg-[#B87C2C] mx-auto flex flex-col md:flex-row items-center justify-between px-6 py-10 md:py-16"> <div className="container bg-[#B4812E] mx-auto flex flex-col md:flex-row items-center justify-between px-6 py-10 md:py-16">
<div className="md:w-1/2 space-y-4"> <div className="md:w-1/2 space-y-4 ml-10">
<h1 className="text-2xl md:text-3xl font-bold leading-snug"> <h1 className="text-2xl md:text-[36px] font-bold leading-snug">
Capai Audiens yang Lebih Luas dengan melakukan <br /> Capai Audiens yang Lebih Luas dengan melakukan <br />
<span className="text-white">Promote di CampaignPOOL</span> <span className="text-white">Promote di CampaignPOOL</span>
</h1> </h1>
@ -27,10 +27,12 @@ export default function Header() {
{/* Bagian Langkah-langkah */} {/* Bagian Langkah-langkah */}
<div className="container mx-auto text-center py-10 px-4"> <div className="container mx-auto text-center py-10 px-4">
<h2 className="text-2xl font-semibold text-black"> <h2 className="text-[30px] font-bold text-black">
Cara Promote di CampaignPOOL Cara Promote di CampaignPOOL
</h2> </h2>
<p className="text-gray-600 mt-2">Langkah mudah untuk memasang iklan</p> <p className="text-[#1D1D1D] mt-2 text-[16px]">
Langkah mudah untuk memasang iklan
</p>
{/* Langkah-langkah */} {/* Langkah-langkah */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-10 place-items-center"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-10 place-items-center">
@ -45,10 +47,10 @@ export default function Header() {
className="object-contain" className="object-contain"
/> />
</div> </div>
<h3 className="mt-4 font-semibold text-black text-center"> <h3 className="mt-4 font-bold text-black text-center text-[20px]">
Langkah 1 Langkah 1
</h3> </h3>
<p className="text-sm text-gray-600 mt-2 text-center"> <p className="text-[16px] text-[#1D1D1D] mt-2 text-center">
Pilih tanggal tayang, tentukan target promote, dan unggah materi Pilih tanggal tayang, tentukan target promote, dan unggah materi
iklan iklan
</p> </p>
@ -65,10 +67,10 @@ export default function Header() {
className="object-contain" className="object-contain"
/> />
</div> </div>
<h3 className="mt-4 font-semibold text-black text-center"> <h3 className="mt-4 font-bold text-black text-center text-[20px]">
Langkah 2 Langkah 2
</h3> </h3>
<p className="text-sm text-gray-600 mt-2 text-center"> <p className="text-[16px] text-[#1D1D1D] mt-2 text-center">
Pemrosesan Internal dan Persetujuan Pemrosesan Internal dan Persetujuan
</p> </p>
</div> </div>
@ -84,10 +86,10 @@ export default function Header() {
className="object-contain" className="object-contain"
/> />
</div> </div>
<h3 className="mt-4 font-semibold text-black text-center"> <h3 className="mt-4 font-bold text-black text-center text-[20px]">
Langkah 3 Langkah 3
</h3> </h3>
<p className="text-sm text-gray-600 mt-2 text-center"> <p className="text-[16px] text-[#1D1D1D] mt-2 text-center">
Selamat! Promote Anda tayang Selamat! Promote Anda tayang
</p> </p>
</div> </div>
@ -104,10 +106,10 @@ export default function Header() {
className="object-contain" className="object-contain"
/> />
</div> </div>
<h3 className="mt-4 font-semibold text-black text-center"> <h3 className="mt-4 font-bold text-black text-center text-[20px]">
Langkah 4 Langkah 4
</h3> </h3>
<p className="text-sm text-gray-600 mt-2 text-center"> <p className="text-[16px] text-[#1D1D1D] mt-2 text-center">
Pantau perkembangan Promote Anda Pantau perkembangan Promote Anda
</p> </p>
</div> </div>

View File

@ -0,0 +1,76 @@
import Image from "next/image";
export default function MediaOptions() {
const items = [
{
title: "Videotron",
desc: "Large-format advertising di lokasi strategis dengan traffic tinggi",
img: "/videotron.png",
},
{
title: "Media Online",
desc: "Branding interior kereta api dengan jangkauan penumpang tinggi",
img: "/gemini.png",
},
{
title: "Media Sosial",
desc: "Display digital untuk konten dinamis dan engaging di stasiun & mall",
img: "/ai.png",
},
{
title: "Radio Polri",
desc: "Large-format advertising di lokasi strategis dengan traffic tinggi",
img: "/radio.png",
},
{
title: "TV Polri",
desc: "Branding interior kereta api dengan jangkauan penumpang tinggi",
img: "/tvpolri.png",
},
{
title: "Majalah Digital",
desc: "Display digital untuk konten dinamis dan engaging di stasiun & mall",
img: "/majalah.png",
},
];
return (
<section className="container mx-auto py-12 px-4">
<div className="text-center mb-10">
<h2 className="text-2xl md:text-3xl font-bold text-black">
Pilihan Media Iklan untuk Brand Anda
</h2>
<p className="text-gray-600 mt-2">
Maksimalkan visibilitas brand dengan media iklan strategis
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mx-16">
{items.map((item, index) => (
<div
key={index}
className="border rounded-xl overflow-hidden shadow-sm hover:shadow-md transition"
>
<div className="relative w-full h-48">
<Image
src={item.img}
alt={item.title}
fill
className="object-cover"
/>
</div>
<div className="p-4">
<h3 className="font-semibold text-lg text-black">{item.title}</h3>
<p className="text-sm text-gray-600 mt-2">{item.desc}</p>
<button className="mt-4 inline-flex items-center gap-2 text-blue-600 border border-blue-600 px-4 py-2 rounded-full text-sm hover:bg-blue-50">
Lihat Layanan
</button>
</div>
</div>
))}
</div>
</section>
);
}

View File

@ -1,127 +1,425 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { import {
Table, BarChart,
TableBody, Bar,
TableCell, XAxis,
TableHead, YAxis,
TableHeader, Tooltip,
TableRow, ResponsiveContainer,
} from "@/components/ui/table"; PieChart,
import DialogUserDetail from "../dialog/admin-detail"; Pie,
Cell,
RadarChart,
PolarGrid,
PolarAngleAxis,
PolarRadiusAxis,
Radar,
Legend,
AreaChart,
CartesianGrid,
Area,
} from "recharts";
export default function AdminTable() { export default function AdminDashboard() {
const [selectedUser, setSelectedUser] = useState<any>(null); const [range, setRange] = useState("1 Day");
const [isDialogOpen, setIsDialogOpen] = useState(false);
const data = [ const userData = [
{ { name: "Salma Husna", value: 80 },
createdAt: "14 Januari 2025 13:00", { name: "Sheva", value: 70 },
fullName: "Novan Farhandi", { name: "Haryanto Wijaya", value: 55 },
email: "novanfarhandi@example.com", { name: "Rina Wati", value: 55 },
status: "Approved", { name: "Novan Farhandi", value: 40 },
},
{
createdAt: "14 Januari 2025 13:00",
fullName: "Salma Husna",
email: "salmahusna@example.com",
status: "Tertunda",
},
]; ];
const openDialog = (user: any) => { const mediaData = [
setSelectedUser(user); { name: "Media Sosial", value: 30 },
setIsDialogOpen(true); { name: "Media Online", value: 25 },
}; { name: "Videotron", value: 20 },
{ name: "Radio Polri", value: 10 },
{ name: "TV Polri", value: 8 },
{ name: "Majalah Digital", value: 12 },
];
const closeDialog = () => { const colors = [
setIsDialogOpen(false); "#1D4ED8",
setSelectedUser(null); "#10B981",
}; "#F59E0B",
"#F97316",
"#86EFAC",
"#A78BFA",
];
const FilterButton = ({
label,
value,
setValue,
}: {
label: string;
value: string;
setValue: (val: string) => void;
}) => (
<button
onClick={() => setValue(label)}
className={`px-3 py-1 rounded-md text-sm ${
value === label ? "bg-white text-black" : "text-gray-300"
}`}
>
{label}
</button>
);
const [rangeLine, setRangeLine] = useState("1 Day");
const [rangeChart, setRangeChart] = useState("1 Day");
const [rangeCalendar, setRangeCalendar] = useState("1 Day");
const [rangeLineChart, setRangeLineChart] = useState("1 Day");
const [rangeMedsos, setRangeMedsos] = useState("1 Day");
const lineData = Array.from({ length: 24 }).map((_, i) => ({
time: `${i}:00`,
value: Math.floor(Math.random() * 100),
}));
const radarData = [
{ subject: "Instagram", humas: 90, polda: 85 },
{ subject: "Facebook", humas: 70, polda: 40 },
{ subject: "Twitter (X)", humas: 50, polda: 80 },
{ subject: "TikTok", humas: 60, polda: 55 },
{ subject: "YouTube", humas: 65, polda: 60 },
];
const colorsChart = ["#2563EB", "#10B981"];
const FilterButtonLine = ({ label }: { label: string }) => (
<button
onClick={() => setRange(label)}
className={`px-3 py-1 rounded-md text-sm ${
range === label ? "bg-white text-black" : "text-gray-300"
}`}
>
{label}
</button>
);
return ( return (
<div className="bg-white shadow rounded-lg p-4"> <section className="p-6 space-y-6">
<div className="overflow-x-auto"> {/* Header */}
<Table> <div className="flex justify-between items-center">
<TableHeader> <h1 className="text-xl font-semibold">Analitik Campaignpool</h1>
<TableRow className="bg-gray-100"> <Button className="bg-blue-600 text-white rounded-sm">
<TableHead className="text-gray-600">Nama Lengkap</TableHead> Unduh Laporan
<TableHead className="text-gray-600">Email</TableHead> </Button>
<TableHead className="text-gray-600">Tanggal Daftar</TableHead> </div>
<TableHead className="text-gray-600">Status</TableHead>
<TableHead className="text-gray-600">Tindakan</TableHead>
</TableRow>
</TableHeader>
<TableBody> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{data.map((row, i) => ( {/* Chart 1 */}
<TableRow key={i} className="hover:bg-gray-50 transition-colors"> <Card className="rounded-2xl shadow-sm">
<TableCell>{row.fullName}</TableCell> <CardContent className="p-4">
<TableCell className="font-medium text-gray-800"> <div className="flex justify-between items-center mb-4">
{row.email} <h2 className="font-semibold">Pengguna Paling Aktif</h2>
</TableCell>
<TableCell className="text-gray-700">{row.createdAt}</TableCell>
<TableCell> <div className="bg-black rounded-lg flex gap-2 p-1">
<span <FilterButton
className={`px-3 py-1 rounded-full text-xs font-medium ${ label="1 Day"
row.status === "Approved" value={rangeLine}
? "bg-green-100 text-green-700" setValue={setRangeLine}
: "bg-gray-200 text-gray-700" />
}`} <FilterButton
label="7 Days"
value={rangeLine}
setValue={setRangeLine}
/>
<FilterButton
label="30 Days"
value={rangeLine}
setValue={setRangeLine}
/>
<FilterButton
label="Custom"
value={rangeLine}
setValue={setRangeLine}
/>
</div>
</div>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={userData} layout="vertical">
<XAxis type="number" hide />
<YAxis dataKey="name" type="category" />
<Tooltip />
<Bar dataKey="value" fill="#4E79A7" radius={[6, 6, 6, 6]} />
</BarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
{/* Chart 2 */}
<Card className="rounded-2xl shadow-sm">
<CardContent className="p-4">
<div className="flex justify-between items-center mb-4">
<h2 className="font-semibold">Media Paling Favorit</h2>
<div className="bg-black rounded-lg flex gap-2 p-1">
<FilterButton
label="1 Day"
value={rangeChart}
setValue={setRangeChart}
/>
<FilterButton
label="7 Days"
value={rangeChart}
setValue={setRangeChart}
/>
<FilterButton
label="30 Days"
value={rangeChart}
setValue={setRangeChart}
/>
<FilterButton
label="Custom"
value={rangeChart}
setValue={setRangeChart}
/>
</div>
</div>
<div className="h-64 flex items-center justify-center">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={mediaData}
innerRadius={60}
outerRadius={90}
paddingAngle={4}
dataKey="value"
> >
{row.status} {mediaData.map((_, index) => (
</span> <Cell key={index} fill={colors[index % colors.length]} />
</TableCell> ))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</div>
<TableCell> {/* Legend */}
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap justify-center gap-4 mt-4 text-sm">
<button {mediaData.map((item, i) => (
className="text-blue-600 hover:underline" <div key={i} className="flex items-center gap-2">
onClick={() => openDialog(row)} <span
> className="w-3 h-3 rounded-sm"
Lihat style={{ backgroundColor: colors[i] }}
</button> />
<button className="text-green-600 hover:underline"> {item.name}
Approve </div>
</button> ))}
<button className="text-red-500 hover:underline"> </div>
Hapus </CardContent>
</button> </Card>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div> </div>
<Card className="rounded-2xl shadow-sm">
<CardContent className="p-4">
<div className="flex justify-between items-center mb-4 ">
<h2 className="font-semibold">Heatmap Order Publish</h2>
{/* Pagination */} <div className="bg-black rounded-lg flex gap-2 p-1">
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mt-4 text-sm text-gray-500 gap-2"> <FilterButton
<div className="flex items-center"> label="1 Day"
<span>Rows per page:</span> value={rangeCalendar}
<select className="ml-2 border rounded px-1 py-0.5"> setValue={setRangeCalendar}
<option>6</option> />
<option>12</option> <FilterButton
</select> label="7 Days"
</div> value={rangeCalendar}
setValue={setRangeCalendar}
<div className="flex items-center justify-between sm:justify-end gap-2"> />
<span>11 of 1</span> <FilterButton
<div className="flex gap-1"> label="30 Days"
<button className="text-gray-400 hover:text-gray-600"></button> value={rangeCalendar}
<button className="text-gray-400 hover:text-gray-600"></button> setValue={setRangeCalendar}
/>
<FilterButton
label="Custom"
value={rangeCalendar}
setValue={setRangeCalendar}
/>
</div>
</div> </div>
</div>
</div>
{/* ✅ Dialog terpisah */} {/* Heatmap Grid */}
<DialogUserDetail <div className="overflow-x-auto">
isOpen={isDialogOpen} <div className="grid grid-cols-13 gap-2 text-sm items-center">
onClose={closeDialog} {[
user={selectedUser} "",
/> "Jan",
</div> "Feb",
"Mar",
"Apr",
"Mei",
"Jun",
"Jul",
"Agu",
"Sep",
"Okt",
"Nov",
"Des",
].map((m, i) => (
<div key={i} className="text-center text-gray-500">
{m}
</div>
))}
{["Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Min"].map(
(day, rowIndex) => (
<>
<div key={day} className="text-gray-500">
{day}
</div>
{Array.from({ length: 12 }).map((_, colIndex) => {
const intensity = Math.floor(Math.random() * 5);
const colors = [
"bg-gray-200",
"bg-blue-200",
"bg-blue-400",
"bg-blue-600",
"bg-blue-800",
];
return (
<div
key={colIndex}
className={`h-6 rounded ${colors[intensity]}`}
/>
);
})}
</>
),
)}
</div>
</div>
{/* Legend */}
<div className="flex justify-end items-center gap-2 mt-4 text-sm text-gray-500">
<span>Less</span>
{[0, 1, 2, 3, 4].map((i) => (
<div
key={i}
className={`w-4 h-4 rounded ${
[
"bg-gray-200",
"bg-blue-200",
"bg-blue-400",
"bg-blue-600",
"bg-blue-800",
][i]
}`}
/>
))}
<span>More</span>
</div>
</CardContent>
</Card>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Line Chart */}
<Card className="rounded-2xl shadow-sm">
<CardContent className="p-4">
<div className="flex justify-between items-center mb-4">
<h2 className="font-semibold">Produksi Konten Internal</h2>
<div className="bg-black rounded-lg flex gap-2 p-1">
<FilterButton
label="1 Day"
value={rangeLineChart}
setValue={setRangeLineChart}
/>
<FilterButton
label="7 Days"
value={rangeLineChart}
setValue={setRangeLineChart}
/>
<FilterButton
label="30 Days"
value={rangeLineChart}
setValue={setRangeLineChart}
/>
<FilterButton
label="Custom"
value={rangeLineChart}
setValue={setRangeLineChart}
/>
</div>
</div>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={lineData}>
<defs>
<linearGradient id="color" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#2563EB" stopOpacity={0.4} />
<stop offset="95%" stopColor="#2563EB" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Area
type="monotone"
dataKey="value"
stroke="#2563EB"
fill="url(#color)"
strokeWidth={3}
dot={{ r: 4 }}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
{/* RADAR CHART (FIXED) */}
<Card className="rounded-2xl shadow-sm">
<CardContent className="p-4">
<div className="flex justify-between items-center mb-4">
<h2 className="font-semibold">Engagement Media Sosial</h2>
<div className="bg-black rounded-lg flex gap-2 p-1">
<FilterButtonLine label="1 Day" />
<FilterButtonLine label="7 Days" />
<FilterButtonLine label="30 Days" />
<FilterButtonLine label="Custom" />
</div>
</div>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<RadarChart data={radarData}>
<PolarGrid />
<PolarAngleAxis dataKey="subject" />
<PolarRadiusAxis />
<Radar
name="Humas Mabes"
dataKey="humas"
stroke="#2563EB"
fill="#2563EB"
fillOpacity={0.4}
/>
<Radar
name="Polda"
dataKey="polda"
stroke="#10B981"
fill="#10B981"
fillOpacity={0.4}
/>
<Legend />
</RadarChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
</div>
</section>
); );
} }

View File

@ -31,7 +31,7 @@ export default function ApproverTable() {
{ {
id: 1, id: 1,
no: 1, no: 1,
media: "Media Online", media: "Online Media",
title: title:
"Lorem ipsum dolor sit amet consectetur. Tempor mi scelerisque enim semper sed nibh. Eget sit molestie.", "Lorem ipsum dolor sit amet consectetur. Tempor mi scelerisque enim semper sed nibh. Eget sit molestie.",
status: "Tertunda", status: "Tertunda",
@ -39,7 +39,7 @@ export default function ApproverTable() {
{ {
id: 2, id: 2,
no: 2, no: 2,
media: "Media Sosial", media: "Social Media",
title: title:
"Lorem ipsum dolor sit amet consectetur. Ultricies pellentesque ullamcorper mattis pellentesque. Amet eu ut.", "Lorem ipsum dolor sit amet consectetur. Ultricies pellentesque ullamcorper mattis pellentesque. Amet eu ut.",
status: "Tertunda", status: "Tertunda",
@ -82,7 +82,7 @@ export default function ApproverTable() {
const filteredData = useMemo(() => { const filteredData = useMemo(() => {
if (activeCategory === "Semua") return data; if (activeCategory === "Semua") return data;
return data.filter( return data.filter(
(item) => item.media.toLowerCase() === activeCategory.toLowerCase() (item) => item.media.toLowerCase() === activeCategory.toLowerCase(),
); );
}, [activeCategory]); }, [activeCategory]);
@ -100,7 +100,7 @@ export default function ApproverTable() {
key={cat} key={cat}
value={cat} value={cat}
className={cn( className={cn(
"data-[state=active]:bg-white data-[state=active]:text-black data-[state=active]:font-semibold text-gray-300 rounded-lg text-sm sm:text-base transition-all px-4 py-2 whitespace-nowrap" "data-[state=active]:bg-white data-[state=active]:text-black data-[state=active]:font-semibold text-gray-300 rounded-lg text-sm sm:text-base transition-all px-4 py-2 whitespace-nowrap",
)} )}
> >
{cat} {cat}
@ -146,7 +146,7 @@ export default function ApproverTable() {
"px-2 sm:px-3 py-1 rounded-full text-[10px] sm:text-xs font-medium whitespace-nowrap", "px-2 sm:px-3 py-1 rounded-full text-[10px] sm:text-xs font-medium whitespace-nowrap",
item.status === "Tertunda" item.status === "Tertunda"
? "bg-gray-200 text-gray-600" ? "bg-gray-200 text-gray-600"
: "bg-green-100 text-green-800" : "bg-green-100 text-green-800",
)} )}
> >
{item.status} {item.status}

View File

@ -19,6 +19,7 @@ export default function SupervisorData() {
type: "bar", type: "bar",
toolbar: { show: false }, toolbar: { show: false },
}, },
colors: ["#4E79A7"], // 👈 tambahkan ini
plotOptions: { plotOptions: {
bar: { bar: {
borderRadius: 4, borderRadius: 4,
@ -51,19 +52,19 @@ export default function SupervisorData() {
breakpoint: 768, breakpoint: 768,
options: { options: {
plotOptions: { plotOptions: {
bar: { bar: { columnWidth: "60%" },
columnWidth: "60%",
},
}, },
xaxis: { xaxis: {
labels: { rotate: -30, style: { fontSize: "9px" } }, labels: {
rotate: -30,
style: { fontSize: "9px" },
},
}, },
dataLabels: { enabled: false }, dataLabels: { enabled: false },
}, },
}, },
], ],
}; };
const chartDataSatker = [ const chartDataSatker = [
{ {
name: "Total Konten", name: "Total Konten",

View File

@ -0,0 +1,127 @@
"use client";
import { useState } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import DialogUserDetail from "../dialog/admin-detail";
export default function UserManagementTable() {
const [selectedUser, setSelectedUser] = useState<any>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const data = [
{
createdAt: "14 Januari 2025 13:00",
fullName: "Novan Farhandi",
email: "novanfarhandi@example.com",
status: "Approved",
},
{
createdAt: "14 Januari 2025 13:00",
fullName: "Salma Husna",
email: "salmahusna@example.com",
status: "Tertunda",
},
];
const openDialog = (user: any) => {
setSelectedUser(user);
setIsDialogOpen(true);
};
const closeDialog = () => {
setIsDialogOpen(false);
setSelectedUser(null);
};
return (
<div className="bg-white shadow rounded-lg p-4">
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow className="bg-gray-100">
<TableHead className="text-gray-600">Nama Lengkap</TableHead>
<TableHead className="text-gray-600">Email</TableHead>
<TableHead className="text-gray-600">Tanggal Daftar</TableHead>
<TableHead className="text-gray-600">Status</TableHead>
<TableHead className="text-gray-600">Tindakan</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, i) => (
<TableRow key={i} className="hover:bg-gray-50 transition-colors">
<TableCell>{row.fullName}</TableCell>
<TableCell className="font-medium text-gray-800">
{row.email}
</TableCell>
<TableCell className="text-gray-700">{row.createdAt}</TableCell>
<TableCell>
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${
row.status === "Approved"
? "bg-green-100 text-green-700"
: "bg-gray-200 text-gray-700"
}`}
>
{row.status}
</span>
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-2">
<button
className="text-blue-600 hover:underline"
onClick={() => openDialog(row)}
>
Lihat
</button>
<button className="text-green-600 hover:underline">
Approve
</button>
<button className="text-red-500 hover:underline">
Hapus
</button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center mt-4 text-sm text-gray-500 gap-2">
<div className="flex items-center">
<span>Rows per page:</span>
<select className="ml-2 border rounded px-1 py-0.5">
<option>6</option>
<option>12</option>
</select>
</div>
<div className="flex items-center justify-between sm:justify-end gap-2">
<span>11 of 1</span>
<div className="flex gap-1">
<button className="text-gray-400 hover:text-gray-600"></button>
<button className="text-gray-400 hover:text-gray-600"></button>
</div>
</div>
</div>
{/* ✅ Dialog terpisah */}
<DialogUserDetail
isOpen={isDialogOpen}
onClose={closeDialog}
user={selectedUser}
/>
</div>
);
}

View File

@ -47,7 +47,6 @@ export default function UserTable() {
return ( return (
<> <>
<div className="flex justify-between items-center mb-5 flex-wrap gap-3"> <div className="flex justify-between items-center mb-5 flex-wrap gap-3">
<h2 className="text-lg font-semibold text-gray-800">Daftar Campaign</h2>
<Link href={"/dashboard/user/create"}> <Link href={"/dashboard/user/create"}>
<Button className="bg-blue-600 hover:bg-blue-700 text-white"> <Button className="bg-blue-600 hover:bg-blue-700 text-white">
Add New Add New

359
package-lock.json generated
View File

@ -31,6 +31,7 @@
"react-apexcharts": "^1.8.0", "react-apexcharts": "^1.8.0",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"recharts": "^3.8.1",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {
@ -1935,6 +1936,50 @@
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="
}, },
"node_modules/@reduxjs/toolkit": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
"integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0",
"immer": "^11.0.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@reduxjs/toolkit/node_modules/immer": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
"integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="
},
"node_modules/@standard-schema/utils": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
},
"node_modules/@svgdotjs/svg.draggable.js": { "node_modules/@svgdotjs/svg.draggable.js": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz",
@ -2074,6 +2119,60 @@
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz",
"integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==" "integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg=="
}, },
"node_modules/@types/d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
"integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
"node_modules/@types/js-cookie": { "node_modules/@types/js-cookie": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@ -2097,7 +2196,7 @@
"version": "19.2.2", "version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
@ -2106,7 +2205,7 @@
"version": "19.2.2", "version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"dev": true, "devOptional": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
@ -2116,6 +2215,11 @@
"resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
"integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==" "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
},
"node_modules/@yr/monotone-cubic-spline": { "node_modules/@yr/monotone-cubic-spline": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
@ -2327,7 +2431,117 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true "devOptional": true
},
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
}, },
"node_modules/date-fns": { "node_modules/date-fns": {
"version": "4.1.0", "version": "4.1.0",
@ -2343,6 +2557,11 @@
"resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==" "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="
}, },
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -2441,6 +2660,11 @@
"benchmarks" "benchmarks"
] ]
}, },
"node_modules/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.11", "version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
@ -2584,6 +2808,23 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/immer": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"engines": {
"node": ">=12"
}
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.6.1", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
@ -2928,6 +3169,28 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
@ -2994,6 +3257,62 @@
} }
} }
}, },
"node_modules/recharts": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz",
"integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==",
"workspaces": [
"www"
],
"dependencies": {
"@reduxjs/toolkit": "^1.9.0 || 2.x.x",
"clsx": "^2.1.1",
"decimal.js-light": "^2.5.1",
"es-toolkit": "^1.39.3",
"eventemitter3": "^5.0.1",
"immer": "^10.1.1",
"react-redux": "8.x.x || 9.x.x",
"reselect": "5.1.1",
"tiny-invariant": "^1.3.3",
"use-sync-external-store": "^1.2.2",
"victory-vendor": "^37.0.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/recharts/node_modules/es-toolkit": {
"version": "1.45.1",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
"integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
"workspaces": [
"docs",
"benchmarks"
]
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@ -3111,6 +3430,11 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -3198,10 +3522,39 @@
} }
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/vanilla-colorful": { "node_modules/vanilla-colorful": {
"version": "0.7.2", "version": "0.7.2",
"resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz",
"integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==" "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg=="
},
"node_modules/victory-vendor": {
"version": "37.3.6",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
} }
} }
} }

View File

@ -31,6 +31,7 @@
"react-apexcharts": "^1.8.0", "react-apexcharts": "^1.8.0",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.11.1",
"react-dom": "19.2.0", "react-dom": "19.2.0",
"recharts": "^3.8.1",
"tailwind-merge": "^3.3.1" "tailwind-merge": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {

BIN
public/ai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

BIN
public/gemini.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

BIN
public/majalah.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

BIN
public/radio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

BIN
public/tvpolri.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

BIN
public/videotron.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB