update filter table, ui dashboard
continuous-integration/drone/push Build encountered an error Details

This commit is contained in:
Anang Yusman 2026-03-16 16:08:49 +08:00
parent a698dfa52a
commit 165ccdb718
7 changed files with 431 additions and 106 deletions

View File

@ -63,8 +63,8 @@ export default function ImageFilterPage() {
<FloatingMenuNews /> <FloatingMenuNews />
</div> </div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6"> <div className="">
{Array.from({ length: 6 }).map((_, i) => ( {Array.from({ length: 1 }).map((_, i) => (
<ImageCard key={i} /> <ImageCard key={i} />
))} ))}
</div> </div>

View File

@ -2,50 +2,97 @@
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useEffect, useState } from "react";
import { getListArticle } from "@/service/article";
type Article = {
id: number;
title: string;
description: string;
categoryName: string;
createdAt: string;
slug: string;
thumbnailUrl?: string;
};
export default function ImageCard() { export default function ImageCard() {
const slug = "bharatu-mardi-hadji-gugur-saat-bertugas"; const [articles, setArticles] = useState<Article[]>([]);
const [page] = useState(1);
const [showData] = useState("6");
const [search] = useState("");
async function initState() {
const req = {
limit: showData,
page,
search,
categorySlug: "",
sort: "desc",
isPublish: true,
sortBy: "created_at",
};
try {
const res = await getListArticle(req);
setArticles(res?.data?.data || []);
} catch (err) {
console.error("Error get article:", err);
}
}
useEffect(() => {
initState();
}, []);
return ( return (
<Link href={`/details/${slug}?type=image`}> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm hover:shadow-md transition duration-300 overflow-hidden"> {articles.map((item) => (
{/* IMAGE */} <Link key={item.id} href={`/details/${item.slug}?type=image`}>
<div className="relative h-[200px] w-full"> <div className="bg-white rounded-2xl border border-gray-100 shadow-sm hover:shadow-md transition duration-300 overflow-hidden">
<Image {/* IMAGE */}
src="/image/novita2.png" <div className="relative h-[200px] w-full">
alt="news" <Image
fill src={item.thumbnailUrl || "/image/novita2.png"}
className="object-cover" alt={item.title}
/> fill
</div> className="object-cover"
/>
</div>
{/* CONTENT */} {/* CONTENT */}
<div className="p-5 space-y-3"> <div className="p-5 space-y-3">
{/* BADGE + TAG */} {/* BADGE + TAG */}
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
<span className="bg-red-600 text-white px-2 py-[3px] rounded-md font-medium"> <span className="bg-red-600 text-white px-2 py-[3px] rounded-md font-medium">
POLRI POLRI
</span> </span>
<span className="text-gray-500 uppercase tracking-wide"> <span className="text-gray-500 uppercase tracking-wide">
SEPUTAR PRESTASI {item.categoryName}
</span> </span>
</div>
{/* DATE */}
<p className="text-xs text-gray-400">
{new Date(item.createdAt).toLocaleDateString("id-ID", {
day: "2-digit",
month: "long",
year: "numeric",
})}
</p>
{/* TITLE */}
<h3 className="font-semibold text-[15px] leading-snug line-clamp-2 hover:text-[#966314] transition">
{item.title}
</h3>
{/* EXCERPT */}
<p className="text-sm text-gray-500 line-clamp-2 leading-relaxed">
{item.description}
</p>
</div>
</div> </div>
</Link>
{/* DATE */} ))}
<p className="text-xs text-gray-400">02 Februari 2024</p> </div>
{/* TITLE */}
<h3 className="font-semibold text-[15px] leading-snug line-clamp-2 hover:text-[#966314] transition">
Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat
Luar Biasa
</h3>
{/* EXCERPT */}
<p className="text-sm text-gray-500 line-clamp-2 leading-relaxed">
Jakarta - Kapolri Jenderal Polisi Drs. Listyo Sigit Prabowo
memberikan kenaikan pangkat luar biasa anumerta kepada...
</p>
</div>
</div>
</Link>
); );
} }

View File

@ -31,7 +31,7 @@ const Option = ({
onMouseLeave={() => setHovered(false)} onMouseLeave={() => setHovered(false)}
className={`relative flex h-12 w-full px-3 items-center rounded-xl transition-all duration-200 cursor-pointer group ${ className={`relative flex h-12 w-full px-3 items-center rounded-xl transition-all duration-200 cursor-pointer group ${
isActive isActive
? "bg-gradient-to-r from-[#966314] to-[#966314] text-white shadow-lg shadow-emerald-500/25" ? "bg-gradient-to-r from-[#966314] to-[#966314] text-white shadow-xl shadow-[#9663144D]/25"
: "text-slate-600 hover:bg-gradient-to-r hover:from-slate-100 hover:to-slate-200/50 hover:text-slate-800" : "text-slate-600 hover:bg-gradient-to-r hover:from-slate-100 hover:to-slate-200/50 hover:text-slate-800"
}`} }`}
whileHover={{ scale: 1.02 }} whileHover={{ scale: 1.02 }}

View File

@ -344,24 +344,88 @@ export default function DashboardContainer() {
value: 24, value: 24,
growth: "+12%", growth: "+12%",
iconBg: "bg-blue-600", iconBg: "bg-blue-600",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
>
<path d="M15 2.5V4c0 1.414 0 2.121.44 2.56C15.878 7 16.585 7 18 7h1.5"></path>
<path d="M4 16V8c0-2.828 0-4.243.879-5.121C5.757 2 7.172 2 10 2h4.172c.408 0 .613 0 .797.076c.183.076.328.22.617.51l3.828 3.828c.29.29.434.434.51.618c.076.183.076.388.076.796V16c0 2.828 0 4.243-.879 5.121C18.243 22 16.828 22 14 22h-4c-2.828 0-4.243 0-5.121-.879C4 20.243 4 18.828 4 16m4-5h8m-8 3h8m-8 3h4.17"></path>
</g>
</svg>
),
}, },
{ {
title: "Pending Approval", title: "Pending Approval",
value: 8, value: 8,
growth: "+3", growth: "+3",
iconBg: "bg-yellow-500", iconBg: "bg-yellow-500",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<g fill="none">
<path
fill="currentColor"
d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2m0 2a8 8 0 1 0 0 16a8 8 0 0 0 0-16m0 2a1 1 0 0 1 .993.883L13 7v4.586l2.707 2.707a1 1 0 0 1-1.32 1.497l-.094-.083l-3-3a1 1 0 0 1-.284-.576L11 12V7a1 1 0 0 1 1-1"
></path>
</g>
</svg>
),
}, },
{ {
title: "Published", title: "Published",
value: 16, value: 16,
growth: "+5", growth: "+5",
iconBg: "bg-green-600", iconBg: "bg-green-600",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M10.5 15.25A.74.74 0 0 1 10 15l-3-3a.75.75 0 0 1 1-1l2.47 2.47L19 5a.75.75 0 0 1 1 1l-9 9a.74.74 0 0 1-.5.25"
></path>
<path
fill="currentColor"
d="M12 21a9 9 0 0 1-7.87-4.66a8.7 8.7 0 0 1-1.07-3.41a9 9 0 0 1 4.6-8.81a8.7 8.7 0 0 1 3.41-1.07a8.9 8.9 0 0 1 3.55.34a.75.75 0 1 1-.43 1.43a7.6 7.6 0 0 0-3-.28a7.4 7.4 0 0 0-2.84.89a7.5 7.5 0 0 0-2.2 1.84a7.42 7.42 0 0 0-1.64 5.51a7.4 7.4 0 0 0 .89 2.84a7.5 7.5 0 0 0 1.84 2.2a7.42 7.42 0 0 0 5.51 1.64a7.4 7.4 0 0 0 2.84-.89a7.5 7.5 0 0 0 2.2-1.84a7.42 7.42 0 0 0 1.64-5.51a.75.75 0 1 1 1.57-.15a9 9 0 0 1-4.61 8.81A8.7 8.7 0 0 1 12.93 21z"
></path>
</svg>
),
}, },
{ {
title: "Rejected", title: "Rejected",
value: 2, value: 2,
growth: "-1", growth: "-1",
iconBg: "bg-red-600", iconBg: "bg-red-600",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m12 13.4l2.9 2.9q.275.275.7.275t.7-.275t.275-.7t-.275-.7L13.4 12l2.9-2.9q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275L12 10.6L9.1 7.7q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7l2.9 2.9l-2.9 2.9q-.275.275-.275.7t.275.7t.7.275t.7-.275zm0 8.6q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"
></path>
</svg>
),
}, },
]; ];
@ -406,20 +470,25 @@ export default function DashboardContainer() {
{stats.map((card, i) => ( {stats.map((card, i) => (
<div <div
key={i} key={i}
className="bg-white rounded-2xl shadow border p-6 flex justify-between items-start" className="bg-white rounded-2xl shadow border p-6 flex-row items-start"
> >
<div> <div className="flex flex-row items-center justify-between">
<p className="text-sm text-slate-500">{card.title}</p> <div
<h2 className="text-3xl font-bold text-slate-800 mt-2"> className={`w-10 h-10 rounded-xl flex items-center justify-center text-white ${card.iconBg}`}
{card.value} >
</h2> {card.icon}
</div> </div>
<div className="text-right">
<p className="text-sm text-green-600 font-medium"> <p className="text-sm text-green-600 font-medium">
{card.growth} {card.growth}
</p> </p>
<div className={`w-10 h-10 rounded-xl mt-3 ${card.iconBg}`} /> </div>
<div>
<h2 className="text-3xl font-bold text-slate-800 mt-2">
{card.value}
</h2>
<p className="text-sm text-slate-500">{card.title}</p>
</div> </div>
</div> </div>
))} ))}
@ -473,19 +542,19 @@ export default function DashboardContainer() {
<div className="bg-[#966314] rounded-2xl shadow p-6 text-white space-y-4"> <div className="bg-[#966314] rounded-2xl shadow p-6 text-white space-y-4">
<h2 className="text-lg font-semibold">Quick Actions</h2> <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"> <button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-md text-sm font-medium text-start pl-3">
+ Create New Article + Create New Article
</button> </button>
<button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-xl text-sm font-medium"> <button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-md text-sm font-medium text-start pl-3">
+ Update Product + Update Product
</button> </button>
<button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-xl text-sm font-medium"> <button className="w-full border border-white bg-[#966314] hover:bg-[#966314] transition py-3 rounded-md text-sm font-medium text-start pl-3">
+ Upload Media + Upload Media
</button> </button>
<p className="border-b-2" />
<button className="w-full bg-white text-amber-800 py-3 rounded-xl text-sm font-semibold"> <button className="w-full bg-[#EBE2D2] text-amber-800 py-3 rounded-md text-sm font-semibold ">
View All Actions View All Actions
</button> </button>
</div> </div>
@ -500,25 +569,89 @@ export default function DashboardContainer() {
title: "Pending Review", title: "Pending Review",
value: 12, value: 12,
growth: "+3", growth: "+3",
color: "bg-yellow-500", iconBg: "bg-yellow-500",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<g fill="none">
<path
fill="currentColor"
d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2m0 2a8 8 0 1 0 0 16a8 8 0 0 0 0-16m0 2a1 1 0 0 1 .993.883L13 7v4.586l2.707 2.707a1 1 0 0 1-1.32 1.497l-.094-.083l-3-3a1 1 0 0 1-.284-.576L11 12V7a1 1 0 0 1 1-1"
></path>
</g>
</svg>
),
}, },
{ {
title: "Approved Today", title: "Approved Today",
value: 8, value: 8,
growth: "+5", growth: "+5",
color: "bg-green-600", iconBg: "bg-green-600",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M10.5 15.25A.74.74 0 0 1 10 15l-3-3a.75.75 0 0 1 1-1l2.47 2.47L19 5a.75.75 0 0 1 1 1l-9 9a.74.74 0 0 1-.5.25"
></path>
<path
fill="currentColor"
d="M12 21a9 9 0 0 1-7.87-4.66a8.7 8.7 0 0 1-1.07-3.41a9 9 0 0 1 4.6-8.81a8.7 8.7 0 0 1 3.41-1.07a8.9 8.9 0 0 1 3.55.34a.75.75 0 1 1-.43 1.43a7.6 7.6 0 0 0-3-.28a7.4 7.4 0 0 0-2.84.89a7.5 7.5 0 0 0-2.2 1.84a7.42 7.42 0 0 0-1.64 5.51a7.4 7.4 0 0 0 .89 2.84a7.5 7.5 0 0 0 1.84 2.2a7.42 7.42 0 0 0 5.51 1.64a7.4 7.4 0 0 0 2.84-.89a7.5 7.5 0 0 0 2.2-1.84a7.42 7.42 0 0 0 1.64-5.51a.75.75 0 1 1 1.57-.15a9 9 0 0 1-4.61 8.81A8.7 8.7 0 0 1 12.93 21z"
></path>
</svg>
),
}, },
{ {
title: "Total Published", title: "Total Published",
value: 156, value: 156,
growth: "+12%", growth: "+12%",
color: "bg-blue-600", iconBg: "bg-blue-600",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
>
<path d="M15 2.5V4c0 1.414 0 2.121.44 2.56C15.878 7 16.585 7 18 7h1.5"></path>
<path d="M4 16V8c0-2.828 0-4.243.879-5.121C5.757 2 7.172 2 10 2h4.172c.408 0 .613 0 .797.076c.183.076.328.22.617.51l3.828 3.828c.29.29.434.434.51.618c.076.183.076.388.076.796V16c0 2.828 0 4.243-.879 5.121C18.243 22 16.828 22 14 22h-4c-2.828 0-4.243 0-5.121-.879C4 20.243 4 18.828 4 16m4-5h8m-8 3h8m-8 3h4.17"></path>
</g>
</svg>
),
}, },
{ {
title: "Rejected", title: "Rejected",
value: 5, value: 5,
growth: "-1", growth: "-1",
color: "bg-red-600", iconBg: "bg-red-600",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m12 13.4l2.9 2.9q.275.275.7.275t.7-.275t.275-.7t-.275-.7L13.4 12l2.9-2.9q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275L12 10.6L9.1 7.7q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7l2.9 2.9l-2.9 2.9q-.275.275-.275.7t.275.7t.7.275t.7-.275zm0 8.6q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"
></path>
</svg>
),
}, },
]; ];
@ -544,16 +677,66 @@ export default function DashboardContainer() {
status: "Approved", status: "Approved",
title: "Technology Summit Event", title: "Technology Summit Event",
time: "10 mins ago", time: "10 mins ago",
iconBg: "bg-[#DCFCE7]",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<g fill="currentColor">
<path d="M10.243 16.314L6 12.07l1.414-1.414l2.829 2.828l5.656-5.657l1.415 1.415z"></path>
<path
fillRule="evenodd"
d="M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11s-4.925 11-11 11S1 18.075 1 12m11 9a9 9 0 1 1 0-18a9 9 0 0 1 0 18"
clipRule="evenodd"
></path>
</g>
</svg>
),
}, },
{ {
status: "Rejected", status: "Rejected",
title: "Product Update Draft", title: "Product Update Draft",
time: "25 mins ago", time: "25 mins ago",
iconBg: "bg-[#FFE2E2]",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m12 13.4l2.9 2.9q.275.275.7.275t.7-.275t.275-.7t-.275-.7L13.4 12l2.9-2.9q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275L12 10.6L9.1 7.7q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7l2.9 2.9l-2.9 2.9q-.275.275-.275.7t.275.7t.7.275t.7-.275zm0 8.6q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"
></path>
</svg>
),
}, },
{ {
status: "Approved", status: "Approved",
title: "Partner Logo Update", title: "Partner Logo Update",
time: "1 hour ago", time: "1 hour ago",
iconBg: "bg-[#DCFCE7]",
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 24 24"
>
<g fill="currentColor">
<path d="M10.243 16.314L6 12.07l1.414-1.414l2.829 2.828l5.656-5.657l1.415 1.415z"></path>
<path
fillRule="evenodd"
d="M1 12C1 5.925 5.925 1 12 1s11 4.925 11 11s-4.925 11-11 11S1 18.075 1 12m11 9a9 9 0 1 1 0-18a9 9 0 0 1 0 18"
clipRule="evenodd"
></path>
</g>
</svg>
),
}, },
]; ];
@ -574,20 +757,25 @@ export default function DashboardContainer() {
{stats.map((card, i) => ( {stats.map((card, i) => (
<div <div
key={i} key={i}
className="bg-white rounded-2xl shadow border p-6 flex justify-between items-start" className="bg-white rounded-2xl shadow border p-6 flex flex-col"
> >
<div> <div className="flex flex-row items-center justify-between">
<p className="text-sm text-slate-500">{card.title}</p> <div
<h2 className="text-3xl font-bold text-slate-800 mt-2"> className={`w-10 h-10 rounded-md flex items-center justify-center text-white ${card.iconBg}`}
{card.value} >
</h2> {card.icon}
</div> </div>
<div className="text-right">
<p className="text-sm text-green-600 font-medium"> <p className="text-sm text-green-600 font-medium">
{card.growth} {card.growth}
</p> </p>
<div className={`w-10 h-10 rounded-xl mt-3 ${card.color}`} /> </div>
<div>
<h2 className="text-3xl font-bold text-slate-800 mt-2">
{card.value}
</h2>
<p className="text-sm text-slate-500">{card.title}</p>
</div> </div>
</div> </div>
))} ))}
@ -656,18 +844,27 @@ export default function DashboardContainer() {
key={i} key={i}
className="border rounded-xl p-4 flex justify-between items-center" className="border rounded-xl p-4 flex justify-between items-center"
> >
<div> <div className="flex flex-row items-center gap-3">
<p <div
className={`text-sm font-medium ${ className={`w-10 h-10 rounded-md flex items-center justify-center
item.status === "Approved" ${item.status === "Approved" ? "text-green-600" : "text-red-600"}
? "text-green-600" ${item.iconBg}`}
: "text-red-600"
}`}
> >
{item.status} {item.icon}
</p> </div>
<p className="text-sm text-slate-700">{item.title}</p> <div>
<p className="text-xs text-slate-500">{item.time}</p> <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>
</div> </div>
))} ))}

View File

@ -15,7 +15,7 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import { Search, Filter, Eye, Pencil, Trash2, Plus } from "lucide-react"; import { Search, Filter, Eye, Pencil, Trash2, Plus } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { getArticlePagination } from "@/service/article"; import { getArticleByCategory, getArticlePagination } from "@/service/article";
import { formatDate } from "@/utils/global"; import { formatDate } from "@/utils/global";
import { close, loading } from "@/config/swal"; import { close, loading } from "@/config/swal";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
@ -26,15 +26,28 @@ export default function NewsImage() {
const [totalPage, setTotalPage] = useState(1); const [totalPage, setTotalPage] = useState(1);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [levelId, setLevelId] = useState<string | undefined>(); const [levelId, setLevelId] = useState<string | undefined>();
const [categories, setupCategory] = useState<any[]>([]);
const [selectedCategory, setSelectedCategory] = useState<number | null>(null);
useEffect(() => { useEffect(() => {
const ulne = Cookies.get("ulne"); const ulne = Cookies.get("ulne");
setLevelId(ulne); setLevelId(ulne);
}, []); }, []);
useEffect(() => {
fetchCategory();
}, []);
const fetchCategory = async () => {
const res = await getArticleByCategory();
if (res?.data?.data) {
setupCategory(res.data.data);
}
};
useEffect(() => { useEffect(() => {
fetchData(); fetchData();
}, [page, search]); }, [page, search, selectedCategory]);
async function fetchData() { async function fetchData() {
loading(); loading();
@ -42,12 +55,13 @@ export default function NewsImage() {
const req = { const req = {
limit: "10", limit: "10",
page: page, page: page,
search: search, title: search,
source: "internal", // jika ingin filter image/internal source: "internal",
categoryId: selectedCategory,
search: "",
sort: "desc", sort: "desc",
sortBy: "created_at", sortBy: "created_at",
}; };
const res = await getArticlePagination(req); const res = await getArticlePagination(req);
const data = res?.data?.data || []; const data = res?.data?.data || [];
@ -58,8 +72,20 @@ export default function NewsImage() {
close(); close();
} }
const getStatus = (article: any) => {
if (article.isDraft) return "Draft";
if (article.publishStatus?.toLowerCase() === "cancel") {
return "Pending";
}
if (article.isPublish) return "Published";
return "Pending";
};
const statusVariant = (status: string) => { const statusVariant = (status: string) => {
const value = status?.toLowerCase(); const value = status.toLowerCase();
if (value === "published") { if (value === "published") {
return "bg-green-100 text-green-700"; return "bg-green-100 text-green-700";
@ -69,13 +95,26 @@ export default function NewsImage() {
return "bg-yellow-100 text-yellow-700"; return "bg-yellow-100 text-yellow-700";
} }
if (value === "cancel") { if (value === "draft") {
return "bg-red-100 text-red-700"; return "bg-gray-200 text-gray-600";
} }
return "bg-gray-200 text-gray-600"; return "bg-gray-200 text-gray-600";
}; };
const getCategoryCount = (title: string) => {
return articles.filter((article) =>
article.categories?.some((c: any) => c.title === title),
).length;
};
const filteredArticles =
selectedCategory === null
? articles
: articles.filter((article) =>
article.categories?.some((c: any) => c.id === selectedCategory),
);
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* ================= HEADER ================= */} {/* ================= HEADER ================= */}
@ -97,7 +136,42 @@ export default function NewsImage() {
</Link> </Link>
)} )}
</div> </div>
<div className="flex flex-wrap gap-2">
<Button
size="sm"
variant={selectedCategory === null ? "default" : "outline"}
onClick={() => {
setSelectedCategory(null);
setPage(1);
}}
>
<div className="flex items-center gap-1">
All
<span className="text-xs bg-slate-200 px-2 py-0.5 rounded-full">
{articles.length}
</span>
</div>
</Button>
{categories.map((cat: any) => (
<Button
key={cat.id}
size="sm"
variant={selectedCategory === cat.id ? "default" : "outline"}
onClick={() => {
setSelectedCategory(cat.id);
setPage(1);
}}
>
<div className="flex items-center gap-1">
{cat.title}
<span className="text-xs bg-white text-black px-2 py-0.5 rounded-full ">
{getCategoryCount(cat.title)}
</span>
</div>
</Button>
))}
</div>
{/* ================= SEARCH ================= */} {/* ================= SEARCH ================= */}
<div className="flex gap-3"> <div className="flex gap-3">
<div className="relative flex-1"> <div className="relative flex-1">
@ -121,7 +195,7 @@ export default function NewsImage() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>Article</TableHead> <TableHead className="pl-3">Article</TableHead>
<TableHead>Category</TableHead> <TableHead>Category</TableHead>
<TableHead>Author</TableHead> <TableHead>Author</TableHead>
<TableHead>Status</TableHead> <TableHead>Status</TableHead>
@ -131,10 +205,10 @@ export default function NewsImage() {
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{articles.length > 0 ? ( {filteredArticles.length > 0 ? (
articles.map((article, index) => ( filteredArticles.map((article, index) => (
<TableRow key={article.id}> <TableRow key={article.id}>
<TableCell className="font-medium max-w-xs"> <TableCell className="font-medium max-w-xs pl-3">
{article.title} {article.title}
</TableCell> </TableCell>
@ -151,13 +225,19 @@ export default function NewsImage() {
</TableCell> </TableCell>
<TableCell> <TableCell>
<span {(() => {
className={`px-3 py-1 text-xs rounded-full font-medium ${statusVariant( const status = getStatus(article);
article.publishStatus,
)}`} return (
> <span
{article.publishStatus} className={`px-3 py-1 text-xs rounded-full font-medium ${statusVariant(
</span> status,
)}`}
>
{status}
</span>
);
})()}
</TableCell> </TableCell>
<TableCell>{formatDate(article.createdAt)}</TableCell> <TableCell>{formatDate(article.createdAt)}</TableCell>

View File

@ -22,7 +22,7 @@ export async function getListArticle(props: PaginationRequest) {
isBanner, isBanner,
} = props; } = props;
return await httpGet( return await httpGetInterceptor(
`/articles?limit=${limit}&page=${page}&isPublish=${ `/articles?limit=${limit}&page=${page}&isPublish=${
isPublish === undefined ? "" : isPublish isPublish === undefined ? "" : isPublish
}&title=${search}&startDate=${startDate || ""}&endDate=${ }&title=${search}&startDate=${startDate || ""}&endDate=${
@ -30,7 +30,6 @@ export async function getListArticle(props: PaginationRequest) {
}&categoryId=${category || ""}&sortBy=${sortBy || "created_at"}&sort=${ }&categoryId=${category || ""}&sortBy=${sortBy || "created_at"}&sort=${
sort || "desc" sort || "desc"
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}`, }&category=${categorySlug || ""}&isBanner=${isBanner || ""}`,
null,
); );
} }
@ -38,10 +37,10 @@ export async function getArticlePagination(props: PaginationRequest) {
const { const {
page, page,
limit, limit,
search, title,
startDate, startDate,
endDate, endDate,
category, categoryId,
sortBy, sortBy,
sort, sort,
categorySlug, categorySlug,
@ -51,9 +50,9 @@ export async function getArticlePagination(props: PaginationRequest) {
} = props; } = props;
return await httpGet( return await httpGet(
`/articles?limit=${limit}&page=${page}&title=${search}&startDate=${ `/articles?limit=${limit}&page=${page}&title=${title || ""}&startDate=${
startDate || "" startDate || ""
}&endDate=${endDate || ""}&categoryId=${category || ""}&source=${ }&endDate=${endDate || ""}&categoryId=${categoryId || ""}&source=${
source || "" source || ""
}&isPublish=${isPublish !== undefined ? isPublish : ""}&sortBy=${ }&isPublish=${isPublish !== undefined ? isPublish : ""}&sortBy=${
sortBy || "created_at" sortBy || "created_at"

View File

@ -306,6 +306,8 @@ export type PaginationRequest = {
search: string; search: string;
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
categoryId: any;
title: string;
isPublish?: boolean; isPublish?: boolean;
category?: string; category?: string;
sortBy?: string; sortBy?: string;