jaecoo-cihampelas/components/table/galery.tsx

244 lines
7.1 KiB
TypeScript
Raw Normal View History

2026-01-07 08:06:07 +00:00
"use client";
import { useEffect, useState } from "react";
import Image from "next/image";
import { Eye, Pencil, Trash2, Calendar, MapPin } from "lucide-react";
import {
deleteGalery,
getGaleryById,
getGaleryData,
getGaleryFileData,
} from "@/service/galery";
import { DialogDetailGaleri } from "../dialog/galery-detail-dialog";
import { DialogUpdateGaleri } from "../dialog/galery-update-dialog";
import { error, success } from "@/config/swal";
import withReactContent from "sweetalert2-react-content";
import Swal from "sweetalert2";
export default function Galery() {
const MySwal = withReactContent(Swal);
const [data, setData] = useState<any[]>([]);
const [page, setPage] = useState(1);
const [showData, setShowData] = useState("10");
const [search, setSearch] = useState("");
const [showDetail, setShowDetail] = useState(false);
const [detailData, setDetailData] = useState<any>(null);
const [showEdit, setShowEdit] = useState(false);
const [editData, setEditData] = useState<any>(null);
const fetchData = async () => {
try {
const req = {
limit: showData,
page: page,
search: search,
};
const res = await getGaleryData(req);
const list = res?.data?.data ?? [];
// Ambil gambar yang sesuai gallery_id dan gabungkan ke data galeri
const merged = await Promise.all(
list.map(async (item: any) => {
try {
const filesRes = await getGaleryFileData(item.id);
const images = filesRes?.data?.data ?? [];
// Filter file dengan gallery_id sama dengan item.id
const filteredImages = images?.filter(
(img: any) => img.gallery_id === item.id
);
// Ambil gambar pertama sebagai thumbnail
const coverImage =
filteredImages.length > 0 ? filteredImages[0].image_url : null;
return {
...item,
image_url: coverImage,
};
} catch (e) {
return { ...item, image_url: null };
}
})
);
setData(merged);
} catch (error) {
console.error("Error fetch galeri:", error);
}
};
useEffect(() => {
fetchData();
}, [page, showData, search]);
const openDetail = async (id: number) => {
try {
const res = await getGaleryById(id);
setDetailData(res?.data?.data);
setShowDetail(true);
} catch (err) {
console.error("Error get detail:", err);
}
};
const openEdit = async (id: number) => {
try {
const res = await getGaleryById(id);
setEditData(res?.data?.data);
setShowEdit(true);
} catch (error) {
console.error("Error open edit:", error);
}
};
async function doDelete(id: any) {
const resDelete = await deleteGalery(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
success("Berhasil Hapus");
fetchData();
}
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
return (
<div className="mt-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
{data?.map((item: any) => (
<div
key={item.id}
className="bg-white shadow-md rounded-2xl overflow-hidden border"
>
{/* Image */}
<div className="relative w-full h-48">
{item.image_url ? (
<Image
src={item.image_url}
alt={item.title}
fill
className="object-cover"
/>
) : (
<div className="flex items-center justify-center h-full bg-gray-100 text-gray-400">
No Image
</div>
)}
{/* Status Badge */}
<span
className={`absolute top-3 left-3 text-xs px-3 py-1 rounded-full font-medium
${
item.is_active
? "bg-green-100 text-green-700"
: "bg-red-100 text-red-600"
}`}
>
{item.is_active ? "Aktif" : "Tidak Aktif"}
</span>
</div>
{/* Content */}
<div className="p-4 space-y-3">
<h2 className="text-[#1F6779] text-lg font-semibold">
{item.title}
</h2>
<p className="text-gray-600 text-sm">{item.description ?? "-"}</p>
<div className="flex items-center gap-2 text-sm text-gray-700">
<Calendar className="h-4 w-4" />{" "}
{new Date(item.created_at).toLocaleDateString("id-ID")}
</div>
<div className="flex items-center gap-2 text-sm text-gray-700">
<MapPin className="h-4 w-4" /> {item.location ?? "-"}
</div>
</div>
{/* Footer Actions */}
<div className="border-t px-4 py-3 flex items-center justify-between text-sm">
<button
onClick={() => openDetail(item.id)}
className="flex items-center gap-1 text-gray-700 hover:text-[#1F6779] transition"
>
<Eye className="h-4 w-4" /> Lihat
</button>
<button
onClick={() => openEdit(item.id)}
className="flex items-center gap-1 text-gray-700 hover:text-[#1F6779] transition"
>
<Pencil className="h-4 w-4" /> Edit
</button>
<button
className="flex items-center gap-1 text-red-600 hover:text-red-700 transition"
onClick={() => handleDelete(item.id)}
>
<Trash2 className="h-4 w-4" /> Hapus
</button>
</div>
</div>
))}
</div>
{/* Pagination */}
<div className="flex items-center justify-between mt-6 text-sm">
<p>Menampilkan {data.length} data</p>
<div className="flex items-center gap-3">
<button
className="px-3 py-1 border rounded-md bg-gray-100 text-gray-700 disabled:opacity-50"
disabled={page === 1}
onClick={() => setPage((prev) => prev - 1)}
>
Previous
</button>
<button
className="px-3 py-1 border rounded-md bg-gray-100 text-gray-700"
onClick={() => setPage((prev) => prev + 1)}
>
Next
</button>
</div>
</div>
{showDetail && detailData && (
<DialogDetailGaleri
open={showDetail}
onClose={() => setShowDetail(false)}
data={detailData}
/>
)}
{showEdit && editData && (
<DialogUpdateGaleri
open={showEdit}
onClose={() => setShowEdit(false)}
data={editData}
onUpdated={fetchData}
/>
)}
</div>
);
}