kontenhumas-fe/components/landing-page/media-update.tsx

476 lines
16 KiB
TypeScript
Raw Normal View History

2025-09-16 08:29:07 +00:00
"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import { Card } from "../ui/card";
import Link from "next/link";
import { useRouter, useParams } from "next/navigation";
import { listData, listArticles } from "@/service/landing/landing";
import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content";
import { getCookiesDecrypt } from "@/lib/utils";
2025-09-16 08:29:07 +00:00
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/navigation";
import { Navigation } from "swiper/modules";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
2025-10-12 15:12:07 +00:00
import { ThumbsUp, ThumbsDown } from "lucide-react";
2025-09-16 08:29:07 +00:00
2025-10-12 15:12:07 +00:00
// 🔹 Fungsi format tanggal ke WIB
2025-09-16 08:29:07 +00:00
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"
);
}
export default function MediaUpdate() {
const [tab, setTab] = useState<"latest" | "popular">("latest");
2025-10-12 15:34:45 +00:00
const [contentType, setContentType] = useState<
"audiovisual" | "audio" | "foto" | "text" | "all"
>("foto");
2025-09-16 08:29:07 +00:00
const [dataToRender, setDataToRender] = useState<any[]>([]);
const [filteredData, setFilteredData] = useState<any[]>([]);
const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
2025-09-16 08:29:07 +00:00
const [loading, setLoading] = useState(true);
const [currentTypeId, setCurrentTypeId] = useState<string>("1");
const router = useRouter();
const params = useParams();
const MySwal = withReactContent(Swal);
2025-09-16 08:29:07 +00:00
// Get slug from URL params
const slug = params?.slug as string;
2025-10-12 15:12:07 +00:00
2025-09-16 08:29:07 +00:00
useEffect(() => {
fetchData(tab);
}, [tab, slug]);
2025-09-16 08:29:07 +00:00
useEffect(() => {
// if (contentType !== "all") {
// fetchData(tab);
// } else {
// filterDataByContentType();
// }
fetchData(tab);
}, [contentType]);
useEffect(() => {
filterDataByContentType();
}, [dataToRender]);
// Function to get typeId based on content type
function getTypeIdByContentType(contentType: string): string {
switch (contentType) {
case "audiovisual":
return "2"; // Video
case "foto":
return "1"; // Image
case "audio":
return "4"; // Audio
case "text":
return "3"; // Text
default:
return "1"; // Default to Image
}
}
// Function to get link based on typeId (same as header.tsx)
function getLink(item: any) {
switch (item?.typeId) {
case 1:
return `/content/image/detail/${item?.id}`;
case 2:
return `/content/video/detail/${item?.id}`;
case 3:
return `/content/text/detail/${item?.id}`;
case 4:
return `/content/audio/detail/${item?.id}`;
default:
return "#";
}
}
2025-10-12 14:57:46 +00:00
// Function to get content type link for "Lihat lebih banyak" button
function getContentTypeLink() {
switch (contentType) {
case "audio":
return "/tenant/" + slug + "/content/audio";
2025-10-12 14:57:46 +00:00
case "foto":
return "/tenant/" + slug + "/content/image";
2025-10-12 14:57:46 +00:00
case "audiovisual":
return "/tenant/" + slug + "/content/video";
2025-10-12 14:57:46 +00:00
case "text":
return "/tenant/" + slug + "/content/text";
2025-10-12 14:57:46 +00:00
default:
return "/tenant/" + slug + "/content/image"; // Default to image page
2025-10-12 14:57:46 +00:00
}
}
// Function to filter data by content type
function filterDataByContentType() {
// if (contentType === "all") {
// setFilteredData(dataToRender);
// return;
// }
const filtered = dataToRender.filter((item) => {
// Determine content type based on item properties
const hasVideo = item.videoUrl || item.videoPath;
const hasAudio = item.audioUrl || item.audioPath;
const hasImage = item.smallThumbnailLink || item.thumbnailUrl || item.imageUrl;
const hasText = item.content || item.description;
switch (contentType) {
case "audiovisual":
return hasVideo && (hasAudio || hasImage);
case "audio":
return hasAudio && !hasVideo;
case "foto":
return hasImage && !hasVideo && !hasAudio;
case "text":
return hasText && !hasVideo && !hasAudio && !hasImage;
default:
return true;
}
});
setFilteredData(filtered);
}
2025-09-16 08:29:07 +00:00
async function fetchData(section: "latest" | "popular") {
try {
setLoading(true);
// Determine typeId based on contentType
const typeId = parseInt(getTypeIdByContentType(contentType));
setCurrentTypeId(typeId.toString());
2025-10-12 15:12:07 +00:00
// 🔹 Ambil data artikel
const response = await listArticles(
1,
10,
typeId, // Dynamic typeId based on content type
undefined,
undefined,
section === "latest" ? "createdAt" : "viewCount",
slug
2025-09-16 08:29:07 +00:00
);
2025-10-12 15:12:07 +00:00
let hasil: any[] = [];
if (response?.error) {
console.error("Articles API failed, fallback ke old API");
const fallbackRes = await listData(
typeId.toString(),
"",
"",
20,
0,
2025-10-12 15:34:45 +00:00
// urutan === "latest" ? "createdAt" : "clickCount",
"",
"",
""
);
2025-10-12 15:34:45 +00:00
// hasil = fallback?.data?.data?.content || [];
} else {
2025-10-12 15:12:07 +00:00
hasil = response?.data?.data || [];
}
// 🔹 Normalisasi struktur data
2025-10-12 16:27:46 +00:00
let transformedData = hasil.map((article: any) => ({
id: article.id,
title: article.title,
category:
article.categoryName ||
(article.categories && article.categories[0]?.title) ||
"Tanpa Kategori",
createdAt: article.createdAt,
smallThumbnailLink: article.thumbnailUrl,
label: article.categoryName,
...article,
}));
2025-10-12 15:34:45 +00:00
// setDataKonten(dataBaru);
2025-10-12 15:12:07 +00:00
// 🔹 Sinkronisasi data bookmark
const roleId = Number(getCookiesDecrypt("urie"));
if (roleId && !isNaN(roleId)) {
2025-10-12 15:12:07 +00:00
const simpananLocal = localStorage.getItem("bookmarkedIds");
let localSet = new Set<number>();
2025-10-12 15:12:07 +00:00
if (simpananLocal) {
localSet = new Set(JSON.parse(simpananLocal));
setBookmarkedIds(localSet);
}
const res = await getBookmarkSummaryForUser();
const bookmarks =
res?.data?.data?.recentBookmarks ||
res?.data?.data?.bookmarks ||
res?.data?.data ||
[];
const ids = new Set<number>(
(Array.isArray(bookmarks) ? bookmarks : [])
.map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
.filter((x) => !isNaN(x))
);
2025-10-12 15:12:07 +00:00
const gabungan = new Set([...localSet, ...ids]);
setBookmarkedIds(gabungan);
localStorage.setItem(
"bookmarkedIds",
2025-10-12 15:12:07 +00:00
JSON.stringify(Array.from(gabungan))
);
}
} catch (err) {
console.error("Gagal memuat data:", err);
2025-09-16 08:29:07 +00:00
} finally {
setLoading(false);
}
}
2025-10-12 15:12:07 +00:00
// 🔹 Simpan bookmark
const handleSave = async (id: number) => {
const roleId = Number(getCookiesDecrypt("urie"));
if (!roleId || isNaN(roleId)) {
MySwal.fire({
icon: "warning",
title: "Login diperlukan",
text: "Silakan login terlebih dahulu untuk menyimpan artikel.",
confirmButtonText: "Login Sekarang",
confirmButtonColor: "#d33",
});
return;
}
try {
const res = await toggleBookmark(id);
if (res?.error) {
MySwal.fire({
icon: "error",
title: "Gagal",
2025-10-12 15:12:07 +00:00
text: "Tidak dapat menyimpan artikel.",
confirmButtonColor: "#d33",
});
} else {
const updated = new Set(bookmarkedIds);
updated.add(Number(id));
setBookmarkedIds(updated);
localStorage.setItem(
"bookmarkedIds",
JSON.stringify(Array.from(updated))
);
MySwal.fire({
icon: "success",
title: "Berhasil",
text: "Artikel berhasil disimpan ke bookmark.",
timer: 1500,
showConfirmButton: false,
});
}
} catch (err) {
2025-10-12 15:12:07 +00:00
console.error("Error menyimpan bookmark:", err);
MySwal.fire({
icon: "error",
title: "Kesalahan",
text: "Terjadi kesalahan saat menyimpan artikel.",
});
}
};
2025-09-16 08:29:07 +00:00
return (
<section className="bg-white px-4 py-10 border max-w-[1350px] mx-auto rounded-md border-[#CDD5DF] my-10">
<div className="max-w-screen-xl mx-auto">
<h2 className="text-2xl font-semibold text-center mb-6">
Media Update
</h2>
{/* Main Tab */}
<div className="flex justify-center mb-6 bg-white">
<Card className="bg-[#FFFFFF] rounded-xl flex flex-row p-3 gap-2 shadow-md border border-gray-200">
2025-09-16 08:29:07 +00:00
<button
onClick={() => setTab("latest")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "latest"
? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10"
2025-09-16 08:29:07 +00:00
}`}
>
2025-10-12 15:12:07 +00:00
Terbaru
2025-09-16 08:29:07 +00:00
</button>
<button
onClick={() => setTab("popular")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "popular"
? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10"
2025-09-16 08:29:07 +00:00
}`}
>
2025-10-12 15:12:07 +00:00
Terpopuler
2025-09-16 08:29:07 +00:00
</button>
</Card>
</div>
{/* Content Type Filter */}
<div className="flex justify-center mb-8">
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-4 border-2 border-blue-100 shadow-lg">
<div className="flex flex-wrap justify-center gap-2">
2025-10-12 14:57:46 +00:00
{/* <button
onClick={() => setContentType("all")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "all"
? "bg-gradient-to-r from-blue-500 to-indigo-600 text-white shadow-lg ring-2 ring-blue-300"
: "bg-white text-blue-600 border-2 border-blue-200 hover:border-blue-400 hover:shadow-md"
2025-10-12 15:12:07 +00:00
}`}
>
📋 Semua
2025-10-12 14:57:46 +00:00
</button> */}
<button
onClick={() => setContentType("foto")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "foto"
? "bg-gradient-to-r from-orange-500 to-red-600 text-white shadow-lg ring-2 ring-orange-300"
: "bg-white text-orange-600 border-2 border-orange-200 hover:border-orange-400 hover:shadow-md"
}`}
>
📸 Foto
</button>
<button
onClick={() => setContentType("audiovisual")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audiovisual"
? "bg-gradient-to-r from-purple-500 to-pink-600 text-white shadow-lg ring-2 ring-purple-300"
: "bg-white text-purple-600 border-2 border-purple-200 hover:border-purple-400 hover:shadow-md"
}`}
>
🎬 Audio Visual
</button>
<button
onClick={() => setContentType("audio")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audio"
? "bg-gradient-to-r from-green-500 to-emerald-600 text-white shadow-lg ring-2 ring-green-300"
: "bg-white text-green-600 border-2 border-green-200 hover:border-green-400 hover:shadow-md"
}`}
>
🎵 Audio
</button>
<button
onClick={() => setContentType("text")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "text"
? "bg-gradient-to-r from-gray-500 to-slate-600 text-white shadow-lg ring-2 ring-gray-300"
: "bg-white text-gray-600 border-2 border-gray-200 hover:border-gray-400 hover:shadow-md"
}`}
>
📝 Text
</button>
</div>
</div>
</div>
2025-10-12 15:12:07 +00:00
2025-09-16 08:29:07 +00:00
{/* Slider */}
{loading ? (
2025-10-12 15:12:07 +00:00
<p className="text-center">Memuat konten...</p>
2025-09-16 08:29:07 +00:00
) : (
<Swiper
modules={[Navigation]}
navigation
spaceBetween={20}
slidesPerView={1}
breakpoints={{
640: { slidesPerView: 2 },
1024: { slidesPerView: 4 },
}}
>
{filteredData.map((item) => (
2025-09-16 08:29:07 +00:00
<SwiperSlide key={item.id}>
<div className="rounded-xl shadow-md overflow-hidden bg-white">
<div className="w-full h-[204px] relative">
<Link href={getLink(item)}>
<Image
src={item.smallThumbnailLink || "/placeholder.png"}
alt={item.title || "No Title"}
fill
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
/>
</Link>
2025-09-16 08:29:07 +00:00
</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">
{item.clientName || "Tanpa Kategori"}
2025-09-16 08:29:07 +00:00
</span>
2025-10-12 15:34:45 +00:00
{/* <span className="text-xs font-medium text-[#b3882e] capitalize">
2025-10-12 15:12:07 +00:00
{tipeKonten}
2025-10-12 15:34:45 +00:00
</span> */}
2025-09-16 08:29:07 +00:00
</div>
<p className="text-xs text-gray-500 mb-1">
{formatTanggal(item.createdAt)}
</p>
<Link href={getLink(item)}>
<p className="text-sm font-semibold mb-3 line-clamp-2 cursor-pointer hover:text-blue-600 transition-colors">
{item.title}
</p>
</Link>
2025-09-16 08:29:07 +00:00
<div className="flex items-center justify-between">
<div className="flex gap-2 text-gray-600">
<ThumbsUp className="w-4 h-4 cursor-pointer" />
<ThumbsDown className="w-4 h-4 cursor-pointer" />
</div>
<Button
onClick={() => handleSave(item.id)}
disabled={bookmarkedIds.has(Number(item.id))}
2025-09-16 08:29:07 +00:00
variant="default"
size="sm"
className={`rounded px-4 ${
bookmarkedIds.has(Number(item.id))
? "bg-gray-400 cursor-not-allowed text-white"
: "bg-blue-600 text-white hover:bg-blue-700"
}`}
2025-09-16 08:29:07 +00:00
>
2025-10-12 15:12:07 +00:00
{bookmarkedIds.has(Number(item.id))
? "Tersimpan"
: "Simpan"}
2025-09-16 08:29:07 +00:00
</Button>
</div>
</div>
</div>
</SwiperSlide>
))}
</Swiper>
)}
2025-10-12 14:57:46 +00:00
{/* Lihat lebih banyak - hanya muncul jika ada data */}
{filteredData.length > 0 && (
<div className="text-center mt-10">
<Link
href={getContentTypeLink()}
2025-09-16 08:29:07 +00:00
>
2025-10-12 14:57:46 +00:00
<Button
size={"lg"}
className="text-[#b3882e] bg-transparent border border-[#b3882e] px-6 py-2 rounded-s-sm text-sm font-medium hover:bg-[#b3882e]/10 transition"
>
Lihat Lebih Banyak
</Button>
</Link>
</div>
)}
2025-09-16 08:29:07 +00:00
</div>
</section>
);
}