285 lines
14 KiB
TypeScript
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>
|
|
);
|
|
}
|