skl/resources/js/pages/admin/post/index_post.tsx

285 lines
14 KiB
TypeScript

import React, { useEffect, useState } from "react";
import { Link, useForm, usePage } from "@inertiajs/react";
import { PageProps } from "@/types";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Table,
TableHeader,
TableRow,
TableHead,
TableBody,
TableCell,
} from "@/components/ui/table";
import { useToast } from "@/hooks/use-toast";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import {
Search,
Plus,
Pencil,
Trash2,
ChevronLeft,
ChevronRight,
} from "lucide-react";
import AuthenticatedLayout from "@/layouts/authenticated-layout";
import { Head } from "@inertiajs/react";
import { Toaster } from "@/components/ui/toaster";
import hasAnyPermission from "@/utils/hasAnyPermission";
interface SubKategori {
SubKategoriId: number;
NamaSubKategori: string;
}
interface Kategori {
KategoriId: number;
NamaKategori: string;
}
interface Posting {
PostId: number | null;
JudulPost: string;
SubKategoriId: number | null;
IsPublish: boolean;
kategori?: Kategori;
subkategori?: SubKategori;
}
const ITEMS_PER_PAGE = 5;
export default function PostIndex({
posts = [],
}: PageProps<{ posts: Posting[] }>) {
const { auth } = usePage().props;
const userPermissions = auth?.user?.permissions ?? [];
const { toast } = useToast();
const [currentPage, setCurrentPage] = useState(1);
const { delete: destroy } = useForm({});
const [search, setSearch] = useState("");
const [filteredPosting, setFilteredPosting] = useState(posts);
useEffect(() => {
let filtered = posts;
if (search) {
filtered = filtered.filter((posting) =>
posting.JudulPost.toLowerCase().includes(search.toLowerCase())
);
}
setFilteredPosting(filtered);
}, [posts, search]);
const totalPages = Math.ceil(filteredPosting.length / ITEMS_PER_PAGE);
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;
const currentItems = filteredPosting.slice(startIndex, endIndex);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
const handleDelete = (PostId: number) => {
if (confirm("Apakah Anda yakin ingin menghapus postingan ini?")) {
destroy(`/admin/post/${PostId}`, {
onSuccess: () =>
toast({
title: "Berhasil",
description: "Post berhasil dihapus",
variant: "default",
}),
onError: () =>
toast({
title: "Gagal",
description: "Terjadi kesalahan saat menghapus Post",
variant: "destructive",
}),
});
}
};
return (
<AuthenticatedLayout header="Daftar Postingan">
<Head title="Daftar Postingan" />
<div className="container mx-auto p-4">
<Card className="shadow-lg">
<CardHeader className="flex flex-col md:flex-row justify-between items-center">
<Input
type="text"
placeholder="Cari Post..."
value={search}
onChange={handleSearch}
className="w-70 md:w-96 border-gray-200 rounded-lg"
/>
<Link href="/admin/post/add">
<Button className="bg-blue-600 text-white flex items-center gap-2">
<Plus className="h-4 w-4" />
Tambah Post
</Button>
</Link>
</CardHeader>
<CardContent>
<div className="overflow-auto rounded-md border">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
<TableHead className="w-[50px]">
No
</TableHead>
<TableHead className="min-w-[200px]">
Judul
</TableHead>
<TableHead className="min-w-[200px]">
Kategori
</TableHead>
<TableHead className="min-w-[100px]">
Status
</TableHead>
<TableHead className="min-w-[150px]">
Aksi
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{currentItems.length > 0 ? (
currentItems.map((posting, index) => (
<TableRow key={posting.PostId}>
<TableCell className="w-[50px]">
{startIndex + index + 1}
</TableCell>
<TableCell className="min-w-[200px]">
{posting.JudulPost}
</TableCell>
<TableCell className="min-w-[200px]">
<div className="flex flex-col gap-1">
<div className="bg-green-700 rounded-xl px-2 py-1 text-white text-xs w-fit">
{
posting.kategori
?.NamaKategori
}
</div>
<span className="text-sm text-gray-600">
{
posting
.subkategori
?.NamaSubKategori
}
</span>
</div>
</TableCell>
<TableCell className="min-w-[100px]">
{posting.IsPublish ? (
<span className="bg-green-100 text-green-800 text-xs font-medium px-2.5 py-0.5 rounded">
Published
</span>
) : (
<span className="bg-yellow-100 text-yellow-800 text-xs font-medium px-2.5 py-0.5 rounded">
Draft
</span>
)}
</TableCell>
<TableCell className="min-w-[150px]">
<div className="flex gap-2">
<Link
href={`/admin/post/${posting.PostId}`}
>
<Button
variant="outline"
className="flex items-center gap-2"
size="sm"
>
<Pencil className="h-4 w-4" />
<span className="hidden sm:inline">
Edit
</span>
</Button>
</Link>
<Button
onClick={() =>
handleDelete(
posting.PostId!
)
}
variant="destructive"
className="flex items-center gap-2"
size="sm"
>
<Trash2 className="h-4 w-4" />
<span className="hidden sm:inline">
Delete
</span>
</Button>
</div>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={5}
className="text-center py-4 text-gray-500"
>
Tidak ada data
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-between mt-4">
<div className="text-sm text-gray-500">
Showing {startIndex + 1} to{" "}
{Math.min(endIndex, filteredPosting.length)} of{" "}
{filteredPosting.length} entries
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() =>
handlePageChange(currentPage - 1)
}
disabled={currentPage === 1}
>
<ChevronLeft className="h-4 w-4" />
</Button>
{Array.from(
{ length: totalPages },
(_, i) => i + 1
).map((page) => (
<Button
key={page}
variant={
currentPage === page
? "default"
: "outline"
}
size="sm"
onClick={() => handlePageChange(page)}
>
{page}
</Button>
))}
<Button
variant="outline"
size="sm"
onClick={() =>
handlePageChange(currentPage + 1)
}
disabled={currentPage === totalPages}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
<Toaster />
</AuthenticatedLayout>
);
}