211 lines
6.4 KiB
TypeScript
211 lines
6.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} 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 { formatDate } from "@/utils/global";
|
|
import { close, loading } from "@/config/swal";
|
|
|
|
export default function NewsImage() {
|
|
const [articles, setArticles] = useState<any[]>([]);
|
|
const [page, setPage] = useState(1);
|
|
const [totalPage, setTotalPage] = useState(1);
|
|
const [search, setSearch] = useState("");
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [page, search]);
|
|
|
|
async function fetchData() {
|
|
loading();
|
|
|
|
const req = {
|
|
limit: "10",
|
|
page: page,
|
|
search: search,
|
|
source: "internal", // jika ingin filter image/internal
|
|
sort: "desc",
|
|
sortBy: "created_at",
|
|
};
|
|
|
|
const res = await getArticlePagination(req);
|
|
|
|
const data = res?.data?.data || [];
|
|
|
|
setArticles(data);
|
|
setTotalPage(res?.data?.meta?.totalPage || 1);
|
|
|
|
close();
|
|
}
|
|
|
|
const statusVariant = (status: string) => {
|
|
switch (status?.toLowerCase()) {
|
|
case "publish":
|
|
return "bg-green-100 text-green-700";
|
|
case "pending":
|
|
return "bg-yellow-100 text-yellow-700";
|
|
case "draft":
|
|
return "bg-gray-200 text-gray-600";
|
|
case "reject":
|
|
return "bg-red-100 text-red-600";
|
|
default:
|
|
return "bg-gray-200 text-gray-600";
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* ================= HEADER ================= */}
|
|
<div className="flex items-start justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold text-slate-800">
|
|
News & Articles
|
|
</h1>
|
|
<p className="text-sm text-slate-500 mt-1">
|
|
Create and manage news articles and blog posts
|
|
</p>
|
|
</div>
|
|
<Link href={"/admin/news-article/image/create"}>
|
|
<Button className="bg-blue-600 hover:bg-blue-700 rounded-lg">
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
Create New Article
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* ================= SEARCH ================= */}
|
|
<div className="flex gap-3">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-3 top-3 w-4 h-4 text-slate-400" />
|
|
<Input
|
|
placeholder="Search articles..."
|
|
className="pl-9"
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<Button variant="outline" className="rounded-lg">
|
|
<Filter className="w-4 h-4 mr-2" />
|
|
Filters
|
|
</Button>
|
|
</div>
|
|
|
|
{/* ================= TABLE ================= */}
|
|
<Card className="rounded-2xl border shadow-sm">
|
|
<CardContent className="p-0">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Article</TableHead>
|
|
<TableHead>Category</TableHead>
|
|
<TableHead>Author</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Date</TableHead>
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
{articles.length > 0 ? (
|
|
articles.map((article, index) => (
|
|
<TableRow key={article.id}>
|
|
<TableCell className="font-medium max-w-xs">
|
|
{article.title}
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
<Badge variant="secondary">
|
|
{article?.categories
|
|
?.map((c: any) => c.title)
|
|
.join(", ")}
|
|
</Badge>
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
{article.customCreatorName || article.createdByName}
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
<span
|
|
className={`px-3 py-1 text-xs rounded-full font-medium ${statusVariant(
|
|
article.publishStatus,
|
|
)}`}
|
|
>
|
|
{article.publishStatus}
|
|
</span>
|
|
</TableCell>
|
|
|
|
<TableCell>{formatDate(article.createdAt)}</TableCell>
|
|
|
|
<TableCell className="text-right space-x-2">
|
|
<Button size="icon" variant="ghost">
|
|
<Eye className="w-4 h-4" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost">
|
|
<Pencil className="w-4 h-4" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost">
|
|
<Trash2 className="w-4 h-4 text-red-500" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
) : (
|
|
<TableRow>
|
|
<TableCell colSpan={6} className="text-center py-4">
|
|
No data available
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
|
|
{/* ================= PAGINATION ================= */}
|
|
<div className="flex items-center justify-between p-4 border-t text-sm text-slate-500">
|
|
<p>
|
|
Page {page} of {totalPage}
|
|
</p>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={page === 1}
|
|
onClick={() => setPage(page - 1)}
|
|
>
|
|
Previous
|
|
</Button>
|
|
|
|
<Button size="sm" className="bg-blue-600">
|
|
{page}
|
|
</Button>
|
|
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={page === totalPage}
|
|
onClick={() => setPage(page + 1)}
|
|
>
|
|
Next
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|