From 165ccdb718db8370fd755b9119cc117ebe57648e Mon Sep 17 00:00:00 2001 From: Anang Yusman Date: Mon, 16 Mar 2026 16:08:49 +0800 Subject: [PATCH] update filter table, ui dashboard --- app/image/filter/page.tsx | 4 +- components/image/image-card.tsx | 127 +++++--- components/landing-page/option.tsx | 2 +- .../main/dashboard/dashboard-container.tsx | 273 +++++++++++++++--- components/main/news-image.tsx | 118 ++++++-- service/article.ts | 11 +- types/globals.tsx | 2 + 7 files changed, 431 insertions(+), 106 deletions(-) diff --git a/app/image/filter/page.tsx b/app/image/filter/page.tsx index e89293e..27aab52 100644 --- a/app/image/filter/page.tsx +++ b/app/image/filter/page.tsx @@ -63,8 +63,8 @@ export default function ImageFilterPage() { -
- {Array.from({ length: 6 }).map((_, i) => ( +
+ {Array.from({ length: 1 }).map((_, i) => ( ))}
diff --git a/components/image/image-card.tsx b/components/image/image-card.tsx index 74b334d..44b8c48 100644 --- a/components/image/image-card.tsx +++ b/components/image/image-card.tsx @@ -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([]); + 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 ( - -
- {/* IMAGE */} -
- news -
+
+ {articles.map((item) => ( + +
+ {/* IMAGE */} +
+ {item.title} +
- {/* CONTENT */} -
- {/* BADGE + TAG */} -
- - POLRI - - - SEPUTAR PRESTASI - + {/* CONTENT */} +
+ {/* BADGE + TAG */} +
+ + POLRI + + + {item.categoryName} + +
+ + {/* DATE */} +

+ {new Date(item.createdAt).toLocaleDateString("id-ID", { + day: "2-digit", + month: "long", + year: "numeric", + })} +

+ + {/* TITLE */} +

+ {item.title} +

+ + {/* EXCERPT */} +

+ {item.description} +

+
- - {/* DATE */} -

02 Februari 2024

- - {/* TITLE */} -

- Bharatu Mardi Hadji Gugur Saat Bertugas, Diganjar Kenaikan Pangkat - Luar Biasa -

- - {/* EXCERPT */} -

- Jakarta - Kapolri Jenderal Polisi Drs. Listyo Sigit Prabowo - memberikan kenaikan pangkat luar biasa anumerta kepada... -

-
-
- + + ))} +
); } diff --git a/components/landing-page/option.tsx b/components/landing-page/option.tsx index d446c71..ac7a05b 100644 --- a/components/landing-page/option.tsx +++ b/components/landing-page/option.tsx @@ -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 }} diff --git a/components/main/dashboard/dashboard-container.tsx b/components/main/dashboard/dashboard-container.tsx index 4ab91ff..cad2d38 100644 --- a/components/main/dashboard/dashboard-container.tsx +++ b/components/main/dashboard/dashboard-container.tsx @@ -344,24 +344,88 @@ export default function DashboardContainer() { value: 24, growth: "+12%", iconBg: "bg-blue-600", + icon: ( + + + + + + + ), }, { title: "Pending Approval", value: 8, growth: "+3", iconBg: "bg-yellow-500", + icon: ( + + + + + + ), }, { title: "Published", value: 16, growth: "+5", iconBg: "bg-green-600", + icon: ( + + + + + ), }, { title: "Rejected", value: 2, growth: "-1", iconBg: "bg-red-600", + icon: ( + + + + ), }, ]; @@ -406,20 +470,25 @@ export default function DashboardContainer() { {stats.map((card, i) => (
-
-

{card.title}

-

- {card.value} -

-
+
+
+ {card.icon} +
-

{card.growth}

-
+
+ +
+

+ {card.value} +

+

{card.title}

))} @@ -473,19 +542,19 @@ export default function DashboardContainer() {

Quick Actions

- - - - -
@@ -500,25 +569,89 @@ export default function DashboardContainer() { title: "Pending Review", value: 12, growth: "+3", - color: "bg-yellow-500", + iconBg: "bg-yellow-500", + icon: ( + + + + + + ), }, { title: "Approved Today", value: 8, growth: "+5", - color: "bg-green-600", + iconBg: "bg-green-600", + icon: ( + + + + + ), }, { title: "Total Published", value: 156, growth: "+12%", - color: "bg-blue-600", + iconBg: "bg-blue-600", + icon: ( + + + + + + + ), }, { title: "Rejected", value: 5, growth: "-1", - color: "bg-red-600", + iconBg: "bg-red-600", + icon: ( + + + + ), }, ]; @@ -544,16 +677,66 @@ export default function DashboardContainer() { status: "Approved", title: "Technology Summit Event", time: "10 mins ago", + iconBg: "bg-[#DCFCE7]", + icon: ( + + + + + + + ), }, { status: "Rejected", title: "Product Update Draft", time: "25 mins ago", + iconBg: "bg-[#FFE2E2]", + icon: ( + + + + ), }, { status: "Approved", title: "Partner Logo Update", time: "1 hour ago", + iconBg: "bg-[#DCFCE7]", + icon: ( + + + + + + + ), }, ]; @@ -574,20 +757,25 @@ export default function DashboardContainer() { {stats.map((card, i) => (
-
-

{card.title}

-

- {card.value} -

-
+
+
+ {card.icon} +
-

{card.growth}

-
+
+ +
+

+ {card.value} +

+

{card.title}

))} @@ -656,18 +844,27 @@ export default function DashboardContainer() { key={i} className="border rounded-xl p-4 flex justify-between items-center" > -
-

+

- {item.status} -

-

{item.title}

-

{item.time}

+ {item.icon} +
+
+

+ {item.status} +

+

{item.title}

+

{item.time}

+
))} diff --git a/components/main/news-image.tsx b/components/main/news-image.tsx index 826ecb9..db941fa 100644 --- a/components/main/news-image.tsx +++ b/components/main/news-image.tsx @@ -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(); + const [categories, setupCategory] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(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 (
{/* ================= HEADER ================= */} @@ -97,7 +136,42 @@ export default function NewsImage() { )}
+
+ + {categories.map((cat: any) => ( + + ))} +
{/* ================= SEARCH ================= */}
@@ -121,7 +195,7 @@ export default function NewsImage() { - Article + Article Category Author Status @@ -131,10 +205,10 @@ export default function NewsImage() { - {articles.length > 0 ? ( - articles.map((article, index) => ( + {filteredArticles.length > 0 ? ( + filteredArticles.map((article, index) => ( - + {article.title} @@ -151,13 +225,19 @@ export default function NewsImage() { - - {article.publishStatus} - + {(() => { + const status = getStatus(article); + + return ( + + {status} + + ); + })()} {formatDate(article.createdAt)} diff --git a/service/article.ts b/service/article.ts index b0ae2dc..f8a5e4c 100644 --- a/service/article.ts +++ b/service/article.ts @@ -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" diff --git a/types/globals.tsx b/types/globals.tsx index 42a9bdc..067ba47 100644 --- a/types/globals.tsx +++ b/types/globals.tsx @@ -306,6 +306,8 @@ export type PaginationRequest = { search: string; startDate?: string; endDate?: string; + categoryId: any; + title: string; isPublish?: boolean; category?: string; sortBy?: string;