fix: all detail content, page untuk anda
This commit is contained in:
parent
74e2588aaf
commit
249f84c002
|
|
@ -5,7 +5,13 @@ import AudioDetail from "@/components/main/content/audio-detail";
|
|||
|
||||
export default function DetailAudioInfo() {
|
||||
const params = useParams();
|
||||
const id = params?.id as string;
|
||||
const idParam = params?.id;
|
||||
const id =
|
||||
typeof idParam === "string"
|
||||
? Number(idParam)
|
||||
: Array.isArray(idParam)
|
||||
? Number(idParam[0])
|
||||
: 0;
|
||||
|
||||
return <AudioDetail id={id} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@ import DocumentDetail from "@/components/main/content/document-detail";
|
|||
|
||||
export default function DetailDocumentInfo() {
|
||||
const params = useParams();
|
||||
const id = params?.id as string;
|
||||
const idParam = params?.id;
|
||||
const id =
|
||||
typeof idParam === "string"
|
||||
? Number(idParam)
|
||||
: Array.isArray(idParam)
|
||||
? Number(idParam[0])
|
||||
: 0;
|
||||
|
||||
return <DocumentDetail id={id} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export default function Header() {
|
|||
};
|
||||
|
||||
fetchBookmarks();
|
||||
}, [userId]); // ← otomatis re-fetch kalau cookie user berubah
|
||||
}, [userId]);
|
||||
|
||||
return (
|
||||
<section className="max-w-[1350px] mx-auto px-4">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import Swal from "sweetalert2";
|
|||
import withReactContent from "sweetalert2-react-content";
|
||||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
||||
|
||||
// 🔹 Fungsi format tanggal ke WIB
|
||||
function formatTanggal(dateString: string) {
|
||||
if (!dateString) return "";
|
||||
return (
|
||||
|
|
@ -49,7 +48,6 @@ export default function MediaUpdate() {
|
|||
const params = useParams();
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
// Get slug from URL params
|
||||
const slug = params?.slug as string;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -73,15 +71,15 @@ export default function MediaUpdate() {
|
|||
function getTypeIdByContentType(contentType: string): string {
|
||||
switch (contentType) {
|
||||
case "audiovisual":
|
||||
return "2"; // Video
|
||||
return "2";
|
||||
case "foto":
|
||||
return "1"; // Image
|
||||
return "1";
|
||||
case "audio":
|
||||
return "4"; // Audio
|
||||
return "4";
|
||||
case "text":
|
||||
return "3"; // Text
|
||||
return "3";
|
||||
default:
|
||||
return "1"; // Default to Image
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,20 +101,41 @@ export default function MediaUpdate() {
|
|||
|
||||
// Function to get content type link for "Lihat lebih banyak" button
|
||||
function getContentTypeLink() {
|
||||
const pathname =
|
||||
typeof window !== "undefined" ? window.location.pathname : "";
|
||||
|
||||
const isTenantRoute = pathname.includes("/tenant/");
|
||||
const basePath = isTenantRoute ? `/tenant/${slug}/content` : "/content";
|
||||
|
||||
switch (contentType) {
|
||||
case "audio":
|
||||
return "/tenant/" + slug + "/content/audio";
|
||||
return `${basePath}/audio`;
|
||||
case "foto":
|
||||
return "/tenant/" + slug + "/content/image";
|
||||
return `${basePath}/image`;
|
||||
case "audiovisual":
|
||||
return "/tenant/" + slug + "/content/video";
|
||||
return `${basePath}/video`;
|
||||
case "text":
|
||||
return "/tenant/" + slug + "/content/text";
|
||||
return `${basePath}/text`;
|
||||
default:
|
||||
return "/tenant/" + slug + "/content/image"; // Default to image page
|
||||
return `${basePath}/image`;
|
||||
}
|
||||
}
|
||||
|
||||
// function getContentTypeLink() {
|
||||
// switch (contentType) {
|
||||
// case "audio":
|
||||
// return "/tenant/" + slug + "/content/audio";
|
||||
// case "foto":
|
||||
// return "/tenant/" + slug + "/content/image";
|
||||
// case "audiovisual":
|
||||
// return "/tenant/" + slug + "/content/video";
|
||||
// case "text":
|
||||
// return "/tenant/" + slug + "/content/text";
|
||||
// default:
|
||||
// return "/tenant/" + slug + "/content/image";
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (contentType === "all") {
|
||||
// setFilteredData(dataToRender);
|
||||
// return;
|
||||
|
|
@ -154,15 +173,15 @@ export default function MediaUpdate() {
|
|||
const filtered = dataToRender.filter((item) => {
|
||||
switch (contentType) {
|
||||
case "audiovisual":
|
||||
return item.typeId === 2; // Video
|
||||
return item.typeId === 2;
|
||||
case "audio":
|
||||
return item.typeId === 4; // Audio
|
||||
return item.typeId === 4;
|
||||
case "foto":
|
||||
return item.typeId === 1; // Image
|
||||
return item.typeId === 1;
|
||||
case "text":
|
||||
return item.typeId === 3; // Text
|
||||
return item.typeId === 3;
|
||||
default:
|
||||
return true; // Semua jenis
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -173,11 +192,9 @@ export default function MediaUpdate() {
|
|||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Tentukan typeId dari contentType
|
||||
const typeId = parseInt(getTypeIdByContentType(contentType));
|
||||
setCurrentTypeId(typeId.toString());
|
||||
|
||||
// 🔹 Ambil data artikel dari API
|
||||
const response = await listArticles(
|
||||
1,
|
||||
10,
|
||||
|
|
@ -204,11 +221,9 @@ export default function MediaUpdate() {
|
|||
);
|
||||
hasil = fallbackRes?.data?.data?.content || [];
|
||||
} else {
|
||||
// ✅ Perhatikan: API kamu mengembalikan `data.data` sebagai array
|
||||
hasil = response?.data?.data || [];
|
||||
}
|
||||
|
||||
// 🔹 Normalisasi struktur data
|
||||
const transformedData = hasil.map((article: any) => ({
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
|
|
@ -224,10 +239,8 @@ export default function MediaUpdate() {
|
|||
...article,
|
||||
}));
|
||||
|
||||
// ✅ INI WAJIB: simpan hasil mapping ke state
|
||||
setDataToRender(transformedData);
|
||||
|
||||
// 🔹 Sinkronisasi data bookmark
|
||||
const roleId = Number(getCookiesDecrypt("urie"));
|
||||
if (roleId && !isNaN(roleId)) {
|
||||
const simpananLocal = localStorage.getItem("bookmarkedIds");
|
||||
|
|
@ -263,7 +276,6 @@ export default function MediaUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
// 🔹 Simpan bookmark
|
||||
const handleSave = async (id: number) => {
|
||||
const roleId = Number(getCookiesDecrypt("urie"));
|
||||
if (!roleId || isNaN(roleId)) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Button } from "../ui/button";
|
|||
import Cookies from "js-cookie";
|
||||
import { Card } from "../ui/card";
|
||||
import { Input } from "../ui/input";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { DynamicLogoTenant } from "./dynamic-logo-tenant";
|
||||
|
||||
|
|
@ -28,86 +28,29 @@ const PUBLIKASI_SUBMENU = [
|
|||
|
||||
export default function Navbar() {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
|
||||
// const [user, setUser] = useState<{
|
||||
// id: number;
|
||||
// name: string;
|
||||
// avatar: string;
|
||||
// } | null>(null);
|
||||
|
||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
// useEffect(() => {
|
||||
// const roleId = getCookiesDecrypt("urie");
|
||||
// console.log("roleId", roleId);
|
||||
|
||||
// switch (roleId) {
|
||||
// case "1":
|
||||
// setUser({
|
||||
// id: 1,
|
||||
// name: "User Test",
|
||||
// avatar: "/contributor.png",
|
||||
// });
|
||||
// break;
|
||||
// case "3":
|
||||
// setUser({
|
||||
// id: 3,
|
||||
// name: "Mabes Polri - Approver",
|
||||
// avatar: "/contributor.png",
|
||||
// });
|
||||
// break;
|
||||
|
||||
// case "7":
|
||||
// setUser({
|
||||
// id: 7,
|
||||
// name: "DivHumas - RoMulmed - BagDise",
|
||||
// avatar: "/contributor.png",
|
||||
// });
|
||||
// break;
|
||||
|
||||
// case "6":
|
||||
// setUser({
|
||||
// id: 11,
|
||||
// name: "jurnalis-kompas1",
|
||||
// avatar: "/contributor.png",
|
||||
// });
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// setUser(null);
|
||||
// }
|
||||
// }, []);
|
||||
|
||||
// const roleId = getCookiesDecrypt("urie");
|
||||
// const isLoggedIn = roleId !== null;
|
||||
|
||||
// 🔍 Fungsi untuk mengecek apakah user sedang login
|
||||
// 🔍 Fungsi cek login
|
||||
const checkLoginStatus = () => {
|
||||
const roleId = getCookiesDecrypt("urie");
|
||||
const fullname = Cookies.get("ufne");
|
||||
return roleId && fullname ? true : false;
|
||||
};
|
||||
|
||||
// 🔄 Cek status login saat pertama kali load
|
||||
useEffect(() => {
|
||||
setIsLoggedIn(checkLoginStatus());
|
||||
}, []);
|
||||
|
||||
// const filteredNavItems = isLoggedIn
|
||||
// ? NAV_ITEMS.filter((item) => item.label !== "Mengikuti")
|
||||
// : NAV_ITEMS;
|
||||
|
||||
// 🔁 Gunakan isLoggedIn untuk menentukan navigasi
|
||||
const filteredNavItems = isLoggedIn
|
||||
? NAV_ITEMS.filter((item) => item.label !== "Mengikuti")
|
||||
: NAV_ITEMS;
|
||||
|
||||
// 🚪 Fungsi logout
|
||||
const handleLogout = () => {
|
||||
// Hapus semua cookie dengan kemungkinan variasi path dan domain
|
||||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||
Cookies.remove(cookieName, { path: "/" });
|
||||
Cookies.remove(cookieName, {
|
||||
|
|
@ -119,42 +62,13 @@ export default function Navbar() {
|
|||
domain: window.location.hostname,
|
||||
});
|
||||
});
|
||||
|
||||
// Ubah state login jadi false agar tampilan langsung berubah
|
||||
setIsLoggedIn(false);
|
||||
setShowProfileMenu(false);
|
||||
|
||||
// Redirect ke homepage
|
||||
window.location.href = "/";
|
||||
};
|
||||
|
||||
// const handleLogout = () => {
|
||||
// Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||
// Cookies.remove(cookieName);
|
||||
// });
|
||||
|
||||
// window.location.href = "/";
|
||||
|
||||
// // setUser(null);
|
||||
// setShowProfileMenu(false);
|
||||
// };
|
||||
|
||||
const username = Cookies.get("username");
|
||||
const fullname = Cookies.get("ufne");
|
||||
// const router = useRouter();
|
||||
// const [detail, setDetail] = useState<Detail>();
|
||||
|
||||
// const onLogout = () => {
|
||||
// Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||
// Cookies.remove(cookieName);
|
||||
// });
|
||||
|
||||
// router.push("/");
|
||||
// };
|
||||
const [isLogin, setIsLogin] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsLogin(fullname ? true : false);
|
||||
}, [fullname]);
|
||||
|
||||
return (
|
||||
<header className="relative max-w-[1400px] mx-auto flex items-center justify-between px-4 py-3 border-b bg-white z-50">
|
||||
|
|
@ -166,7 +80,7 @@ export default function Navbar() {
|
|||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>{" "}
|
||||
</div>
|
||||
<Menu
|
||||
className="w-6 h-6 cursor-pointer"
|
||||
onClick={() => setIsSidebarOpen(true)}
|
||||
|
|
@ -174,10 +88,23 @@ export default function Navbar() {
|
|||
<DynamicLogoTenant />
|
||||
</div>
|
||||
|
||||
{/* 🌐 NAV MENU */}
|
||||
<nav className="absolute left-1/2 -translate-x-1/2 hidden md:flex space-x-3 lg:space-x-8 text-sm font-medium">
|
||||
{filteredNavItems.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
|
||||
// 🔹 Pengecekan khusus untuk "Untuk Anda"
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
if (item.label === "Untuk Anda") {
|
||||
e.preventDefault();
|
||||
if (!checkLoginStatus()) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
router.push("/in/public/for-you");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={item.label} className="relative">
|
||||
{item.label === "Publikasi" ? (
|
||||
|
|
@ -221,6 +148,7 @@ export default function Navbar() {
|
|||
) : (
|
||||
<Link
|
||||
href={item.href}
|
||||
onClick={handleClick}
|
||||
className={cn(
|
||||
"relative text-gray-500 hover:text-black transition-colors",
|
||||
isActive && "text-black"
|
||||
|
|
@ -236,6 +164,8 @@ export default function Navbar() {
|
|||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* 🔹 PROFILE / LOGIN SECTION */}
|
||||
<nav className="hidden md:flex items-center gap-3 z-10 relative">
|
||||
{!isLoggedIn ? (
|
||||
<>
|
||||
|
|
@ -283,9 +213,10 @@ export default function Navbar() {
|
|||
)}
|
||||
</nav>
|
||||
|
||||
{/* 📱 SIDEBAR MOBILE */}
|
||||
{isSidebarOpen && (
|
||||
<div className="fixed inset-0 z-50 flex">
|
||||
<div className="w-80 bg-white p-6 space-y-6 shadow-lg relative h-full overflow-y-auto">
|
||||
<div className="w-80 bg-white p-6 space-y-6 shadow-lg relative h-full overflow-y-auto">
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
className="absolute top-4 right-4 text-gray-600"
|
||||
|
|
@ -329,37 +260,39 @@ export default function Navbar() {
|
|||
</h3>
|
||||
<div className="space-y-5 ml-3">
|
||||
{NAV_ITEMS.map((item) => (
|
||||
<Link
|
||||
<button
|
||||
key={item.label}
|
||||
href={item.href}
|
||||
className="block text-[15px] text-gray-800"
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
onClick={() => {
|
||||
if (item.label === "Untuk Anda") {
|
||||
if (!checkLoginStatus()) {
|
||||
router.push("/auth");
|
||||
} else {
|
||||
router.push("/public/for-you");
|
||||
}
|
||||
} else {
|
||||
router.push(item.href);
|
||||
}
|
||||
setIsSidebarOpen(false);
|
||||
}}
|
||||
className="block text-[15px] text-gray-800 text-left w-full"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5 text-[16px] font-bold">
|
||||
<Link
|
||||
href="/about"
|
||||
className="block text-black text-[16px] font-bold"
|
||||
>
|
||||
<Link href="/about" className="block text-black">
|
||||
Tentang Kami
|
||||
</Link>
|
||||
<Link
|
||||
href="/advertising"
|
||||
className="block text-[16px] font-bold text-black"
|
||||
>
|
||||
<Link href="/advertising" className="block text-black">
|
||||
Advertising
|
||||
</Link>
|
||||
<Link
|
||||
href="/contact"
|
||||
className="block text-[16px] font-bold text-black"
|
||||
>
|
||||
<Link href="/contact" className="block text-black">
|
||||
Kontak Kami
|
||||
</Link>
|
||||
|
||||
{!isLoggedIn ? (
|
||||
<>
|
||||
<Link href="/auth" className="block text-lg text-gray-800">
|
||||
|
|
@ -381,6 +314,7 @@ export default function Navbar() {
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Card className="rounded-none p-4">
|
||||
<h2 className="text-[#C6A455] text-center text-lg font-semibold">
|
||||
Subscribe to Our Newsletter
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
"use client";
|
||||
import Image from "next/image";
|
||||
import { Calendar, Clock, Eye } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Calendar, Eye } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
FaFacebookF,
|
||||
FaTiktok,
|
||||
|
|
@ -15,31 +14,24 @@ import {
|
|||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { getArticleDetail } from "@/service/content/content";
|
||||
|
||||
export default function AudioDetail({ id }: { id: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
export default function AudioDetail({ id }: { id: number }) {
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedDoc, setSelectedDoc] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
const [selectedAudio, setSelectedAudio] = useState(0);
|
||||
|
||||
// Salin tautan
|
||||
const handleCopyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(window.location.href);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy: ", err);
|
||||
console.error("Gagal menyalin link:", err);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -60,86 +52,63 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
const fetchDetail = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Try new Articles API first
|
||||
const response = await getArticleDetail(id);
|
||||
console.log("Article Detail API response:", response);
|
||||
|
||||
if (response?.error) {
|
||||
console.error("Articles API failed, falling back to old API");
|
||||
// Fallback to old API
|
||||
const fallbackResponse = await getDetail(id);
|
||||
setData(fallbackResponse?.data?.data);
|
||||
console.log(
|
||||
"doc",
|
||||
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
);
|
||||
|
||||
// 1️⃣ Coba API baru
|
||||
const res = await getArticleDetail(id);
|
||||
console.log("Audio detail API response:", res);
|
||||
|
||||
const article = res?.data?.data;
|
||||
|
||||
if (article) {
|
||||
const mappedData = {
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
description: article.description,
|
||||
createdAt: article.createdAt,
|
||||
clickCount: article.viewCount,
|
||||
creatorGroupLevelName: article.createdByName || "Unknown",
|
||||
uploadedBy: {
|
||||
publisher: article.createdByName || "MABES POLRI",
|
||||
},
|
||||
files:
|
||||
article.files?.map((f: any) => ({
|
||||
id: f.id,
|
||||
fileName: f.file_name,
|
||||
url: f.file_url,
|
||||
secondaryUrl: f.file_url,
|
||||
fileAlt: f.file_alt,
|
||||
size: f.size,
|
||||
createdAt: f.created_at,
|
||||
updatedAt: f.updated_at,
|
||||
})) || [],
|
||||
};
|
||||
setData(mappedData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle new API response structure
|
||||
const articleData = response?.data?.data;
|
||||
if (articleData) {
|
||||
// Transform article data to match old structure for backward compatibility
|
||||
const transformedData = {
|
||||
id: articleData.id,
|
||||
title: articleData.title,
|
||||
description: articleData.description,
|
||||
createdAt: articleData.createdAt,
|
||||
clickCount: articleData.viewCount,
|
||||
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||
uploadedBy: {
|
||||
publisher: articleData.createdByName || "MABES POLRI"
|
||||
},
|
||||
files: articleData.files?.map((file: any) => ({
|
||||
id: file.id,
|
||||
url: file.file_url,
|
||||
fileName: file.file_name,
|
||||
filePath: file.file_path,
|
||||
fileThumbnail: file.file_thumbnail,
|
||||
fileAlt: file.file_alt,
|
||||
widthPixel: file.width_pixel,
|
||||
heightPixel: file.height_pixel,
|
||||
size: file.size,
|
||||
downloadCount: file.download_count,
|
||||
createdAt: file.created_at,
|
||||
updatedAt: file.updated_at,
|
||||
secondaryUrl: file.file_url, // For audio files, use same URL
|
||||
...file
|
||||
})) || [],
|
||||
...articleData
|
||||
};
|
||||
|
||||
setData(transformedData);
|
||||
console.log(
|
||||
"doc",
|
||||
transformedData.files[selectedDoc]?.secondaryUrl
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching detail:", error);
|
||||
// Try fallback to old API if new API fails
|
||||
try {
|
||||
const fallbackResponse = await getDetail(id);
|
||||
setData(fallbackResponse?.data?.data);
|
||||
console.log(
|
||||
"doc",
|
||||
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
);
|
||||
} catch (fallbackError) {
|
||||
console.error("Fallback API also failed:", fallbackError);
|
||||
}
|
||||
// 2️⃣ Fallback ke API lama
|
||||
// const fallback = await getDetail(id);
|
||||
// setData(fallback?.data?.data);
|
||||
} catch (err) {
|
||||
console.error("Gagal memuat audio:", err);
|
||||
// try {
|
||||
// const fallback = await getDetail(id);
|
||||
// setData(fallback?.data?.data);
|
||||
// } catch (err2) {
|
||||
// console.error("Fallback gagal:", err2);
|
||||
// }
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (id) fetchDetail();
|
||||
}, [id]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||
<p>Loading...</p>
|
||||
<p>Memuat data audio...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -154,19 +123,25 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
{/* Pemutar Audio Utama */}
|
||||
<div className="relative">
|
||||
<audio controls className="w-full" src={data?.files[selectedDoc]?.url}>
|
||||
Your browser does not support the audio element.
|
||||
<audio
|
||||
controls
|
||||
className="w-full"
|
||||
src={data?.files?.[selectedAudio]?.url || ""}
|
||||
>
|
||||
Browser Anda tidak mendukung elemen audio.
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
{/* Pilihan file audio */}
|
||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||
{data?.files?.map((file: any, index: number) => (
|
||||
<button
|
||||
key={file?.id}
|
||||
onClick={() => setSelectedDoc(index)}
|
||||
onClick={() => setSelectedAudio(index)}
|
||||
className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
||||
selectedDoc === index ? "border-red-600 bg-gray-50" : ""
|
||||
selectedAudio === index ? "border-red-600 bg-gray-50" : ""
|
||||
}`}
|
||||
>
|
||||
🎵 {file?.fileName || `Audio ${index + 1}`}
|
||||
|
|
@ -174,110 +149,45 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-black">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{new Date(data.createdAt)
|
||||
.toLocaleString("id-ID", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZone: "Asia/Jakarta",
|
||||
})
|
||||
.replace(".", ":")}{" "}
|
||||
WIB
|
||||
</span>
|
||||
{/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
<Clock className="w-4 h-4" />
|
||||
{data.time || "-"}
|
||||
</span> */}
|
||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
<Eye className="w-4 h-4" />
|
||||
{data.clickCount || 0}
|
||||
</span>
|
||||
<span className="text-black">
|
||||
{" "}
|
||||
Creator: {data.creatorGroupLevelName}
|
||||
</span>
|
||||
</div>
|
||||
{/* Informasi artikel */}
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-black">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{new Date(data.createdAt)
|
||||
.toLocaleString("id-ID", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZone: "Asia/Jakarta",
|
||||
})
|
||||
.replace(".", ":")}{" "}
|
||||
WIB
|
||||
</span>
|
||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
<Eye className="w-4 h-4" />
|
||||
{data.clickCount || 0}
|
||||
</span>
|
||||
<span className="text-black">
|
||||
Creator: {data.creatorGroupLevelName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Deskripsi */}
|
||||
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
||||
{/* Sidebar actions */}
|
||||
<div className="hidden md:flex flex-col gap-4 relative z-10">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button
|
||||
onClick={handleCopyLink}
|
||||
size="lg"
|
||||
className="justify-start bg-black text-white rounded-full"
|
||||
>
|
||||
{copied ? <FaCheck /> : <FaLink />}
|
||||
</Button>
|
||||
<span>COPY LINK</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center relative">
|
||||
<Button
|
||||
onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
size="lg"
|
||||
className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||
>
|
||||
<FaShareAlt />
|
||||
</Button>
|
||||
<span>SHARE</span>
|
||||
|
||||
{showShareMenu && (
|
||||
<div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48">
|
||||
<SocialItem icon={<FaFacebookF />} label="Facebook" />
|
||||
<SocialItem icon={<FaTiktok />} label="TikTok" />
|
||||
<SocialItem icon={<FaYoutube />} label="YouTube" />
|
||||
<SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
||||
<SocialItem icon={<FaInstagram />} label="Instagram" />
|
||||
<SocialItem icon={<FaTwitter />} label="Twitter" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
COMMENT
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 space-y-4">
|
||||
<h1 className="text-xl font-bold">{data.title}</h1>
|
||||
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||
<p>{data.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Actions bawah */}
|
||||
<div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
||||
{/* Tombol aksi bawah */}
|
||||
<div className="flex flex-wrap justify-center gap-4 my-20">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button
|
||||
onClick={handleCopyLink}
|
||||
|
|
@ -288,7 +198,7 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
</Button>
|
||||
<span>COPY LINK</span>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex gap-2 items-center relative">
|
||||
<Button
|
||||
onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
size="lg"
|
||||
|
|
@ -297,25 +207,25 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
<FaShareAlt />
|
||||
</Button>
|
||||
<span>SHARE</span>
|
||||
{showShareMenu && (
|
||||
<div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48 z-10">
|
||||
<SocialItem icon={<FaFacebookF />} label="Facebook" />
|
||||
<SocialItem icon={<FaTiktok />} label="TikTok" />
|
||||
<SocialItem icon={<FaYoutube />} label="YouTube" />
|
||||
<SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
||||
<SocialItem icon={<FaInstagram />} label="Instagram" />
|
||||
<SocialItem icon={<FaTwitter />} label="Twitter" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Link href={`/content/audio/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
/>
|
||||
</svg>
|
||||
💬
|
||||
</Button>
|
||||
COMMENT
|
||||
</Link>
|
||||
|
|
@ -326,3 +236,332 @@ export default function AudioDetail({ id }: { id: string }) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// "use client";
|
||||
// import Image from "next/image";
|
||||
// import { Calendar, Clock, Eye } from "lucide-react";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import Link from "next/link";
|
||||
// import { useState, useEffect } from "react";
|
||||
// import {
|
||||
// FaFacebookF,
|
||||
// FaTiktok,
|
||||
// FaYoutube,
|
||||
// FaWhatsapp,
|
||||
// FaInstagram,
|
||||
// FaTwitter,
|
||||
// FaCheck,
|
||||
// FaLink,
|
||||
// FaShareAlt,
|
||||
// } from "react-icons/fa";
|
||||
// import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
|
||||
// export default function AudioDetail({ id }: { id: string }) {
|
||||
// const [copied, setCopied] = useState(false);
|
||||
// const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
// const [data, setData] = useState<any>(null);
|
||||
// const [loading, setLoading] = useState(true);
|
||||
// const [selectedDoc, setSelectedDoc] = useState(0);
|
||||
// const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
// useEffect(() => {
|
||||
// const timer = setTimeout(() => {
|
||||
// setIsLoading(false);
|
||||
// }, 3000);
|
||||
|
||||
// return () => clearTimeout(timer);
|
||||
// }, []);
|
||||
|
||||
// const handleCopyLink = async () => {
|
||||
// try {
|
||||
// await navigator.clipboard.writeText(window.location.href);
|
||||
// setCopied(true);
|
||||
// setTimeout(() => setCopied(false), 2000);
|
||||
// } catch (err) {
|
||||
// console.error("Failed to copy: ", err);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const SocialItem = ({
|
||||
// icon,
|
||||
// label,
|
||||
// }: {
|
||||
// icon: React.ReactNode;
|
||||
// label: string;
|
||||
// }) => (
|
||||
// <div className="flex items-center gap-3 cursor-pointer hover:opacity-80">
|
||||
// <div className="bg-[#C6A455] p-2 rounded-full text-white">{icon}</div>
|
||||
// <span className="text-sm">{label}</span>
|
||||
// </div>
|
||||
// );
|
||||
|
||||
// useEffect(() => {
|
||||
// const fetchDetail = async () => {
|
||||
// try {
|
||||
// setLoading(true);
|
||||
|
||||
// // Try new Articles API first
|
||||
// const response = await getArticleDetail(id);
|
||||
// console.log("Article Detail API response:", response);
|
||||
|
||||
// if (response?.error) {
|
||||
// console.error("Articles API failed, falling back to old API");
|
||||
// // Fallback to old API
|
||||
// const fallbackResponse = await getDetail(id);
|
||||
// setData(fallbackResponse?.data?.data);
|
||||
// console.log(
|
||||
// "doc",
|
||||
// fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Handle new API response structure
|
||||
// const articleData = response?.data?.data;
|
||||
// if (articleData) {
|
||||
// // Transform article data to match old structure for backward compatibility
|
||||
// const transformedData = {
|
||||
// id: articleData.id,
|
||||
// title: articleData.title,
|
||||
// description: articleData.description,
|
||||
// createdAt: articleData.createdAt,
|
||||
// clickCount: articleData.viewCount,
|
||||
// creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||
// uploadedBy: {
|
||||
// publisher: articleData.createdByName || "MABES POLRI"
|
||||
// },
|
||||
// files: articleData.files?.map((file: any) => ({
|
||||
// id: file.id,
|
||||
// url: file.file_url,
|
||||
// fileName: file.file_name,
|
||||
// filePath: file.file_path,
|
||||
// fileThumbnail: file.file_thumbnail,
|
||||
// fileAlt: file.file_alt,
|
||||
// widthPixel: file.width_pixel,
|
||||
// heightPixel: file.height_pixel,
|
||||
// size: file.size,
|
||||
// downloadCount: file.download_count,
|
||||
// createdAt: file.created_at,
|
||||
// updatedAt: file.updated_at,
|
||||
// secondaryUrl: file.file_url, // For audio files, use same URL
|
||||
// ...file
|
||||
// })) || [],
|
||||
// ...articleData
|
||||
// };
|
||||
|
||||
// setData(transformedData);
|
||||
// console.log(
|
||||
// "doc",
|
||||
// transformedData.files[selectedDoc]?.secondaryUrl
|
||||
// );
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching detail:", error);
|
||||
// // Try fallback to old API if new API fails
|
||||
// try {
|
||||
// const fallbackResponse = await getDetail(id);
|
||||
// setData(fallbackResponse?.data?.data);
|
||||
// console.log(
|
||||
// "doc",
|
||||
// fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
// );
|
||||
// } catch (fallbackError) {
|
||||
// console.error("Fallback API also failed:", fallbackError);
|
||||
// }
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
// if (id) fetchDetail();
|
||||
// }, [id]);
|
||||
|
||||
// if (loading) {
|
||||
// return (
|
||||
// <div className="max-w-6xl mx-auto px-4 py-6">
|
||||
// <p>Loading...</p>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (!data) {
|
||||
// return (
|
||||
// <div className="max-w-6xl mx-auto px-4 py-6">
|
||||
// <p>Data tidak ditemukan</p>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
// <div className="relative">
|
||||
// <audio controls className="w-full" src={data?.files[selectedDoc]?.url}>
|
||||
// Your browser does not support the audio element.
|
||||
// </audio>
|
||||
// </div>
|
||||
|
||||
// <div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||
// {data?.files?.map((file: any, index: number) => (
|
||||
// <button
|
||||
// key={file?.id}
|
||||
// onClick={() => setSelectedDoc(index)}
|
||||
// className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
||||
// selectedDoc === index ? "border-red-600 bg-gray-50" : ""
|
||||
// }`}
|
||||
// >
|
||||
// 🎵 {file?.fileName || `Audio ${index + 1}`}
|
||||
// </button>
|
||||
// ))}
|
||||
// </div>
|
||||
|
||||
// <div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
||||
// <div className="flex flex-wrap items-center gap-2">
|
||||
// <span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||
// by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||
// </span>
|
||||
// <span className="flex items-center gap-1 text-black">
|
||||
// <Calendar className="w-4 h-4" />
|
||||
// {new Date(data.createdAt)
|
||||
// .toLocaleString("id-ID", {
|
||||
// day: "2-digit",
|
||||
// month: "short",
|
||||
// year: "numeric",
|
||||
// hour: "2-digit",
|
||||
// minute: "2-digit",
|
||||
// hour12: false,
|
||||
// timeZone: "Asia/Jakarta",
|
||||
// })
|
||||
// .replace(".", ":")}{" "}
|
||||
// WIB
|
||||
// </span>
|
||||
// {/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
// <Clock className="w-4 h-4" />
|
||||
// {data.time || "-"}
|
||||
// </span> */}
|
||||
// <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
// <Eye className="w-4 h-4" />
|
||||
// {data.clickCount || 0}
|
||||
// </span>
|
||||
// <span className="text-black">
|
||||
// {" "}
|
||||
// Creator: {data.creatorGroupLevelName}
|
||||
// </span>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="flex flex-col md:flex-row gap-6 mt-6">
|
||||
// {/* Sidebar actions */}
|
||||
// <div className="hidden md:flex flex-col gap-4 relative z-10">
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Button
|
||||
// onClick={handleCopyLink}
|
||||
// size="lg"
|
||||
// className="justify-start bg-black text-white rounded-full"
|
||||
// >
|
||||
// {copied ? <FaCheck /> : <FaLink />}
|
||||
// </Button>
|
||||
// <span>COPY LINK</span>
|
||||
// </div>
|
||||
|
||||
// <div className="flex gap-2 items-center relative">
|
||||
// <Button
|
||||
// onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||
// >
|
||||
// <FaShareAlt />
|
||||
// </Button>
|
||||
// <span>SHARE</span>
|
||||
|
||||
// {showShareMenu && (
|
||||
// <div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48">
|
||||
// <SocialItem icon={<FaFacebookF />} label="Facebook" />
|
||||
// <SocialItem icon={<FaTiktok />} label="TikTok" />
|
||||
// <SocialItem icon={<FaYoutube />} label="YouTube" />
|
||||
// <SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
||||
// <SocialItem icon={<FaInstagram />} label="Instagram" />
|
||||
// <SocialItem icon={<FaTwitter />} label="Twitter" />
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Link href={`/content/video/comment/${id}`}>
|
||||
// <Button
|
||||
// variant="default"
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
// >
|
||||
// <svg
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// width="15"
|
||||
// height="15"
|
||||
// viewBox="0 0 24 24"
|
||||
// >
|
||||
// <path
|
||||
// fill="currentColor"
|
||||
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
// />
|
||||
// </svg>
|
||||
// </Button>
|
||||
// COMMENT
|
||||
// </Link>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Content */}
|
||||
// <div className="flex-1 space-y-4">
|
||||
// <h1 className="text-xl font-bold">{data.title}</h1>
|
||||
// <div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||
// <p>{data.description}</p>
|
||||
// </div>
|
||||
|
||||
// {/* Actions bawah */}
|
||||
// <div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Button
|
||||
// onClick={handleCopyLink}
|
||||
// size="lg"
|
||||
// className="justify-start bg-black text-white rounded-full"
|
||||
// >
|
||||
// {copied ? <FaCheck /> : <FaLink />}
|
||||
// </Button>
|
||||
// <span>COPY LINK</span>
|
||||
// </div>
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Button
|
||||
// onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||
// >
|
||||
// <FaShareAlt />
|
||||
// </Button>
|
||||
// <span>SHARE</span>
|
||||
// </div>
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Link href={`/content/video/comment/${id}`}>
|
||||
// <Button
|
||||
// variant="default"
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
// >
|
||||
// <svg
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// width="15"
|
||||
// height="15"
|
||||
// viewBox="0 0 24 24"
|
||||
// >
|
||||
// <path
|
||||
// fill="currentColor"
|
||||
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
// />
|
||||
// </svg>
|
||||
// </Button>
|
||||
// COMMENT
|
||||
// </Link>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
"use client";
|
||||
import Image from "next/image";
|
||||
import { Calendar, Clock, Eye } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Calendar, Eye } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
FaFacebookF,
|
||||
FaTiktok,
|
||||
|
|
@ -15,34 +14,24 @@ import {
|
|||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
import VideoPlayer from "@/utils/video-player";
|
||||
import { toBase64, shimmer } from "@/utils/globals";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { getArticleDetail } from "@/service/content/content";
|
||||
|
||||
export default function DocumentDetail({ id }: { id: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
export default function DocumentDetail({ id }: { id: number }) {
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
const [selectedDoc, setSelectedDoc] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Copy link
|
||||
const handleCopyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(window.location.href);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy: ", err);
|
||||
console.error("Gagal menyalin:", err);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -63,86 +52,64 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
const fetchDetail = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Try new Articles API first
|
||||
const response = await getArticleDetail(id);
|
||||
console.log("Article Detail API response:", response);
|
||||
|
||||
if (response?.error) {
|
||||
console.error("Articles API failed, falling back to old API");
|
||||
// Fallback to old API
|
||||
const fallbackResponse = await getDetail(id);
|
||||
setData(fallbackResponse?.data?.data);
|
||||
console.log(
|
||||
"doc",
|
||||
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
);
|
||||
|
||||
// 1️⃣ Coba ambil dari API baru
|
||||
const res = await getArticleDetail(id);
|
||||
console.log("Response artikel:", res);
|
||||
|
||||
const article = res?.data?.data;
|
||||
|
||||
if (article) {
|
||||
const mappedData = {
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
description: article.description,
|
||||
createdAt: article.createdAt,
|
||||
clickCount: article.viewCount,
|
||||
creatorGroupLevelName: article.createdByName || "Unknown",
|
||||
uploadedBy: {
|
||||
publisher: article.createdByName || "MABES POLRI",
|
||||
},
|
||||
files:
|
||||
article.files?.map((f: any) => ({
|
||||
id: f.id,
|
||||
fileName: f.file_name,
|
||||
url: f.file_url,
|
||||
secondaryUrl: f.file_url,
|
||||
fileThumbnail: f.file_thumbnail,
|
||||
fileAlt: f.file_alt,
|
||||
size: f.size,
|
||||
createdAt: f.created_at,
|
||||
updatedAt: f.updated_at,
|
||||
})) || [],
|
||||
};
|
||||
setData(mappedData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle new API response structure
|
||||
const articleData = response?.data?.data;
|
||||
if (articleData) {
|
||||
// Transform article data to match old structure for backward compatibility
|
||||
const transformedData = {
|
||||
id: articleData.id,
|
||||
title: articleData.title,
|
||||
description: articleData.description,
|
||||
createdAt: articleData.createdAt,
|
||||
clickCount: articleData.viewCount,
|
||||
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||
uploadedBy: {
|
||||
publisher: articleData.createdByName || "MABES POLRI"
|
||||
},
|
||||
files: articleData.files?.map((file: any) => ({
|
||||
id: file.id,
|
||||
url: file.file_url,
|
||||
fileName: file.file_name,
|
||||
filePath: file.file_path,
|
||||
fileThumbnail: file.file_thumbnail,
|
||||
fileAlt: file.file_alt,
|
||||
widthPixel: file.width_pixel,
|
||||
heightPixel: file.height_pixel,
|
||||
size: file.size,
|
||||
downloadCount: file.download_count,
|
||||
createdAt: file.created_at,
|
||||
updatedAt: file.updated_at,
|
||||
secondaryUrl: file.file_url, // For document files, use same URL
|
||||
...file
|
||||
})) || [],
|
||||
...articleData
|
||||
};
|
||||
|
||||
setData(transformedData);
|
||||
console.log(
|
||||
"doc",
|
||||
transformedData.files[selectedDoc]?.secondaryUrl
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching detail:", error);
|
||||
// Try fallback to old API if new API fails
|
||||
try {
|
||||
const fallbackResponse = await getDetail(id);
|
||||
setData(fallbackResponse?.data?.data);
|
||||
console.log(
|
||||
"doc",
|
||||
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
);
|
||||
} catch (fallbackError) {
|
||||
console.error("Fallback API also failed:", fallbackError);
|
||||
}
|
||||
// 2️⃣ Fallback ke API lama
|
||||
// const fallback = await getDetail(id);
|
||||
// setData(fallback?.data?.data);
|
||||
} catch (err) {
|
||||
console.error("Gagal ambil detail:", err);
|
||||
// try {
|
||||
// const fallback = await getDetail(id);
|
||||
// setData(fallback?.data?.data);
|
||||
// } catch (err2) {
|
||||
// console.error("Fallback gagal:", err2);
|
||||
// }
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (id) fetchDetail();
|
||||
}, [id]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||
<p>Loading...</p>
|
||||
<p>Memuat data...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -157,17 +124,32 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
<div className="relative">
|
||||
{/* Viewer dokumen utama */}
|
||||
{/* <div className="relative">
|
||||
<iframe
|
||||
src={data?.files[selectedDoc]?.secondaryUrl}
|
||||
src={data?.files?.[selectedDoc]?.secondaryUrl || ""}
|
||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full"
|
||||
/>
|
||||
</div> */}
|
||||
<div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="150"
|
||||
height="150"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207zM7 7.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M7.5 9a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zM7 11.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M5.5 8a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1M6 9.5a.5.5 0 1 1-1 0a.5.5 0 0 1 1 0M5.5 12a.5.5 0 1 0 0-1a.5.5 0 0 0 0 1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Pilihan dokumen */}
|
||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||
{data?.files?.map((file: any, index: number) => (
|
||||
<button
|
||||
key={file?.id}
|
||||
key={file.id}
|
||||
onClick={() => setSelectedDoc(index)}
|
||||
className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
||||
selectedDoc === index ? "border-red-600 bg-gray-50" : ""
|
||||
|
|
@ -178,110 +160,45 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-black">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{new Date(data.createdAt)
|
||||
.toLocaleString("id-ID", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZone: "Asia/Jakarta",
|
||||
})
|
||||
.replace(".", ":")}{" "}
|
||||
WIB
|
||||
</span>
|
||||
{/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
<Clock className="w-4 h-4" />
|
||||
{data.time || "-"}
|
||||
</span> */}
|
||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
<Eye className="w-4 h-4" />
|
||||
{data.clickCount || 0}
|
||||
</span>
|
||||
<span className="text-black">
|
||||
{" "}
|
||||
Creator: {data.creatorGroupLevelName}
|
||||
</span>
|
||||
</div>
|
||||
{/* Informasi artikel */}
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-black">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{new Date(data.createdAt)
|
||||
.toLocaleString("id-ID", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZone: "Asia/Jakarta",
|
||||
})
|
||||
.replace(".", ":")}{" "}
|
||||
WIB
|
||||
</span>
|
||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
<Eye className="w-4 h-4" />
|
||||
{data.clickCount || 0}
|
||||
</span>
|
||||
<span className="text-black">
|
||||
Creator: {data.creatorGroupLevelName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Konten deskripsi */}
|
||||
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
||||
{/* Sidebar actions */}
|
||||
<div className="hidden md:flex flex-col gap-4 relative z-10">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button
|
||||
onClick={handleCopyLink}
|
||||
size="lg"
|
||||
className="justify-start bg-black text-white rounded-full"
|
||||
>
|
||||
{copied ? <FaCheck /> : <FaLink />}
|
||||
</Button>
|
||||
<span>COPY LINK</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center relative">
|
||||
<Button
|
||||
onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
size="lg"
|
||||
className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||
>
|
||||
<FaShareAlt />
|
||||
</Button>
|
||||
<span>SHARE</span>
|
||||
|
||||
{showShareMenu && (
|
||||
<div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48">
|
||||
<SocialItem icon={<FaFacebookF />} label="Facebook" />
|
||||
<SocialItem icon={<FaTiktok />} label="TikTok" />
|
||||
<SocialItem icon={<FaYoutube />} label="YouTube" />
|
||||
<SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
||||
<SocialItem icon={<FaInstagram />} label="Instagram" />
|
||||
<SocialItem icon={<FaTwitter />} label="Twitter" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
COMMENT
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 space-y-4">
|
||||
<h1 className="text-xl font-bold">{data.title}</h1>
|
||||
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||
<p>{data.description}</p>
|
||||
</div>
|
||||
|
||||
{/* Actions bawah */}
|
||||
<div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
||||
{/* Tombol aksi bawah */}
|
||||
<div className="flex flex-wrap justify-center gap-4 my-20">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Button
|
||||
onClick={handleCopyLink}
|
||||
|
|
@ -301,6 +218,16 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
<FaShareAlt />
|
||||
</Button>
|
||||
<span>SHARE</span>
|
||||
{showShareMenu && (
|
||||
<div className="absolute mt-10 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48 z-10">
|
||||
<SocialItem icon={<FaFacebookF />} label="Facebook" />
|
||||
<SocialItem icon={<FaTiktok />} label="TikTok" />
|
||||
<SocialItem icon={<FaYoutube />} label="YouTube" />
|
||||
<SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
||||
<SocialItem icon={<FaInstagram />} label="Instagram" />
|
||||
<SocialItem icon={<FaTwitter />} label="Twitter" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link href={`/content/video/comment/${id}`}>
|
||||
|
|
@ -309,17 +236,7 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
size="lg"
|
||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
/>
|
||||
</svg>
|
||||
💬
|
||||
</Button>
|
||||
COMMENT
|
||||
</Link>
|
||||
|
|
@ -330,3 +247,333 @@ export default function DocumentDetail({ id }: { id: string }) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// "use client";
|
||||
// import Image from "next/image";
|
||||
// import { Calendar, Clock, Eye } from "lucide-react";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import Link from "next/link";
|
||||
// import { useState, useEffect } from "react";
|
||||
// import {
|
||||
// FaFacebookF,
|
||||
// FaTiktok,
|
||||
// FaYoutube,
|
||||
// FaWhatsapp,
|
||||
// FaInstagram,
|
||||
// FaTwitter,
|
||||
// FaCheck,
|
||||
// FaLink,
|
||||
// FaShareAlt,
|
||||
// } from "react-icons/fa";
|
||||
// import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
|
||||
// export default function DocumentDetail({ id }: { id: string }) {
|
||||
// const [copied, setCopied] = useState(false);
|
||||
// const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
// const [data, setData] = useState<any>(null);
|
||||
// const [loading, setLoading] = useState(true);
|
||||
// const [selectedDoc, setSelectedDoc] = useState(0);
|
||||
// const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
// useEffect(() => {
|
||||
// const timer = setTimeout(() => {
|
||||
// setIsLoading(false);
|
||||
// }, 3000);
|
||||
|
||||
// return () => clearTimeout(timer);
|
||||
// }, []);
|
||||
|
||||
// const handleCopyLink = async () => {
|
||||
// try {
|
||||
// await navigator.clipboard.writeText(window.location.href);
|
||||
// setCopied(true);
|
||||
// setTimeout(() => setCopied(false), 2000);
|
||||
// } catch (err) {
|
||||
// console.error("Failed to copy: ", err);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const SocialItem = ({
|
||||
// icon,
|
||||
// label,
|
||||
// }: {
|
||||
// icon: React.ReactNode;
|
||||
// label: string;
|
||||
// }) => (
|
||||
// <div className="flex items-center gap-3 cursor-pointer hover:opacity-80">
|
||||
// <div className="bg-[#C6A455] p-2 rounded-full text-white">{icon}</div>
|
||||
// <span className="text-sm">{label}</span>
|
||||
// </div>
|
||||
// );
|
||||
|
||||
// useEffect(() => {
|
||||
// const fetchDetail = async () => {
|
||||
// try {
|
||||
// setLoading(true);
|
||||
|
||||
// // Try new Articles API first
|
||||
// const response = await getArticleDetail(id);
|
||||
// console.log("Article Detail API response:", response);
|
||||
|
||||
// if (response?.error) {
|
||||
// console.error("Articles API failed, falling back to old API");
|
||||
// // Fallback to old API
|
||||
// const fallbackResponse = await getDetail(id);
|
||||
// setData(fallbackResponse?.data?.data);
|
||||
// console.log(
|
||||
// "doc",
|
||||
// fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Handle new API response structure
|
||||
// const articleData = response?.data?.data;
|
||||
// if (articleData) {
|
||||
// // Transform article data to match old structure for backward compatibility
|
||||
// const transformedData = {
|
||||
// id: articleData.id,
|
||||
// title: articleData.title,
|
||||
// description: articleData.description,
|
||||
// createdAt: articleData.createdAt,
|
||||
// clickCount: articleData.viewCount,
|
||||
// creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||
// uploadedBy: {
|
||||
// publisher: articleData.createdByName || "MABES POLRI"
|
||||
// },
|
||||
// files: articleData.files?.map((file: any) => ({
|
||||
// id: file.id,
|
||||
// url: file.file_url,
|
||||
// fileName: file.file_name,
|
||||
// filePath: file.file_path,
|
||||
// fileThumbnail: file.file_thumbnail,
|
||||
// fileAlt: file.file_alt,
|
||||
// widthPixel: file.width_pixel,
|
||||
// heightPixel: file.height_pixel,
|
||||
// size: file.size,
|
||||
// downloadCount: file.download_count,
|
||||
// createdAt: file.created_at,
|
||||
// updatedAt: file.updated_at,
|
||||
// secondaryUrl: file.file_url, // For document files, use same URL
|
||||
// ...file
|
||||
// })) || [],
|
||||
// ...articleData
|
||||
// };
|
||||
|
||||
// setData(transformedData);
|
||||
// console.log(
|
||||
// "doc",
|
||||
// transformedData.files[selectedDoc]?.secondaryUrl
|
||||
// );
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching detail:", error);
|
||||
// // Try fallback to old API if new API fails
|
||||
// try {
|
||||
// const fallbackResponse = await getDetail(id);
|
||||
// setData(fallbackResponse?.data?.data);
|
||||
// console.log(
|
||||
// "doc",
|
||||
// fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
||||
// );
|
||||
// } catch (fallbackError) {
|
||||
// console.error("Fallback API also failed:", fallbackError);
|
||||
// }
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
// if (id) fetchDetail();
|
||||
// }, [id]);
|
||||
|
||||
// if (loading) {
|
||||
// return (
|
||||
// <div className="max-w-6xl mx-auto px-4 py-6">
|
||||
// <p>Loading...</p>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (!data) {
|
||||
// return (
|
||||
// <div className="max-w-6xl mx-auto px-4 py-6">
|
||||
// <p>Data tidak ditemukan</p>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
// <div className="relative">
|
||||
// <iframe
|
||||
// src={data?.files[selectedDoc]?.secondaryUrl}
|
||||
// className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full"
|
||||
// />
|
||||
// </div>
|
||||
|
||||
// <div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||
// {data?.files?.map((file: any, index: number) => (
|
||||
// <button
|
||||
// key={file?.id}
|
||||
// onClick={() => setSelectedDoc(index)}
|
||||
// className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
||||
// selectedDoc === index ? "border-red-600 bg-gray-50" : ""
|
||||
// }`}
|
||||
// >
|
||||
// 📄 {file?.fileName || `Dokumen ${index + 1}`}
|
||||
// </button>
|
||||
// ))}
|
||||
// </div>
|
||||
|
||||
// <div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
||||
// <div className="flex flex-wrap items-center gap-2">
|
||||
// <span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||
// by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||
// </span>
|
||||
// <span className="flex items-center gap-1 text-black">
|
||||
// <Calendar className="w-4 h-4" />
|
||||
// {new Date(data.createdAt)
|
||||
// .toLocaleString("id-ID", {
|
||||
// day: "2-digit",
|
||||
// month: "short",
|
||||
// year: "numeric",
|
||||
// hour: "2-digit",
|
||||
// minute: "2-digit",
|
||||
// hour12: false,
|
||||
// timeZone: "Asia/Jakarta",
|
||||
// })
|
||||
// .replace(".", ":")}{" "}
|
||||
// WIB
|
||||
// </span>
|
||||
// {/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
// <Clock className="w-4 h-4" />
|
||||
// {data.time || "-"}
|
||||
// </span> */}
|
||||
// <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||
// <Eye className="w-4 h-4" />
|
||||
// {data.clickCount || 0}
|
||||
// </span>
|
||||
// <span className="text-black">
|
||||
// {" "}
|
||||
// Creator: {data.creatorGroupLevelName}
|
||||
// </span>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="flex flex-col md:flex-row gap-6 mt-6">
|
||||
// {/* Sidebar actions */}
|
||||
// <div className="hidden md:flex flex-col gap-4 relative z-10">
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Button
|
||||
// onClick={handleCopyLink}
|
||||
// size="lg"
|
||||
// className="justify-start bg-black text-white rounded-full"
|
||||
// >
|
||||
// {copied ? <FaCheck /> : <FaLink />}
|
||||
// </Button>
|
||||
// <span>COPY LINK</span>
|
||||
// </div>
|
||||
|
||||
// <div className="flex gap-2 items-center relative">
|
||||
// <Button
|
||||
// onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||
// >
|
||||
// <FaShareAlt />
|
||||
// </Button>
|
||||
// <span>SHARE</span>
|
||||
|
||||
// {showShareMenu && (
|
||||
// <div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48">
|
||||
// <SocialItem icon={<FaFacebookF />} label="Facebook" />
|
||||
// <SocialItem icon={<FaTiktok />} label="TikTok" />
|
||||
// <SocialItem icon={<FaYoutube />} label="YouTube" />
|
||||
// <SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
||||
// <SocialItem icon={<FaInstagram />} label="Instagram" />
|
||||
// <SocialItem icon={<FaTwitter />} label="Twitter" />
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Link href={`/content/video/comment/${id}`}>
|
||||
// <Button
|
||||
// variant="default"
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
// >
|
||||
// <svg
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// width="15"
|
||||
// height="15"
|
||||
// viewBox="0 0 24 24"
|
||||
// >
|
||||
// <path
|
||||
// fill="currentColor"
|
||||
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
// />
|
||||
// </svg>
|
||||
// </Button>
|
||||
// COMMENT
|
||||
// </Link>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Content */}
|
||||
// <div className="flex-1 space-y-4">
|
||||
// <h1 className="text-xl font-bold">{data.title}</h1>
|
||||
// <div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||
// <p>{data.description}</p>
|
||||
// </div>
|
||||
|
||||
// {/* Actions bawah */}
|
||||
// <div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Button
|
||||
// onClick={handleCopyLink}
|
||||
// size="lg"
|
||||
// className="justify-start bg-black text-white rounded-full"
|
||||
// >
|
||||
// {copied ? <FaCheck /> : <FaLink />}
|
||||
// </Button>
|
||||
// <span>COPY LINK</span>
|
||||
// </div>
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Button
|
||||
// onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||
// >
|
||||
// <FaShareAlt />
|
||||
// </Button>
|
||||
// <span>SHARE</span>
|
||||
// </div>
|
||||
// <div className="flex gap-2 items-center">
|
||||
// <Link href={`/content/video/comment/${id}`}>
|
||||
// <Button
|
||||
// variant="default"
|
||||
// size="lg"
|
||||
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||
// >
|
||||
// <svg
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// width="15"
|
||||
// height="15"
|
||||
// viewBox="0 0 24 24"
|
||||
// >
|
||||
// <path
|
||||
// fill="currentColor"
|
||||
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||
// />
|
||||
// </svg>
|
||||
// </Button>
|
||||
// COMMENT
|
||||
// </Link>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,45 +1,90 @@
|
|||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useState } from "react";
|
||||
import CategoryTabs from "./category-tabs";
|
||||
import PublicationCardGrid from "./publication-card";
|
||||
import Cookies from "js-cookie";
|
||||
import SidebarFilterForYou from "./sidebar-filter-for-you";
|
||||
import { useRouter } from "next/navigation";
|
||||
import ForYouCardGrid from "./for-you-card";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
|
||||
export default function PublicationKlLayout() {
|
||||
const [selectedCategory, setSelectedCategory] = useState("SEMUA");
|
||||
const router = useRouter();
|
||||
|
||||
// useEffect(() => {
|
||||
// const roleId = getCookiesDecrypt("urie") ?? "";
|
||||
|
||||
// const allowedRoles = ["6", "7", "2", "3"];
|
||||
|
||||
// if (!allowedRoles.includes(roleId)) {
|
||||
// router.push("/auth");
|
||||
// }
|
||||
// }, [router]);
|
||||
// 🔹 Tab filter untuk SidebarFilterForYou
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
"My Collections" | "Archives" | "Favorites"
|
||||
>("My Collections");
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 py-6 space-y-6">
|
||||
{/* Kategori utama (SEMUA, POLDA, SATKER, dst) */}
|
||||
<CategoryTabs
|
||||
selectedCategory={selectedCategory}
|
||||
onCategoryChange={setSelectedCategory}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
{/* Sidebar Filter */}
|
||||
<div className="lg:w-1/4 xl:w-1/5 w-full">
|
||||
<div className="border rounded p-4 bg-white">
|
||||
<SidebarFilterForYou />
|
||||
<SidebarFilterForYou
|
||||
activeTab={activeTab}
|
||||
onTabChange={(tab) =>
|
||||
setActiveTab(tab as "My Collections" | "Archives" | "Favorites")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Grid Konten */}
|
||||
<div className="flex-1">
|
||||
<ForYouCardGrid selectedCategory={selectedCategory} />
|
||||
<ForYouCardGrid
|
||||
selectedCategory={selectedCategory}
|
||||
filterType={activeTab}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// "use client";
|
||||
// import { useState } from "react";
|
||||
// import CategoryTabs from "./category-tabs";
|
||||
// import SidebarFilterForYou from "./sidebar-filter-for-you";
|
||||
// import { useRouter } from "next/navigation";
|
||||
// import ForYouCardGrid from "./for-you-card";
|
||||
|
||||
// export default function PublicationKlLayout() {
|
||||
// const [selectedCategory, setSelectedCategory] = useState("SEMUA");
|
||||
// const router = useRouter();
|
||||
|
||||
// // useEffect(() => {
|
||||
// // const roleId = getCookiesDecrypt("urie") ?? "";
|
||||
|
||||
// // const allowedRoles = ["6", "7", "2", "3"];
|
||||
|
||||
// // if (!allowedRoles.includes(roleId)) {
|
||||
// // router.push("/auth");
|
||||
// // }
|
||||
// // }, [router]);
|
||||
|
||||
// return (
|
||||
// <div className="max-w-7xl mx-auto px-4 py-6 space-y-6">
|
||||
// <CategoryTabs
|
||||
// selectedCategory={selectedCategory}
|
||||
// onCategoryChange={setSelectedCategory}
|
||||
// />
|
||||
|
||||
// <div className="flex flex-col lg:flex-row gap-6">
|
||||
// <div className="lg:w-1/4 xl:w-1/5 w-full">
|
||||
// <div className="border rounded p-4 bg-white">
|
||||
// <SidebarFilterForYou />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="flex-1">
|
||||
// <ForYouCardGrid selectedCategory={selectedCategory} />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState, useEffect } 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 { Button } from "@/components/ui/button";
|
||||
import { Archive, Star } from "lucide-react";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { getBookmarks } from "@/service/content";
|
||||
import { BookmarkItem } from "@/service/content";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import { Navigation } from "swiper/modules";
|
||||
|
||||
// Format tanggal
|
||||
function formatTanggal(dateString: string) {
|
||||
|
|
@ -28,7 +32,6 @@ function formatTanggal(dateString: string) {
|
|||
);
|
||||
}
|
||||
|
||||
// Function to get link based on typeId
|
||||
function getLink(item: BookmarkItem) {
|
||||
switch (item.article?.typeId) {
|
||||
case 1:
|
||||
|
|
@ -44,236 +47,234 @@ function getLink(item: BookmarkItem) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
type ForYouCardGridProps = {
|
||||
filterType: "My Collections" | "Archives" | "Favorites";
|
||||
selectedCategory?: string;
|
||||
};
|
||||
|
||||
export default function ForYouCardGrid({
|
||||
selectedCategory,
|
||||
title,
|
||||
refresh,
|
||||
isInstitute = false,
|
||||
instituteId = "",
|
||||
}: PublicationCardGridProps) {
|
||||
export default function ForYouCardGrid({ filterType }: ForYouCardGridProps) {
|
||||
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]);
|
||||
}, [filterType]);
|
||||
|
||||
const fetchBookmarks = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getBookmarks(currentPage, limit);
|
||||
|
||||
const response = await getBookmarks(1, 50, filterType);
|
||||
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",
|
||||
});
|
||||
MySwal.fire("Error", "Gagal memuat bookmark.", "error");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching bookmarks:", err);
|
||||
MySwal.fire({
|
||||
icon: "error",
|
||||
title: "Error",
|
||||
text: "Terjadi kesalahan saat memuat bookmark.",
|
||||
confirmButtonColor: "#d33",
|
||||
});
|
||||
console.error(err);
|
||||
MySwal.fire("Error", "Terjadi kesalahan saat memuat bookmark.", "error");
|
||||
} 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 grouped = {
|
||||
image: bookmarks.filter((b) => b.article?.typeId === 1).slice(0, ),
|
||||
video: bookmarks.filter((b) => b.article?.typeId === 2).slice(0, 5),
|
||||
text: bookmarks.filter((b) => b.article?.typeId === 3).slice(0, 5),
|
||||
audio: bookmarks.filter((b) => b.article?.typeId === 4).slice(0, 5),
|
||||
};
|
||||
|
||||
const goToPage = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
const renderImageVideoCard = (bookmark: BookmarkItem) => (
|
||||
<div className="rounded-xl shadow-md bg-white hover:shadow-lg transition-shadow overflow-hidden">
|
||||
<div className="relative w-full h-[200px]">
|
||||
<Link href={getLink(bookmark)}>
|
||||
<Image
|
||||
src={bookmark.article?.thumbnailUrl || "/placeholder.png"}
|
||||
alt={bookmark.article?.title || "No Title"}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<p className="text-xs text-gray-500 mb-1">
|
||||
Disimpan: {formatTanggal(bookmark.createdAt)}
|
||||
</p>
|
||||
<Link href={getLink(bookmark)}>
|
||||
<p className="text-sm font-semibold line-clamp-2 cursor-pointer hover:text-blue-600 transition-colors">
|
||||
{bookmark.article?.title}
|
||||
</p>
|
||||
</Link>
|
||||
<div className="flex gap-2 mt-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex items-center gap-1 text-gray-700 border-gray-300 hover:bg-gray-100"
|
||||
>
|
||||
<Archive className="w-4 h-4" /> Archive
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex items-center gap-1 text-yellow-600 border-yellow-200 hover:bg-yellow-50"
|
||||
>
|
||||
<Star className="w-4 h-4" /> Favorite
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderTextCard = (bookmark: BookmarkItem) => (
|
||||
<div className="cursor-pointer rounded-lg shadow-md overflow-hidden bg-white dark:bg-black dark:border dark:border-gray-500 hover:shadow-lg transition-shadow">
|
||||
{/* Background kuning & ikon dokumen */}
|
||||
<div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Konten bawah */}
|
||||
<div className="p-4 flex flex-col gap-2">
|
||||
<p className="text-xs text-gray-500 mb-1">
|
||||
Disimpan: {formatTanggal(bookmark.createdAt)}
|
||||
</p>
|
||||
<Link
|
||||
href={getLink(bookmark)}
|
||||
className="font-semibold text-gray-900 dark:text-white text-base leading-snug line-clamp-3"
|
||||
>
|
||||
{bookmark.article?.title}
|
||||
</Link>
|
||||
<div className="flex gap-2 mt-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex items-center gap-1 text-gray-700 border-gray-300 hover:bg-gray-100"
|
||||
>
|
||||
<Archive className="w-4 h-4" /> Archive
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex items-center gap-1 text-yellow-600 border-yellow-200 hover:bg-yellow-50"
|
||||
>
|
||||
<Star className="w-4 h-4" /> Favorite
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderAudioCard = (bookmark: BookmarkItem) => (
|
||||
<div className="cursor-pointer bg-white dark:bg-black dark:border dark:border-gray-500 rounded-xl shadow-md hover:shadow-lg transition-shadow overflow-hidden">
|
||||
{/* Background merah & ikon audio */}
|
||||
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[170px] text-white">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Caption */}
|
||||
<Link href={getLink(bookmark)} className="p-4">
|
||||
<p className="text-xs text-gray-500 mb-1">
|
||||
Disimpan: {formatTanggal(bookmark.createdAt)}
|
||||
</p>
|
||||
<p className="text-base font-semibold text-black dark:text-white line-clamp-3">
|
||||
{bookmark.article?.title}
|
||||
</p>
|
||||
</Link>
|
||||
<div className="flex gap-2 mx-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex items-center gap-1 text-gray-700 border-gray-300 hover:bg-gray-100"
|
||||
>
|
||||
<Archive className="w-4 h-4" /> Archive
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="flex items-center gap-1 text-yellow-600 border-yellow-200 hover:bg-yellow-50"
|
||||
>
|
||||
<Star className="w-4 h-4" /> Favorite
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderSection = (
|
||||
title: string,
|
||||
items: BookmarkItem[],
|
||||
type: "imagevideo" | "text" | "audio"
|
||||
) => {
|
||||
if (!items.length) return null;
|
||||
|
||||
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 className="mb-10">
|
||||
<h2 className="text-xl font-bold text-gray-800 mb-4">{title}</h2>
|
||||
<Swiper
|
||||
spaceBetween={20}
|
||||
slidesPerView={1.2}
|
||||
breakpoints={{
|
||||
640: { slidesPerView: 2.2 },
|
||||
1024: { slidesPerView: 3.2 },
|
||||
1280: { slidesPerView: 4.2 },
|
||||
}}
|
||||
navigation
|
||||
modules={[Navigation]}
|
||||
>
|
||||
{items.map((bookmark) => (
|
||||
<SwiperSlide key={bookmark.id}>
|
||||
{type === "imagevideo"
|
||||
? renderImageVideoCard(bookmark)
|
||||
: type === "text"
|
||||
? renderTextCard(bookmark)
|
||||
: renderAudioCard(bookmark)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (bookmarks.length === 0) {
|
||||
if (loading)
|
||||
return (
|
||||
<div className="text-center py-12 text-gray-500">Memuat konten...</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 className="text-gray-400 text-6xl mb-4">📂</div>
|
||||
<h3 className="text-xl font-semibold text-gray-600 mb-2">
|
||||
Tidak Ada Konten di {filterType}
|
||||
</h3>
|
||||
</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 className="space-y-10">
|
||||
{renderSection("📸 Image", grouped.image, "imagevideo")}
|
||||
{renderSection("🎬 Video", grouped.video, "imagevideo")}
|
||||
{renderSection("📝 Text", grouped.text, "text")}
|
||||
{renderSection("🎵 Audio", grouped.audio, "audio")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import { Yarndings_12 } from "next/font/google";
|
||||
import { BurgerButtonIcon } from "../icons";
|
||||
"use client";
|
||||
|
||||
import { Plus } from "lucide-react";
|
||||
|
||||
const filters = ["My Collections", "Archives", "Favorites", "Highlight"];
|
||||
const filters = ["My Collections", "Archives", "Favorites"];
|
||||
|
||||
export default function SidebarFilterForYou() {
|
||||
type SidebarFilterForYouProps = {
|
||||
activeTab: string;
|
||||
onTabChange: (tab: string) => void;
|
||||
};
|
||||
|
||||
export default function SidebarFilterForYou({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
}: SidebarFilterForYouProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex gap-2 items-center border-b-2 mb-3">
|
||||
|
|
@ -13,7 +21,13 @@ export default function SidebarFilterForYou() {
|
|||
</div>
|
||||
<ul className="space-y-6 text-[16px] text-[#757575]">
|
||||
{filters.map((item, idx) => (
|
||||
<li key={idx} className="cursor-pointer hover:font-semibold">
|
||||
<li
|
||||
key={idx}
|
||||
onClick={() => onTabChange(item)}
|
||||
className={`cursor-pointer hover:font-semibold ${
|
||||
activeTab === item ? "font-semibold text-black" : ""
|
||||
}`}
|
||||
>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -21,3 +35,27 @@ export default function SidebarFilterForYou() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// import { Yarndings_12 } from "next/font/google";
|
||||
// import { BurgerButtonIcon } from "../icons";
|
||||
// import { Plus } from "lucide-react";
|
||||
|
||||
// const filters = ["My Collections", "Archives", "Favorites", "Highlight"];
|
||||
|
||||
// export default function SidebarFilterForYou() {
|
||||
// return (
|
||||
// <div>
|
||||
// <div className="flex gap-2 items-center border-b-2 mb-3">
|
||||
// <h2 className="font-semibold text-lg">Create New Collection </h2>
|
||||
// <Plus size={20} />
|
||||
// </div>
|
||||
// <ul className="space-y-6 text-[16px] text-[#757575]">
|
||||
// {filters.map((item, idx) => (
|
||||
// <li key={idx} className="cursor-pointer hover:font-semibold">
|
||||
// {item}
|
||||
// </li>
|
||||
// ))}
|
||||
// </ul>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -474,7 +474,26 @@ export interface BookmarksResponse {
|
|||
};
|
||||
}
|
||||
|
||||
export async function getBookmarks(page = 1, limit = 10) {
|
||||
const url = `bookmarks?page=${page}&limit=${limit}`;
|
||||
// export async function getBookmarks(page = 1, limit = 10) {
|
||||
// const url = `bookmarks?page=${page}&limit=${limit}`;
|
||||
// return httpGetInterceptor(url);
|
||||
// }
|
||||
|
||||
export async function getBookmarks(
|
||||
page = 1,
|
||||
limit = 10,
|
||||
filterType?: "My Collections" | "Archives" | "Favorites"
|
||||
) {
|
||||
let url = `bookmarks?page=${page}&limit=${limit}`;
|
||||
|
||||
// Tambahkan filter berdasarkan tab aktif
|
||||
if (filterType === "Archives") {
|
||||
url += `&status=archived`;
|
||||
} else if (filterType === "Favorites") {
|
||||
url += `&isFavorite=true`;
|
||||
} else {
|
||||
url += `&status=active`;
|
||||
}
|
||||
|
||||
return httpGetInterceptor(url);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue