398 lines
12 KiB
TypeScript
398 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Image from "next/image";
|
|
import { Button } from "@/components/ui/button";
|
|
import { ThumbsUp, ThumbsDown, Trash2 } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { getBookmarks, toggleBookmarkById, BookmarkItem } from "@/service/content";
|
|
import { getCookiesDecrypt } from "@/lib/utils";
|
|
import Swal from "sweetalert2";
|
|
import withReactContent from "sweetalert2-react-content";
|
|
|
|
// Format tanggal
|
|
function formatTanggal(dateString: string) {
|
|
if (!dateString) return "";
|
|
return (
|
|
new Date(dateString)
|
|
.toLocaleString("id-ID", {
|
|
day: "2-digit",
|
|
month: "short",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
hour12: false,
|
|
timeZone: "Asia/Jakarta",
|
|
})
|
|
.replace(/\./g, ":") + " WIB"
|
|
);
|
|
}
|
|
|
|
// Function to get link based on typeId
|
|
function getLink(item: BookmarkItem) {
|
|
switch (item.article?.typeId) {
|
|
case 1:
|
|
return `/content/image/detail/${item.article?.id}`;
|
|
case 2:
|
|
return `/content/video/detail/${item.article?.id}`;
|
|
case 3:
|
|
return `/content/text/detail/${item.article?.id}`;
|
|
case 4:
|
|
return `/content/audio/detail/${item.article?.id}`;
|
|
default:
|
|
return "#";
|
|
}
|
|
}
|
|
|
|
// Function to get content type label
|
|
function getContentTypeLabel(typeId: number) {
|
|
switch (typeId) {
|
|
case 1:
|
|
return "📸 Image";
|
|
case 2:
|
|
return "🎬 Video";
|
|
case 3:
|
|
return "📝 Text";
|
|
case 4:
|
|
return "🎵 Audio";
|
|
default:
|
|
return "📄 Content";
|
|
}
|
|
}
|
|
|
|
type PublicationCardGridProps = {
|
|
selectedCategory: string;
|
|
title?: string;
|
|
refresh?: boolean;
|
|
categoryFilter?: string[];
|
|
formatFilter?: string[];
|
|
isInstitute?: boolean;
|
|
instituteId?: string;
|
|
sortBy?: string;
|
|
};
|
|
|
|
export default function ForYouCardGrid({
|
|
selectedCategory,
|
|
title,
|
|
refresh,
|
|
isInstitute = false,
|
|
instituteId = "",
|
|
}: PublicationCardGridProps) {
|
|
const [bookmarks, setBookmarks] = useState<BookmarkItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [totalCount, setTotalCount] = useState(0);
|
|
const [limit] = useState(12);
|
|
const MySwal = withReactContent(Swal);
|
|
|
|
useEffect(() => {
|
|
fetchBookmarks();
|
|
}, [currentPage, selectedCategory, title, refresh]);
|
|
|
|
const fetchBookmarks = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await getBookmarks(currentPage, limit);
|
|
|
|
if (!response?.error) {
|
|
setBookmarks(response.data?.data || []);
|
|
setTotalPages(response.data?.meta?.totalPage || 1);
|
|
setTotalCount(response.data?.meta?.count || 0);
|
|
} else {
|
|
console.error("Failed to fetch bookmarks:", response?.error);
|
|
MySwal.fire({
|
|
icon: "error",
|
|
title: "Error",
|
|
text: "Gagal memuat bookmark.",
|
|
confirmButtonColor: "#d33",
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error("Error fetching bookmarks:", err);
|
|
MySwal.fire({
|
|
icon: "error",
|
|
title: "Error",
|
|
text: "Terjadi kesalahan saat memuat bookmark.",
|
|
confirmButtonColor: "#d33",
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDeleteBookmark = async (bookmarkId: number, articleId: number, articleTitle: string) => {
|
|
const result = await MySwal.fire({
|
|
title: "Hapus Bookmark",
|
|
text: `Apakah Anda yakin ingin menghapus "${articleTitle}" dari bookmark?`,
|
|
icon: "warning",
|
|
showCancelButton: true,
|
|
confirmButtonColor: "#d33",
|
|
cancelButtonColor: "#3085d6",
|
|
confirmButtonText: "Ya, Hapus!",
|
|
cancelButtonText: "Batal",
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
try {
|
|
const response = await toggleBookmarkById(articleId);
|
|
|
|
if (response?.data?.success || !response?.error) {
|
|
// Remove from local state
|
|
setBookmarks(prev => prev.filter(bookmark => bookmark.id !== bookmarkId));
|
|
setTotalCount(prev => prev - 1);
|
|
|
|
MySwal.fire({
|
|
icon: "success",
|
|
title: "Berhasil",
|
|
text: "Bookmark berhasil dihapus.",
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
});
|
|
} else {
|
|
MySwal.fire({
|
|
icon: "error",
|
|
title: "Gagal",
|
|
text: "Gagal menghapus bookmark.",
|
|
confirmButtonColor: "#d33",
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error("Error deleting bookmark:", err);
|
|
MySwal.fire({
|
|
icon: "error",
|
|
title: "Error",
|
|
text: "Terjadi kesalahan saat menghapus bookmark.",
|
|
confirmButtonColor: "#d33",
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const goToPage = (page: number) => {
|
|
setCurrentPage(page);
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{Array.from({ length: 6 }).map((_, index) => (
|
|
<div key={index} className="rounded-xl shadow-md overflow-hidden bg-white animate-pulse">
|
|
<div className="w-full h-[204px] bg-gray-200"></div>
|
|
<div className="p-3 space-y-2">
|
|
<div className="h-4 bg-gray-200 rounded"></div>
|
|
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
|
|
<div className="h-4 bg-gray-200 rounded"></div>
|
|
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (bookmarks.length === 0) {
|
|
return (
|
|
<div className="text-center py-12">
|
|
<div className="text-gray-400 text-6xl mb-4">📚</div>
|
|
<h3 className="text-xl font-semibold text-gray-600 mb-2">Tidak Ada Bookmark</h3>
|
|
<p className="text-gray-500">Anda belum menyimpan artikel apapun ke bookmark.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="mb-6">
|
|
<h2 className="text-2xl font-semibold text-gray-800 mb-2">Bookmark Saya</h2>
|
|
<p className="text-gray-600">Total {totalCount} artikel tersimpan</p>
|
|
</div>
|
|
|
|
{/* Grid Card */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{bookmarks.map((bookmark) => (
|
|
<div key={bookmark.id} className="rounded-xl shadow-md overflow-hidden bg-white hover:shadow-lg transition-shadow">
|
|
<div className="w-full h-[204px] relative">
|
|
<Link href={getLink(bookmark)}>
|
|
<Image
|
|
src={bookmark.article?.thumbnailUrl || "/placeholder.png"}
|
|
alt={bookmark.article?.title || "No Title"}
|
|
fill
|
|
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
|
|
/>
|
|
</Link>
|
|
</div>
|
|
<div className="p-3">
|
|
<div className="flex gap-2 mb-1">
|
|
<span className="text-xs text-white px-2 py-0.5 rounded bg-blue-600">
|
|
{getContentTypeLabel(bookmark.article?.typeId || 0)}
|
|
</span>
|
|
{/* <span className="text-xs font-medium text-[#b3882e]">
|
|
Bookmark
|
|
</span> */}
|
|
</div>
|
|
<p className="text-xs text-gray-500 mb-1">
|
|
Disimpan: {formatTanggal(bookmark.createdAt)}
|
|
</p>
|
|
<Link href={getLink(bookmark)}>
|
|
<p className="text-sm font-semibold mb-3 line-clamp-2 cursor-pointer hover:text-blue-600 transition-colors">
|
|
{bookmark.article?.title}
|
|
</p>
|
|
</Link>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex gap-2 text-gray-600">
|
|
<ThumbsUp className="w-4 h-4 cursor-pointer hover:text-green-600" />
|
|
<ThumbsDown className="w-4 h-4 cursor-pointer hover:text-red-600" />
|
|
</div>
|
|
<Button
|
|
onClick={() => handleDeleteBookmark(bookmark.id, bookmark.article?.id || 0, bookmark.article?.title || "")}
|
|
variant="outline"
|
|
size="sm"
|
|
className="rounded px-3 text-red-600 border-red-200 hover:bg-red-50 hover:border-red-300"
|
|
>
|
|
<Trash2 className="w-4 h-4 mr-1" />
|
|
Hapus
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Pagination */}
|
|
<div className="flex justify-center gap-2">
|
|
{Array.from({ length: totalPages }, (_, i) => (
|
|
<Button
|
|
key={i}
|
|
variant={currentPage === i + 1 ? "default" : "outline"}
|
|
size="sm"
|
|
onClick={() => goToPage(i + 1)}
|
|
>
|
|
{i + 1}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// "use client";
|
|
|
|
// import { useEffect, useState } from "react";
|
|
// import PublicationKlFilter from "./publication-filter";
|
|
// import { Button } from "@/components/ui/button";
|
|
// import { mediaWishlist } from "@/service/landing/landing";
|
|
|
|
// const itemsPerPage = 9;
|
|
|
|
// type PublicationCardGridProps = {
|
|
// selectedCategory: string;
|
|
// title?: string;
|
|
// refresh?: boolean;
|
|
// categoryFilter?: string[];
|
|
// formatFilter?: string[];
|
|
// isInstitute?: boolean;
|
|
// instituteId?: string;
|
|
// sortBy?: string;
|
|
// };
|
|
|
|
// export default function ForYouCardGrid({
|
|
// selectedCategory,
|
|
// title,
|
|
// refresh,
|
|
|
|
// isInstitute = false,
|
|
// instituteId = "",
|
|
// }: PublicationCardGridProps) {
|
|
// const [currentPage, setCurrentPage] = useState(0);
|
|
// const [contentImage, setContentImage] = useState([]);
|
|
// const [totalPages, setTotalPages] = useState(1);
|
|
// const [categoryFilter] = useState([]);
|
|
// const [formatFilter] = useState([]);
|
|
// const [sortBy] = useState();
|
|
|
|
// useEffect(() => {
|
|
// getDataImage();
|
|
// }, [currentPage, selectedCategory, title, refresh]);
|
|
|
|
// async function getDataImage() {
|
|
// const filter =
|
|
// categoryFilter?.length > 0
|
|
// ? categoryFilter?.sort().join(",")
|
|
// : selectedCategory || "";
|
|
|
|
// const name = title ?? "";
|
|
// const format = formatFilter?.join(",") ?? "";
|
|
|
|
// const response = await mediaWishlist(
|
|
// "1",
|
|
// isInstitute ? instituteId : "",
|
|
// name,
|
|
// filter,
|
|
// "12",
|
|
// currentPage,
|
|
// sortBy,
|
|
// format
|
|
// );
|
|
|
|
// setTotalPages(response?.data?.data?.totalPages || 1);
|
|
// setContentImage(response?.data?.data?.content || []);
|
|
// }
|
|
|
|
// const goToPage = (page: number) => {
|
|
// setCurrentPage(page);
|
|
// };
|
|
|
|
// return (
|
|
// <div className="space-y-6">
|
|
// {/* Grid Card */}
|
|
// <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
// {contentImage.length === 0 ? (
|
|
// <div className="col-span-3 text-center text-muted-foreground">
|
|
// Tidak ada konten ditemukan.
|
|
// </div>
|
|
// ) : (
|
|
// contentImage.map((item: any, idx: number) => (
|
|
// <PublicationKlFilter
|
|
// key={item.id}
|
|
// id={item.id} // ✅ tambahkan ini
|
|
// image={item.mediaUpload?.smallThumbnailLink}
|
|
// label={item.mediaType?.name ?? "UNKNOWN"}
|
|
// category={item.tags ?? "-"}
|
|
// date={new Date(item?.mediaUpload?.createdAt).toLocaleString(
|
|
// "id-ID",
|
|
// {
|
|
// day: "2-digit",
|
|
// month: "short",
|
|
// year: "numeric",
|
|
// hour: "2-digit",
|
|
// minute: "2-digit",
|
|
// timeZoneName: "short",
|
|
// }
|
|
// )}
|
|
// title={item?.mediaUpload?.title}
|
|
// description={""} // Optional: Tambahkan jika tersedia dari API
|
|
// />
|
|
// ))
|
|
// )}
|
|
// </div>
|
|
|
|
// {/* Pagination */}
|
|
// <div className="flex justify-center gap-2">
|
|
// {Array.from({ length: totalPages }, (_, i) => (
|
|
// <Button
|
|
// key={i}
|
|
// variant={currentPage === i + 1 ? "default" : "outline"}
|
|
// size="sm"
|
|
// onClick={() => goToPage(i + 1)}
|
|
// >
|
|
// {i + 1}
|
|
// </Button>
|
|
// ))}
|
|
// </div>
|
|
// </div>
|
|
// );
|
|
// }
|