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() {
|
export default function DetailAudioInfo() {
|
||||||
const params = useParams();
|
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} />;
|
return <AudioDetail id={id} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,13 @@ import DocumentDetail from "@/components/main/content/document-detail";
|
||||||
|
|
||||||
export default function DetailDocumentInfo() {
|
export default function DetailDocumentInfo() {
|
||||||
const params = useParams();
|
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} />;
|
return <DocumentDetail id={id} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ export default function Header() {
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchBookmarks();
|
fetchBookmarks();
|
||||||
}, [userId]); // ← otomatis re-fetch kalau cookie user berubah
|
}, [userId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="max-w-[1350px] mx-auto px-4">
|
<section className="max-w-[1350px] mx-auto px-4">
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
||||||
|
|
||||||
// 🔹 Fungsi format tanggal ke WIB
|
|
||||||
function formatTanggal(dateString: string) {
|
function formatTanggal(dateString: string) {
|
||||||
if (!dateString) return "";
|
if (!dateString) return "";
|
||||||
return (
|
return (
|
||||||
|
|
@ -49,7 +48,6 @@ export default function MediaUpdate() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
// Get slug from URL params
|
|
||||||
const slug = params?.slug as string;
|
const slug = params?.slug as string;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -73,15 +71,15 @@ export default function MediaUpdate() {
|
||||||
function getTypeIdByContentType(contentType: string): string {
|
function getTypeIdByContentType(contentType: string): string {
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case "audiovisual":
|
case "audiovisual":
|
||||||
return "2"; // Video
|
return "2";
|
||||||
case "foto":
|
case "foto":
|
||||||
return "1"; // Image
|
return "1";
|
||||||
case "audio":
|
case "audio":
|
||||||
return "4"; // Audio
|
return "4";
|
||||||
case "text":
|
case "text":
|
||||||
return "3"; // Text
|
return "3";
|
||||||
default:
|
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 to get content type link for "Lihat lebih banyak" button
|
||||||
function getContentTypeLink() {
|
function getContentTypeLink() {
|
||||||
|
const pathname =
|
||||||
|
typeof window !== "undefined" ? window.location.pathname : "";
|
||||||
|
|
||||||
|
const isTenantRoute = pathname.includes("/tenant/");
|
||||||
|
const basePath = isTenantRoute ? `/tenant/${slug}/content` : "/content";
|
||||||
|
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case "audio":
|
case "audio":
|
||||||
return "/tenant/" + slug + "/content/audio";
|
return `${basePath}/audio`;
|
||||||
case "foto":
|
case "foto":
|
||||||
return "/tenant/" + slug + "/content/image";
|
return `${basePath}/image`;
|
||||||
case "audiovisual":
|
case "audiovisual":
|
||||||
return "/tenant/" + slug + "/content/video";
|
return `${basePath}/video`;
|
||||||
case "text":
|
case "text":
|
||||||
return "/tenant/" + slug + "/content/text";
|
return `${basePath}/text`;
|
||||||
default:
|
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") {
|
// if (contentType === "all") {
|
||||||
// setFilteredData(dataToRender);
|
// setFilteredData(dataToRender);
|
||||||
// return;
|
// return;
|
||||||
|
|
@ -154,15 +173,15 @@ export default function MediaUpdate() {
|
||||||
const filtered = dataToRender.filter((item) => {
|
const filtered = dataToRender.filter((item) => {
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case "audiovisual":
|
case "audiovisual":
|
||||||
return item.typeId === 2; // Video
|
return item.typeId === 2;
|
||||||
case "audio":
|
case "audio":
|
||||||
return item.typeId === 4; // Audio
|
return item.typeId === 4;
|
||||||
case "foto":
|
case "foto":
|
||||||
return item.typeId === 1; // Image
|
return item.typeId === 1;
|
||||||
case "text":
|
case "text":
|
||||||
return item.typeId === 3; // Text
|
return item.typeId === 3;
|
||||||
default:
|
default:
|
||||||
return true; // Semua jenis
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -173,11 +192,9 @@ export default function MediaUpdate() {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Tentukan typeId dari contentType
|
|
||||||
const typeId = parseInt(getTypeIdByContentType(contentType));
|
const typeId = parseInt(getTypeIdByContentType(contentType));
|
||||||
setCurrentTypeId(typeId.toString());
|
setCurrentTypeId(typeId.toString());
|
||||||
|
|
||||||
// 🔹 Ambil data artikel dari API
|
|
||||||
const response = await listArticles(
|
const response = await listArticles(
|
||||||
1,
|
1,
|
||||||
10,
|
10,
|
||||||
|
|
@ -204,11 +221,9 @@ export default function MediaUpdate() {
|
||||||
);
|
);
|
||||||
hasil = fallbackRes?.data?.data?.content || [];
|
hasil = fallbackRes?.data?.data?.content || [];
|
||||||
} else {
|
} else {
|
||||||
// ✅ Perhatikan: API kamu mengembalikan `data.data` sebagai array
|
|
||||||
hasil = response?.data?.data || [];
|
hasil = response?.data?.data || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Normalisasi struktur data
|
|
||||||
const transformedData = hasil.map((article: any) => ({
|
const transformedData = hasil.map((article: any) => ({
|
||||||
id: article.id,
|
id: article.id,
|
||||||
title: article.title,
|
title: article.title,
|
||||||
|
|
@ -224,10 +239,8 @@ export default function MediaUpdate() {
|
||||||
...article,
|
...article,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// ✅ INI WAJIB: simpan hasil mapping ke state
|
|
||||||
setDataToRender(transformedData);
|
setDataToRender(transformedData);
|
||||||
|
|
||||||
// 🔹 Sinkronisasi data bookmark
|
|
||||||
const roleId = Number(getCookiesDecrypt("urie"));
|
const roleId = Number(getCookiesDecrypt("urie"));
|
||||||
if (roleId && !isNaN(roleId)) {
|
if (roleId && !isNaN(roleId)) {
|
||||||
const simpananLocal = localStorage.getItem("bookmarkedIds");
|
const simpananLocal = localStorage.getItem("bookmarkedIds");
|
||||||
|
|
@ -263,7 +276,6 @@ export default function MediaUpdate() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Simpan bookmark
|
|
||||||
const handleSave = async (id: number) => {
|
const handleSave = async (id: number) => {
|
||||||
const roleId = Number(getCookiesDecrypt("urie"));
|
const roleId = Number(getCookiesDecrypt("urie"));
|
||||||
if (!roleId || isNaN(roleId)) {
|
if (!roleId || isNaN(roleId)) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { Button } from "../ui/button";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { Card } from "../ui/card";
|
import { Card } from "../ui/card";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
import { Link } from "@/i18n/routing";
|
import { Link } from "@/i18n/routing";
|
||||||
import { DynamicLogoTenant } from "./dynamic-logo-tenant";
|
import { DynamicLogoTenant } from "./dynamic-logo-tenant";
|
||||||
|
|
||||||
|
|
@ -28,86 +28,29 @@ const PUBLIKASI_SUBMENU = [
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
|
||||||
// const [user, setUser] = useState<{
|
|
||||||
// id: number;
|
|
||||||
// name: string;
|
|
||||||
// avatar: string;
|
|
||||||
// } | null>(null);
|
|
||||||
|
|
||||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// useEffect(() => {
|
// 🔍 Fungsi cek login
|
||||||
// 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
|
|
||||||
const checkLoginStatus = () => {
|
const checkLoginStatus = () => {
|
||||||
const roleId = getCookiesDecrypt("urie");
|
const roleId = getCookiesDecrypt("urie");
|
||||||
const fullname = Cookies.get("ufne");
|
const fullname = Cookies.get("ufne");
|
||||||
return roleId && fullname ? true : false;
|
return roleId && fullname ? true : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 🔄 Cek status login saat pertama kali load
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoggedIn(checkLoginStatus());
|
setIsLoggedIn(checkLoginStatus());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// const filteredNavItems = isLoggedIn
|
|
||||||
// ? NAV_ITEMS.filter((item) => item.label !== "Mengikuti")
|
|
||||||
// : NAV_ITEMS;
|
|
||||||
|
|
||||||
// 🔁 Gunakan isLoggedIn untuk menentukan navigasi
|
|
||||||
const filteredNavItems = isLoggedIn
|
const filteredNavItems = isLoggedIn
|
||||||
? NAV_ITEMS.filter((item) => item.label !== "Mengikuti")
|
? NAV_ITEMS.filter((item) => item.label !== "Mengikuti")
|
||||||
: NAV_ITEMS;
|
: NAV_ITEMS;
|
||||||
|
|
||||||
// 🚪 Fungsi logout
|
// 🚪 Fungsi logout
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
// Hapus semua cookie dengan kemungkinan variasi path dan domain
|
|
||||||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||||
Cookies.remove(cookieName, { path: "/" });
|
Cookies.remove(cookieName, { path: "/" });
|
||||||
Cookies.remove(cookieName, {
|
Cookies.remove(cookieName, {
|
||||||
|
|
@ -119,42 +62,13 @@ export default function Navbar() {
|
||||||
domain: window.location.hostname,
|
domain: window.location.hostname,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ubah state login jadi false agar tampilan langsung berubah
|
|
||||||
setIsLoggedIn(false);
|
setIsLoggedIn(false);
|
||||||
setShowProfileMenu(false);
|
setShowProfileMenu(false);
|
||||||
|
|
||||||
// Redirect ke homepage
|
|
||||||
window.location.href = "/";
|
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 username = Cookies.get("username");
|
||||||
const fullname = Cookies.get("ufne");
|
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 (
|
return (
|
||||||
<header className="relative max-w-[1400px] mx-auto flex items-center justify-between px-4 py-3 border-b bg-white z-50">
|
<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
|
fill
|
||||||
className="object-contain"
|
className="object-contain"
|
||||||
/>
|
/>
|
||||||
</div>{" "}
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
className="w-6 h-6 cursor-pointer"
|
className="w-6 h-6 cursor-pointer"
|
||||||
onClick={() => setIsSidebarOpen(true)}
|
onClick={() => setIsSidebarOpen(true)}
|
||||||
|
|
@ -174,10 +88,23 @@ export default function Navbar() {
|
||||||
<DynamicLogoTenant />
|
<DynamicLogoTenant />
|
||||||
</div>
|
</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">
|
<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) => {
|
{filteredNavItems.map((item) => {
|
||||||
const isActive = pathname === item.href;
|
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 (
|
return (
|
||||||
<div key={item.label} className="relative">
|
<div key={item.label} className="relative">
|
||||||
{item.label === "Publikasi" ? (
|
{item.label === "Publikasi" ? (
|
||||||
|
|
@ -221,6 +148,7 @@ export default function Navbar() {
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
href={item.href}
|
href={item.href}
|
||||||
|
onClick={handleClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative text-gray-500 hover:text-black transition-colors",
|
"relative text-gray-500 hover:text-black transition-colors",
|
||||||
isActive && "text-black"
|
isActive && "text-black"
|
||||||
|
|
@ -236,6 +164,8 @@ export default function Navbar() {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{/* 🔹 PROFILE / LOGIN SECTION */}
|
||||||
<nav className="hidden md:flex items-center gap-3 z-10 relative">
|
<nav className="hidden md:flex items-center gap-3 z-10 relative">
|
||||||
{!isLoggedIn ? (
|
{!isLoggedIn ? (
|
||||||
<>
|
<>
|
||||||
|
|
@ -283,9 +213,10 @@ export default function Navbar() {
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{/* 📱 SIDEBAR MOBILE */}
|
||||||
{isSidebarOpen && (
|
{isSidebarOpen && (
|
||||||
<div className="fixed inset-0 z-50 flex">
|
<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
|
<button
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
className="absolute top-4 right-4 text-gray-600"
|
className="absolute top-4 right-4 text-gray-600"
|
||||||
|
|
@ -329,37 +260,39 @@ export default function Navbar() {
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-5 ml-3">
|
<div className="space-y-5 ml-3">
|
||||||
{NAV_ITEMS.map((item) => (
|
{NAV_ITEMS.map((item) => (
|
||||||
<Link
|
<button
|
||||||
key={item.label}
|
key={item.label}
|
||||||
href={item.href}
|
onClick={() => {
|
||||||
className="block text-[15px] text-gray-800"
|
if (item.label === "Untuk Anda") {
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
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}
|
{item.label}
|
||||||
</Link>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-5 text-[16px] font-bold">
|
<div className="space-y-5 text-[16px] font-bold">
|
||||||
<Link
|
<Link href="/about" className="block text-black">
|
||||||
href="/about"
|
|
||||||
className="block text-black text-[16px] font-bold"
|
|
||||||
>
|
|
||||||
Tentang Kami
|
Tentang Kami
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link href="/advertising" className="block text-black">
|
||||||
href="/advertising"
|
|
||||||
className="block text-[16px] font-bold text-black"
|
|
||||||
>
|
|
||||||
Advertising
|
Advertising
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link href="/contact" className="block text-black">
|
||||||
href="/contact"
|
|
||||||
className="block text-[16px] font-bold text-black"
|
|
||||||
>
|
|
||||||
Kontak Kami
|
Kontak Kami
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{!isLoggedIn ? (
|
{!isLoggedIn ? (
|
||||||
<>
|
<>
|
||||||
<Link href="/auth" className="block text-lg text-gray-800">
|
<Link href="/auth" className="block text-lg text-gray-800">
|
||||||
|
|
@ -381,6 +314,7 @@ export default function Navbar() {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="rounded-none p-4">
|
<Card className="rounded-none p-4">
|
||||||
<h2 className="text-[#C6A455] text-center text-lg font-semibold">
|
<h2 className="text-[#C6A455] text-center text-lg font-semibold">
|
||||||
Subscribe to Our Newsletter
|
Subscribe to Our Newsletter
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
import { useState, useEffect } from "react";
|
||||||
import { Calendar, Clock, Eye } from "lucide-react";
|
import { Calendar, Eye } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import {
|
import {
|
||||||
FaFacebookF,
|
FaFacebookF,
|
||||||
FaTiktok,
|
FaTiktok,
|
||||||
|
|
@ -15,31 +14,24 @@ import {
|
||||||
FaLink,
|
FaLink,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from "react-icons/fa";
|
} 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 }) {
|
export default function AudioDetail({ id }: { id: number }) {
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
|
||||||
const [data, setData] = useState<any>(null);
|
const [data, setData] = useState<any>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [selectedDoc, setSelectedDoc] = useState(0);
|
const [copied, setCopied] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState<any>(true);
|
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||||
|
const [selectedAudio, setSelectedAudio] = useState(0);
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
// Salin tautan
|
||||||
const handleCopyLink = async () => {
|
const handleCopyLink = async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(window.location.href);
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
} catch (err) {
|
} 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 () => {
|
const fetchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Try new Articles API first
|
// 1️⃣ Coba API baru
|
||||||
const response = await getArticleDetail(id);
|
const res = await getArticleDetail(id);
|
||||||
console.log("Article Detail API response:", response);
|
console.log("Audio detail API response:", res);
|
||||||
|
|
||||||
if (response?.error) {
|
const article = res?.data?.data;
|
||||||
console.error("Articles API failed, falling back to old API");
|
|
||||||
// Fallback to old API
|
if (article) {
|
||||||
const fallbackResponse = await getDetail(id);
|
const mappedData = {
|
||||||
setData(fallbackResponse?.data?.data);
|
id: article.id,
|
||||||
console.log(
|
title: article.title,
|
||||||
"doc",
|
description: article.description,
|
||||||
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new API response structure
|
// 2️⃣ Fallback ke API lama
|
||||||
const articleData = response?.data?.data;
|
// const fallback = await getDetail(id);
|
||||||
if (articleData) {
|
// setData(fallback?.data?.data);
|
||||||
// Transform article data to match old structure for backward compatibility
|
} catch (err) {
|
||||||
const transformedData = {
|
console.error("Gagal memuat audio:", err);
|
||||||
id: articleData.id,
|
// try {
|
||||||
title: articleData.title,
|
// const fallback = await getDetail(id);
|
||||||
description: articleData.description,
|
// setData(fallback?.data?.data);
|
||||||
createdAt: articleData.createdAt,
|
// } catch (err2) {
|
||||||
clickCount: articleData.viewCount,
|
// console.error("Fallback gagal:", err2);
|
||||||
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 {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id) fetchDetail();
|
if (id) fetchDetail();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||||
<p>Loading...</p>
|
<p>Memuat data audio...</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -154,19 +123,25 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||||
|
{/* Pemutar Audio Utama */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<audio controls className="w-full" src={data?.files[selectedDoc]?.url}>
|
<audio
|
||||||
Your browser does not support the audio element.
|
controls
|
||||||
|
className="w-full"
|
||||||
|
src={data?.files?.[selectedAudio]?.url || ""}
|
||||||
|
>
|
||||||
|
Browser Anda tidak mendukung elemen audio.
|
||||||
</audio>
|
</audio>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Pilihan file audio */}
|
||||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||||
{data?.files?.map((file: any, index: number) => (
|
{data?.files?.map((file: any, index: number) => (
|
||||||
<button
|
<button
|
||||||
key={file?.id}
|
key={file?.id}
|
||||||
onClick={() => setSelectedDoc(index)}
|
onClick={() => setSelectedAudio(index)}
|
||||||
className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
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}`}
|
🎵 {file?.fileName || `Audio ${index + 1}`}
|
||||||
|
|
@ -174,110 +149,45 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
{/* Informasi artikel */}
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<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">
|
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1 text-black">
|
<span className="flex items-center gap-1 text-black">
|
||||||
<Calendar className="w-4 h-4" />
|
<Calendar className="w-4 h-4" />
|
||||||
{new Date(data.createdAt)
|
{new Date(data.createdAt)
|
||||||
.toLocaleString("id-ID", {
|
.toLocaleString("id-ID", {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "short",
|
month: "short",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
hour12: false,
|
hour12: false,
|
||||||
timeZone: "Asia/Jakarta",
|
timeZone: "Asia/Jakarta",
|
||||||
})
|
})
|
||||||
.replace(".", ":")}{" "}
|
.replace(".", ":")}{" "}
|
||||||
WIB
|
WIB
|
||||||
</span>
|
</span>
|
||||||
{/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||||
<Clock className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
{data.time || "-"}
|
{data.clickCount || 0}
|
||||||
</span> */}
|
</span>
|
||||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
<span className="text-black">
|
||||||
<Eye className="w-4 h-4" />
|
Creator: {data.creatorGroupLevelName}
|
||||||
{data.clickCount || 0}
|
</span>
|
||||||
</span>
|
|
||||||
<span className="text-black">
|
|
||||||
{" "}
|
|
||||||
Creator: {data.creatorGroupLevelName}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Deskripsi */}
|
||||||
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
<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">
|
<div className="flex-1 space-y-4">
|
||||||
<h1 className="text-xl font-bold">{data.title}</h1>
|
<h1 className="text-xl font-bold">{data.title}</h1>
|
||||||
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||||
<p>{data.description}</p>
|
<p>{data.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions bawah */}
|
{/* Tombol aksi bawah */}
|
||||||
<div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
<div className="flex flex-wrap justify-center gap-4 my-20">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
|
|
@ -288,7 +198,7 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
</Button>
|
</Button>
|
||||||
<span>COPY LINK</span>
|
<span>COPY LINK</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center relative">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowShareMenu(!showShareMenu)}
|
onClick={() => setShowShareMenu(!showShareMenu)}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
@ -297,25 +207,25 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
<FaShareAlt />
|
<FaShareAlt />
|
||||||
</Button>
|
</Button>
|
||||||
<span>SHARE</span>
|
<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>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/content/video/comment/${id}`}>
|
<Link href={`/content/audio/comment/${id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
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>
|
</Button>
|
||||||
COMMENT
|
COMMENT
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -326,3 +236,332 @@ export default function AudioDetail({ id }: { id: string }) {
|
||||||
</div>
|
</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";
|
"use client";
|
||||||
import Image from "next/image";
|
import { useState, useEffect } from "react";
|
||||||
import { Calendar, Clock, Eye } from "lucide-react";
|
import { Calendar, Eye } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import {
|
import {
|
||||||
FaFacebookF,
|
FaFacebookF,
|
||||||
FaTiktok,
|
FaTiktok,
|
||||||
|
|
@ -15,34 +14,24 @@ import {
|
||||||
FaLink,
|
FaLink,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
import { getDetail } from "@/service/landing/landing";
|
||||||
import VideoPlayer from "@/utils/video-player";
|
import { getArticleDetail } from "@/service/content/content";
|
||||||
import { toBase64, shimmer } from "@/utils/globals";
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
|
||||||
|
|
||||||
export default function DocumentDetail({ id }: { id: string }) {
|
export default function DocumentDetail({ id }: { id: number }) {
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
|
||||||
const [data, setData] = useState<any>(null);
|
const [data, setData] = useState<any>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||||
const [selectedDoc, setSelectedDoc] = useState(0);
|
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 () => {
|
const handleCopyLink = async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(window.location.href);
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
} catch (err) {
|
} 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 () => {
|
const fetchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Try new Articles API first
|
// 1️⃣ Coba ambil dari API baru
|
||||||
const response = await getArticleDetail(id);
|
const res = await getArticleDetail(id);
|
||||||
console.log("Article Detail API response:", response);
|
console.log("Response artikel:", res);
|
||||||
|
|
||||||
if (response?.error) {
|
const article = res?.data?.data;
|
||||||
console.error("Articles API failed, falling back to old API");
|
|
||||||
// Fallback to old API
|
if (article) {
|
||||||
const fallbackResponse = await getDetail(id);
|
const mappedData = {
|
||||||
setData(fallbackResponse?.data?.data);
|
id: article.id,
|
||||||
console.log(
|
title: article.title,
|
||||||
"doc",
|
description: article.description,
|
||||||
fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new API response structure
|
// 2️⃣ Fallback ke API lama
|
||||||
const articleData = response?.data?.data;
|
// const fallback = await getDetail(id);
|
||||||
if (articleData) {
|
// setData(fallback?.data?.data);
|
||||||
// Transform article data to match old structure for backward compatibility
|
} catch (err) {
|
||||||
const transformedData = {
|
console.error("Gagal ambil detail:", err);
|
||||||
id: articleData.id,
|
// try {
|
||||||
title: articleData.title,
|
// const fallback = await getDetail(id);
|
||||||
description: articleData.description,
|
// setData(fallback?.data?.data);
|
||||||
createdAt: articleData.createdAt,
|
// } catch (err2) {
|
||||||
clickCount: articleData.viewCount,
|
// console.error("Fallback gagal:", err2);
|
||||||
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 {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id) fetchDetail();
|
if (id) fetchDetail();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||||
<p>Loading...</p>
|
<p>Memuat data...</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -157,17 +124,32 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||||
<div className="relative">
|
{/* Viewer dokumen utama */}
|
||||||
|
{/* <div className="relative">
|
||||||
<iframe
|
<iframe
|
||||||
src={data?.files[selectedDoc]?.secondaryUrl}
|
src={data?.files?.[selectedDoc]?.secondaryUrl || ""}
|
||||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full"
|
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>
|
</div>
|
||||||
|
|
||||||
|
{/* Pilihan dokumen */}
|
||||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||||
{data?.files?.map((file: any, index: number) => (
|
{data?.files?.map((file: any, index: number) => (
|
||||||
<button
|
<button
|
||||||
key={file?.id}
|
key={file.id}
|
||||||
onClick={() => setSelectedDoc(index)}
|
onClick={() => setSelectedDoc(index)}
|
||||||
className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
className={`px-4 py-2 rounded-md border cursor-pointer hover:bg-gray-100 ${
|
||||||
selectedDoc === index ? "border-red-600 bg-gray-50" : ""
|
selectedDoc === index ? "border-red-600 bg-gray-50" : ""
|
||||||
|
|
@ -178,110 +160,45 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
{/* Informasi artikel */}
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<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">
|
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1 text-black">
|
<span className="flex items-center gap-1 text-black">
|
||||||
<Calendar className="w-4 h-4" />
|
<Calendar className="w-4 h-4" />
|
||||||
{new Date(data.createdAt)
|
{new Date(data.createdAt)
|
||||||
.toLocaleString("id-ID", {
|
.toLocaleString("id-ID", {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "short",
|
month: "short",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
hour12: false,
|
hour12: false,
|
||||||
timeZone: "Asia/Jakarta",
|
timeZone: "Asia/Jakarta",
|
||||||
})
|
})
|
||||||
.replace(".", ":")}{" "}
|
.replace(".", ":")}{" "}
|
||||||
WIB
|
WIB
|
||||||
</span>
|
</span>
|
||||||
{/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||||
<Clock className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
{data.time || "-"}
|
{data.clickCount || 0}
|
||||||
</span> */}
|
</span>
|
||||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
<span className="text-black">
|
||||||
<Eye className="w-4 h-4" />
|
Creator: {data.creatorGroupLevelName}
|
||||||
{data.clickCount || 0}
|
</span>
|
||||||
</span>
|
|
||||||
<span className="text-black">
|
|
||||||
{" "}
|
|
||||||
Creator: {data.creatorGroupLevelName}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Konten deskripsi */}
|
||||||
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
<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">
|
<div className="flex-1 space-y-4">
|
||||||
<h1 className="text-xl font-bold">{data.title}</h1>
|
<h1 className="text-xl font-bold">{data.title}</h1>
|
||||||
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||||
<p>{data.description}</p>
|
<p>{data.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions bawah */}
|
{/* Tombol aksi bawah */}
|
||||||
<div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
<div className="flex flex-wrap justify-center gap-4 my-20">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
|
|
@ -301,6 +218,16 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
<FaShareAlt />
|
<FaShareAlt />
|
||||||
</Button>
|
</Button>
|
||||||
<span>SHARE</span>
|
<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>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Link href={`/content/video/comment/${id}`}>
|
<Link href={`/content/video/comment/${id}`}>
|
||||||
|
|
@ -309,17 +236,7 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
size="lg"
|
size="lg"
|
||||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
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>
|
</Button>
|
||||||
COMMENT
|
COMMENT
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -330,3 +247,333 @@ export default function DocumentDetail({ id }: { id: string }) {
|
||||||
</div>
|
</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";
|
"use client";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
import { useState } from "react";
|
||||||
import CategoryTabs from "./category-tabs";
|
import CategoryTabs from "./category-tabs";
|
||||||
import PublicationCardGrid from "./publication-card";
|
|
||||||
import Cookies from "js-cookie";
|
|
||||||
import SidebarFilterForYou from "./sidebar-filter-for-you";
|
import SidebarFilterForYou from "./sidebar-filter-for-you";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import ForYouCardGrid from "./for-you-card";
|
import ForYouCardGrid from "./for-you-card";
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
|
||||||
|
|
||||||
export default function PublicationKlLayout() {
|
export default function PublicationKlLayout() {
|
||||||
const [selectedCategory, setSelectedCategory] = useState("SEMUA");
|
const [selectedCategory, setSelectedCategory] = useState("SEMUA");
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// useEffect(() => {
|
// 🔹 Tab filter untuk SidebarFilterForYou
|
||||||
// const roleId = getCookiesDecrypt("urie") ?? "";
|
const [activeTab, setActiveTab] = useState<
|
||||||
|
"My Collections" | "Archives" | "Favorites"
|
||||||
// const allowedRoles = ["6", "7", "2", "3"];
|
>("My Collections");
|
||||||
|
|
||||||
// if (!allowedRoles.includes(roleId)) {
|
|
||||||
// router.push("/auth");
|
|
||||||
// }
|
|
||||||
// }, [router]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-4 py-6 space-y-6">
|
<div className="max-w-7xl mx-auto px-4 py-6 space-y-6">
|
||||||
|
{/* Kategori utama (SEMUA, POLDA, SATKER, dst) */}
|
||||||
<CategoryTabs
|
<CategoryTabs
|
||||||
selectedCategory={selectedCategory}
|
selectedCategory={selectedCategory}
|
||||||
onCategoryChange={setSelectedCategory}
|
onCategoryChange={setSelectedCategory}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-col lg:flex-row gap-6">
|
<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="lg:w-1/4 xl:w-1/5 w-full">
|
||||||
<div className="border rounded p-4 bg-white">
|
<div className="border rounded p-4 bg-white">
|
||||||
<SidebarFilterForYou />
|
<SidebarFilterForYou
|
||||||
|
activeTab={activeTab}
|
||||||
|
onTabChange={(tab) =>
|
||||||
|
setActiveTab(tab as "My Collections" | "Archives" | "Favorites")
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Grid Konten */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<ForYouCardGrid selectedCategory={selectedCategory} />
|
<ForYouCardGrid
|
||||||
|
selectedCategory={selectedCategory}
|
||||||
|
filterType={activeTab}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ThumbsUp, ThumbsDown, Trash2 } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getBookmarks, toggleBookmarkById, BookmarkItem } from "@/service/content";
|
import { Button } from "@/components/ui/button";
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
import { Archive, Star } from "lucide-react";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
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
|
// Format tanggal
|
||||||
function formatTanggal(dateString: string) {
|
function formatTanggal(dateString: string) {
|
||||||
|
|
@ -28,7 +32,6 @@ function formatTanggal(dateString: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get link based on typeId
|
|
||||||
function getLink(item: BookmarkItem) {
|
function getLink(item: BookmarkItem) {
|
||||||
switch (item.article?.typeId) {
|
switch (item.article?.typeId) {
|
||||||
case 1:
|
case 1:
|
||||||
|
|
@ -44,236 +47,234 @@ function getLink(item: BookmarkItem) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get content type label
|
type ForYouCardGridProps = {
|
||||||
function getContentTypeLabel(typeId: number) {
|
filterType: "My Collections" | "Archives" | "Favorites";
|
||||||
switch (typeId) {
|
selectedCategory?: string;
|
||||||
case 1:
|
|
||||||
return "📸 Image";
|
|
||||||
case 2:
|
|
||||||
return "🎬 Video";
|
|
||||||
case 3:
|
|
||||||
return "📝 Text";
|
|
||||||
case 4:
|
|
||||||
return "🎵 Audio";
|
|
||||||
default:
|
|
||||||
return "📄 Content";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PublicationCardGridProps = {
|
|
||||||
selectedCategory: string;
|
|
||||||
title?: string;
|
|
||||||
refresh?: boolean;
|
|
||||||
categoryFilter?: string[];
|
|
||||||
formatFilter?: string[];
|
|
||||||
isInstitute?: boolean;
|
|
||||||
instituteId?: string;
|
|
||||||
sortBy?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ForYouCardGrid({
|
export default function ForYouCardGrid({ filterType }: ForYouCardGridProps) {
|
||||||
selectedCategory,
|
|
||||||
title,
|
|
||||||
refresh,
|
|
||||||
isInstitute = false,
|
|
||||||
instituteId = "",
|
|
||||||
}: PublicationCardGridProps) {
|
|
||||||
const [bookmarks, setBookmarks] = useState<BookmarkItem[]>([]);
|
const [bookmarks, setBookmarks] = useState<BookmarkItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
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);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchBookmarks();
|
fetchBookmarks();
|
||||||
}, [currentPage, selectedCategory, title, refresh]);
|
}, [filterType]);
|
||||||
|
|
||||||
const fetchBookmarks = async () => {
|
const fetchBookmarks = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await getBookmarks(currentPage, limit);
|
const response = await getBookmarks(1, 50, filterType);
|
||||||
|
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
setBookmarks(response.data?.data || []);
|
setBookmarks(response.data?.data || []);
|
||||||
setTotalPages(response.data?.meta?.totalPage || 1);
|
|
||||||
setTotalCount(response.data?.meta?.count || 0);
|
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to fetch bookmarks:", response?.error);
|
MySwal.fire("Error", "Gagal memuat bookmark.", "error");
|
||||||
MySwal.fire({
|
|
||||||
icon: "error",
|
|
||||||
title: "Error",
|
|
||||||
text: "Gagal memuat bookmark.",
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error fetching bookmarks:", err);
|
console.error(err);
|
||||||
MySwal.fire({
|
MySwal.fire("Error", "Terjadi kesalahan saat memuat bookmark.", "error");
|
||||||
icon: "error",
|
|
||||||
title: "Error",
|
|
||||||
text: "Terjadi kesalahan saat memuat bookmark.",
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteBookmark = async (bookmarkId: number, articleId: number, articleTitle: string) => {
|
const grouped = {
|
||||||
const result = await MySwal.fire({
|
image: bookmarks.filter((b) => b.article?.typeId === 1).slice(0, ),
|
||||||
title: "Hapus Bookmark",
|
video: bookmarks.filter((b) => b.article?.typeId === 2).slice(0, 5),
|
||||||
text: `Apakah Anda yakin ingin menghapus "${articleTitle}" dari bookmark?`,
|
text: bookmarks.filter((b) => b.article?.typeId === 3).slice(0, 5),
|
||||||
icon: "warning",
|
audio: bookmarks.filter((b) => b.article?.typeId === 4).slice(0, 5),
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
cancelButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "Ya, Hapus!",
|
|
||||||
cancelButtonText: "Batal",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
try {
|
|
||||||
const response = await toggleBookmarkById(articleId);
|
|
||||||
|
|
||||||
if (response?.data?.success || !response?.error) {
|
|
||||||
// Remove from local state
|
|
||||||
setBookmarks(prev => prev.filter(bookmark => bookmark.id !== bookmarkId));
|
|
||||||
setTotalCount(prev => prev - 1);
|
|
||||||
|
|
||||||
MySwal.fire({
|
|
||||||
icon: "success",
|
|
||||||
title: "Berhasil",
|
|
||||||
text: "Bookmark berhasil dihapus.",
|
|
||||||
timer: 1500,
|
|
||||||
showConfirmButton: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
MySwal.fire({
|
|
||||||
icon: "error",
|
|
||||||
title: "Gagal",
|
|
||||||
text: "Gagal menghapus bookmark.",
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error deleting bookmark:", err);
|
|
||||||
MySwal.fire({
|
|
||||||
icon: "error",
|
|
||||||
title: "Error",
|
|
||||||
text: "Terjadi kesalahan saat menghapus bookmark.",
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToPage = (page: number) => {
|
const renderImageVideoCard = (bookmark: BookmarkItem) => (
|
||||||
setCurrentPage(page);
|
<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 (
|
return (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="mb-10">
|
||||||
{Array.from({ length: 6 }).map((_, index) => (
|
<h2 className="text-xl font-bold text-gray-800 mb-4">{title}</h2>
|
||||||
<div key={index} className="rounded-xl shadow-md overflow-hidden bg-white animate-pulse">
|
<Swiper
|
||||||
<div className="w-full h-[204px] bg-gray-200"></div>
|
spaceBetween={20}
|
||||||
<div className="p-3 space-y-2">
|
slidesPerView={1.2}
|
||||||
<div className="h-4 bg-gray-200 rounded"></div>
|
breakpoints={{
|
||||||
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
|
640: { slidesPerView: 2.2 },
|
||||||
<div className="h-4 bg-gray-200 rounded"></div>
|
1024: { slidesPerView: 3.2 },
|
||||||
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
|
1280: { slidesPerView: 4.2 },
|
||||||
</div>
|
}}
|
||||||
</div>
|
navigation
|
||||||
))}
|
modules={[Navigation]}
|
||||||
|
>
|
||||||
|
{items.map((bookmark) => (
|
||||||
|
<SwiperSlide key={bookmark.id}>
|
||||||
|
{type === "imagevideo"
|
||||||
|
? renderImageVideoCard(bookmark)
|
||||||
|
: type === "text"
|
||||||
|
? renderTextCard(bookmark)
|
||||||
|
: renderAudioCard(bookmark)}
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<div className="text-gray-400 text-6xl mb-4">📚</div>
|
<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>
|
<h3 className="text-xl font-semibold text-gray-600 mb-2">
|
||||||
<p className="text-gray-500">Anda belum menyimpan artikel apapun ke bookmark.</p>
|
Tidak Ada Konten di {filterType}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-10">
|
||||||
{/* Header */}
|
{renderSection("📸 Image", grouped.image, "imagevideo")}
|
||||||
<div className="mb-6">
|
{renderSection("🎬 Video", grouped.video, "imagevideo")}
|
||||||
<h2 className="text-2xl font-semibold text-gray-800 mb-2">Bookmark Saya</h2>
|
{renderSection("📝 Text", grouped.text, "text")}
|
||||||
<p className="text-gray-600">Total {totalCount} artikel tersimpan</p>
|
{renderSection("🎵 Audio", grouped.audio, "audio")}
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
import { Yarndings_12 } from "next/font/google";
|
"use client";
|
||||||
import { BurgerButtonIcon } from "../icons";
|
|
||||||
import { Plus } from "lucide-react";
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex gap-2 items-center border-b-2 mb-3">
|
<div className="flex gap-2 items-center border-b-2 mb-3">
|
||||||
|
|
@ -13,7 +21,13 @@ export default function SidebarFilterForYou() {
|
||||||
</div>
|
</div>
|
||||||
<ul className="space-y-6 text-[16px] text-[#757575]">
|
<ul className="space-y-6 text-[16px] text-[#757575]">
|
||||||
{filters.map((item, idx) => (
|
{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}
|
{item}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
@ -21,3 +35,27 @@ export default function SidebarFilterForYou() {
|
||||||
</div>
|
</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) {
|
// export async function getBookmarks(page = 1, limit = 10) {
|
||||||
const url = `bookmarks?page=${page}&limit=${limit}`;
|
// 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);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue