update filter table, ui dashboard
continuous-integration/drone/push Build encountered an error
Details
continuous-integration/drone/push Build encountered an error
Details
This commit is contained in:
parent
a698dfa52a
commit
165ccdb718
|
|
@ -63,8 +63,8 @@ export default function ImageFilterPage() {
|
|||
<FloatingMenuNews />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div className="">
|
||||
{Array.from({ length: 1 }).map((_, i) => (
|
||||
<ImageCard key={i} />
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,50 +2,97 @@
|
|||
|
||||
import Image from "next/image";
|
||||
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() {
|
||||
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 (
|
||||
<Link href={`/details/${slug}?type=image`}>
|
||||
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm hover:shadow-md transition duration-300 overflow-hidden">
|
||||
{/* IMAGE */}
|
||||
<div className="relative h-[200px] w-full">
|
||||
<Image
|
||||
src="/image/novita2.png"
|
||||
alt="news"
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{articles.map((item) => (
|
||||
<Link key={item.id} href={`/details/${item.slug}?type=image`}>
|
||||
<div className="bg-white rounded-2xl border border-gray-100 shadow-sm hover:shadow-md transition duration-300 overflow-hidden">
|
||||
{/* IMAGE */}
|
||||
<div className="relative h-[200px] w-full">
|
||||
<Image
|
||||
src={item.thumbnailUrl || "/image/novita2.png"}
|
||||
alt={item.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="p-5 space-y-3">
|
||||
{/* BADGE + TAG */}
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="bg-red-600 text-white px-2 py-[3px] rounded-md font-medium">
|
||||
POLRI
|
||||
</span>
|
||||
<span className="text-gray-500 uppercase tracking-wide">
|
||||
SEPUTAR PRESTASI
|
||||
</span>
|
||||
{/* CONTENT */}
|
||||
<div className="p-5 space-y-3">
|
||||
{/* BADGE + TAG */}
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="bg-red-600 text-white px-2 py-[3px] rounded-md font-medium">
|
||||
POLRI
|
||||
</span>
|
||||
<span className="text-gray-500 uppercase tracking-wide">
|
||||
{item.categoryName}
|
||||
</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>
|
||||
|
||||
{/* DATE */}
|
||||
<p className="text-xs text-gray-400">02 Februari 2024</p>
|
||||
|
||||
{/* 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>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const Option = ({
|
|||
onMouseLeave={() => setHovered(false)}
|
||||
className={`relative flex h-12 w-full px-3 items-center rounded-xl transition-all duration-200 cursor-pointer group ${
|
||||
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"
|
||||
}`}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
|
|
|
|||
|
|
@ -344,24 +344,88 @@ export default function DashboardContainer() {
|
|||
value: 24,
|
||||
growth: "+12%",
|
||||
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",
|
||||
value: 8,
|
||||
growth: "+3",
|
||||
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",
|
||||
value: 16,
|
||||
growth: "+5",
|
||||
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",
|
||||
value: 2,
|
||||
growth: "-1",
|
||||
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) => (
|
||||
<div
|
||||
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>
|
||||
<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="flex flex-row items-center justify-between">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-xl flex items-center justify-center text-white ${card.iconBg}`}
|
||||
>
|
||||
{card.icon}
|
||||
</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>
|
||||
<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>
|
||||
))}
|
||||
|
|
@ -473,19 +542,19 @@ export default function DashboardContainer() {
|
|||
<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">
|
||||
<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
|
||||
</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
|
||||
</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
|
||||
</button>
|
||||
|
||||
<button className="w-full bg-white text-amber-800 py-3 rounded-xl text-sm font-semibold">
|
||||
<p className="border-b-2" />
|
||||
<button className="w-full bg-[#EBE2D2] text-amber-800 py-3 rounded-md text-sm font-semibold ">
|
||||
View All Actions
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -500,25 +569,89 @@ export default function DashboardContainer() {
|
|||
title: "Pending Review",
|
||||
value: 12,
|
||||
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",
|
||||
value: 8,
|
||||
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",
|
||||
value: 156,
|
||||
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",
|
||||
value: 5,
|
||||
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",
|
||||
title: "Technology Summit Event",
|
||||
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",
|
||||
title: "Product Update Draft",
|
||||
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",
|
||||
title: "Partner Logo Update",
|
||||
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) => (
|
||||
<div
|
||||
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>
|
||||
<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="flex flex-row items-center justify-between">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-md flex items-center justify-center text-white ${card.iconBg}`}
|
||||
>
|
||||
{card.icon}
|
||||
</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>
|
||||
<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>
|
||||
))}
|
||||
|
|
@ -656,18 +844,27 @@ export default function DashboardContainer() {
|
|||
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"
|
||||
}`}
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-md flex items-center justify-center
|
||||
${item.status === "Approved" ? "text-green-600" : "text-red-600"}
|
||||
${item.iconBg}`}
|
||||
>
|
||||
{item.status}
|
||||
</p>
|
||||
<p className="text-sm text-slate-700">{item.title}</p>
|
||||
<p className="text-xs text-slate-500">{item.time}</p>
|
||||
{item.icon}
|
||||
</div>
|
||||
<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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from "@/components/ui/table";
|
||||
import { Search, Filter, Eye, Pencil, Trash2, Plus } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { getArticlePagination } from "@/service/article";
|
||||
import { getArticleByCategory, getArticlePagination } from "@/service/article";
|
||||
import { formatDate } from "@/utils/global";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import Cookies from "js-cookie";
|
||||
|
|
@ -26,15 +26,28 @@ export default function NewsImage() {
|
|||
const [totalPage, setTotalPage] = useState(1);
|
||||
const [search, setSearch] = useState("");
|
||||
const [levelId, setLevelId] = useState<string | undefined>();
|
||||
const [categories, setupCategory] = useState<any[]>([]);
|
||||
const [selectedCategory, setSelectedCategory] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ulne = Cookies.get("ulne");
|
||||
setLevelId(ulne);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategory();
|
||||
}, []);
|
||||
|
||||
const fetchCategory = async () => {
|
||||
const res = await getArticleByCategory();
|
||||
if (res?.data?.data) {
|
||||
setupCategory(res.data.data);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, search]);
|
||||
}, [page, search, selectedCategory]);
|
||||
|
||||
async function fetchData() {
|
||||
loading();
|
||||
|
|
@ -42,12 +55,13 @@ export default function NewsImage() {
|
|||
const req = {
|
||||
limit: "10",
|
||||
page: page,
|
||||
search: search,
|
||||
source: "internal", // jika ingin filter image/internal
|
||||
title: search,
|
||||
source: "internal",
|
||||
categoryId: selectedCategory,
|
||||
search: "",
|
||||
sort: "desc",
|
||||
sortBy: "created_at",
|
||||
};
|
||||
|
||||
const res = await getArticlePagination(req);
|
||||
|
||||
const data = res?.data?.data || [];
|
||||
|
|
@ -58,8 +72,20 @@ export default function NewsImage() {
|
|||
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 value = status?.toLowerCase();
|
||||
const value = status.toLowerCase();
|
||||
|
||||
if (value === "published") {
|
||||
return "bg-green-100 text-green-700";
|
||||
|
|
@ -69,13 +95,26 @@ export default function NewsImage() {
|
|||
return "bg-yellow-100 text-yellow-700";
|
||||
}
|
||||
|
||||
if (value === "cancel") {
|
||||
return "bg-red-100 text-red-700";
|
||||
if (value === "draft") {
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
{/* ================= HEADER ================= */}
|
||||
|
|
@ -97,7 +136,42 @@ export default function NewsImage() {
|
|||
</Link>
|
||||
)}
|
||||
</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 ================= */}
|
||||
<div className="flex gap-3">
|
||||
<div className="relative flex-1">
|
||||
|
|
@ -121,7 +195,7 @@ export default function NewsImage() {
|
|||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Article</TableHead>
|
||||
<TableHead className="pl-3">Article</TableHead>
|
||||
<TableHead>Category</TableHead>
|
||||
<TableHead>Author</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
|
|
@ -131,10 +205,10 @@ export default function NewsImage() {
|
|||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{articles.length > 0 ? (
|
||||
articles.map((article, index) => (
|
||||
{filteredArticles.length > 0 ? (
|
||||
filteredArticles.map((article, index) => (
|
||||
<TableRow key={article.id}>
|
||||
<TableCell className="font-medium max-w-xs">
|
||||
<TableCell className="font-medium max-w-xs pl-3">
|
||||
{article.title}
|
||||
</TableCell>
|
||||
|
||||
|
|
@ -151,13 +225,19 @@ export default function NewsImage() {
|
|||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<span
|
||||
className={`px-3 py-1 text-xs rounded-full font-medium ${statusVariant(
|
||||
article.publishStatus,
|
||||
)}`}
|
||||
>
|
||||
{article.publishStatus}
|
||||
</span>
|
||||
{(() => {
|
||||
const status = getStatus(article);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`px-3 py-1 text-xs rounded-full font-medium ${statusVariant(
|
||||
status,
|
||||
)}`}
|
||||
>
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{formatDate(article.createdAt)}</TableCell>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export async function getListArticle(props: PaginationRequest) {
|
|||
isBanner,
|
||||
} = props;
|
||||
|
||||
return await httpGet(
|
||||
return await httpGetInterceptor(
|
||||
`/articles?limit=${limit}&page=${page}&isPublish=${
|
||||
isPublish === undefined ? "" : isPublish
|
||||
}&title=${search}&startDate=${startDate || ""}&endDate=${
|
||||
|
|
@ -30,7 +30,6 @@ export async function getListArticle(props: PaginationRequest) {
|
|||
}&categoryId=${category || ""}&sortBy=${sortBy || "created_at"}&sort=${
|
||||
sort || "desc"
|
||||
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}`,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -38,10 +37,10 @@ export async function getArticlePagination(props: PaginationRequest) {
|
|||
const {
|
||||
page,
|
||||
limit,
|
||||
search,
|
||||
title,
|
||||
startDate,
|
||||
endDate,
|
||||
category,
|
||||
categoryId,
|
||||
sortBy,
|
||||
sort,
|
||||
categorySlug,
|
||||
|
|
@ -51,9 +50,9 @@ export async function getArticlePagination(props: PaginationRequest) {
|
|||
} = props;
|
||||
|
||||
return await httpGet(
|
||||
`/articles?limit=${limit}&page=${page}&title=${search}&startDate=${
|
||||
`/articles?limit=${limit}&page=${page}&title=${title || ""}&startDate=${
|
||||
startDate || ""
|
||||
}&endDate=${endDate || ""}&categoryId=${category || ""}&source=${
|
||||
}&endDate=${endDate || ""}&categoryId=${categoryId || ""}&source=${
|
||||
source || ""
|
||||
}&isPublish=${isPublish !== undefined ? isPublish : ""}&sortBy=${
|
||||
sortBy || "created_at"
|
||||
|
|
|
|||
|
|
@ -306,6 +306,8 @@ export type PaginationRequest = {
|
|||
search: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
categoryId: any;
|
||||
title: string;
|
||||
isPublish?: boolean;
|
||||
category?: string;
|
||||
sortBy?: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue