fix: all detail content
This commit is contained in:
parent
1ce44acc3c
commit
ef4887cfc1
|
|
@ -1,158 +1,152 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { Calendar, 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 {
|
|
||||||
FaFacebookF,
|
|
||||||
FaTiktok,
|
|
||||||
FaYoutube,
|
|
||||||
FaWhatsapp,
|
|
||||||
FaInstagram,
|
|
||||||
FaTwitter,
|
|
||||||
FaCheck,
|
|
||||||
FaLink,
|
|
||||||
FaShareAlt,
|
|
||||||
} from "react-icons/fa";
|
|
||||||
import { getDetail } from "@/service/landing/landing";
|
import { getDetail } from "@/service/landing/landing";
|
||||||
|
import { BarWave } from "react-cssfx-loading";
|
||||||
|
import WaveSurfer from "wavesurfer.js";
|
||||||
import { getArticleDetail } from "@/service/content/content";
|
import { getArticleDetail } from "@/service/content/content";
|
||||||
|
|
||||||
export default function AudioDetail({ id }: { id: number }) {
|
export default function AudioDetail({ id }: { id: number }) {
|
||||||
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 [audioLoaded, setAudioLoaded] = useState(false);
|
||||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
const [playing, setPlaying] = useState(false);
|
||||||
|
const [volume, setVolume] = useState(0.5);
|
||||||
|
const waveformRef = useRef<HTMLDivElement>(null);
|
||||||
|
const wavesurfer = useRef<any>(null);
|
||||||
const [selectedAudio, setSelectedAudio] = useState(0);
|
const [selectedAudio, setSelectedAudio] = useState(0);
|
||||||
|
|
||||||
// Salin tautan
|
// Ambil data audio dari API lama
|
||||||
const handleCopyLink = async () => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(window.location.href);
|
|
||||||
setCopied(true);
|
|
||||||
setTimeout(() => setCopied(false), 2000);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Gagal menyalin link:", 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(() => {
|
useEffect(() => {
|
||||||
const fetchDetail = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// 1️⃣ Coba API baru
|
|
||||||
const res = await getArticleDetail(id);
|
const res = await getArticleDetail(id);
|
||||||
console.log("Audio detail API response:", res);
|
const detail = res?.data?.data;
|
||||||
|
if (detail) setData(detail);
|
||||||
const article = res?.data?.data;
|
|
||||||
|
|
||||||
if (article) {
|
|
||||||
const mappedData = {
|
|
||||||
id: article.id,
|
|
||||||
title: article.title,
|
|
||||||
description: article.description,
|
|
||||||
createdAt: article.createdAt,
|
|
||||||
clickCount: article.viewCount,
|
|
||||||
creatorGroupLevelName: article.createdByName || "Unknown",
|
|
||||||
uploadedBy: {
|
|
||||||
publisher: article.createdByName || "MABES POLRI",
|
|
||||||
},
|
|
||||||
files:
|
|
||||||
article.files?.map((f: any) => ({
|
|
||||||
id: f.id,
|
|
||||||
fileName: f.file_name,
|
|
||||||
url: f.file_url,
|
|
||||||
secondaryUrl: f.file_url,
|
|
||||||
fileAlt: f.file_alt,
|
|
||||||
size: f.size,
|
|
||||||
createdAt: f.created_at,
|
|
||||||
updatedAt: f.updated_at,
|
|
||||||
})) || [],
|
|
||||||
};
|
|
||||||
setData(mappedData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2️⃣ Fallback ke API lama
|
|
||||||
// const fallback = await getDetail(id);
|
|
||||||
// setData(fallback?.data?.data);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Gagal memuat audio:", err);
|
|
||||||
// try {
|
|
||||||
// const fallback = await getDetail(id);
|
|
||||||
// setData(fallback?.data?.data);
|
|
||||||
// } catch (err2) {
|
|
||||||
// console.error("Fallback gagal:", err2);
|
|
||||||
// }
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
fetchData();
|
||||||
if (id) fetchDetail();
|
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
if (loading) {
|
// Inisialisasi WaveSurfer
|
||||||
return (
|
useEffect(() => {
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
if (!data?.files?.[selectedAudio]?.url) return;
|
||||||
<p>Memuat data audio...</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data) {
|
// Hancurkan instance sebelumnya jika ada
|
||||||
return (
|
if (wavesurfer.current) wavesurfer.current.destroy();
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
|
||||||
<p>Data tidak ditemukan</p>
|
const url = data.files[selectedAudio].url;
|
||||||
</div>
|
|
||||||
);
|
const options = {
|
||||||
}
|
container: waveformRef.current!,
|
||||||
|
waveColor: "#E0E0E0",
|
||||||
|
progressColor: "#FFC831",
|
||||||
|
cursorColor: "#000",
|
||||||
|
barWidth: 2,
|
||||||
|
barRadius: 2,
|
||||||
|
responsive: true,
|
||||||
|
height: 80,
|
||||||
|
normalize: true,
|
||||||
|
partialRender: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ws = WaveSurfer.create(options);
|
||||||
|
wavesurfer.current = ws;
|
||||||
|
ws.load(url);
|
||||||
|
|
||||||
|
ws.on("ready", () => {
|
||||||
|
setAudioLoaded(true);
|
||||||
|
ws.setVolume(volume);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on("finish", () => {
|
||||||
|
setPlaying(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ws.destroy();
|
||||||
|
};
|
||||||
|
}, [data?.files, selectedAudio]);
|
||||||
|
|
||||||
|
const handlePlayPause = () => {
|
||||||
|
if (!wavesurfer.current) return;
|
||||||
|
wavesurfer.current.playPause();
|
||||||
|
setPlaying(!playing);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVolumeChange = (e: any) => {
|
||||||
|
const vol = parseFloat(e.target.value);
|
||||||
|
setVolume(vol);
|
||||||
|
wavesurfer.current?.setVolume(vol);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <p className="p-6">Memuat audio...</p>;
|
||||||
|
if (!data) return <p className="p-6">Data tidak ditemukan</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
<div className="max-w-5xl mx-auto px-4 py-6 space-y-6">
|
||||||
{/* Pemutar Audio Utama */}
|
{/* 🎵 Player */}
|
||||||
<div className="relative">
|
<div className="relative flex flex-col items-start">
|
||||||
<audio
|
<div className="flex items-center gap-4">
|
||||||
controls
|
<button
|
||||||
className="w-full"
|
onClick={handlePlayPause}
|
||||||
src={data?.files?.[selectedAudio]?.url || ""}
|
className="w-12 h-12 bg-yellow-500 rounded-full flex justify-center items-center text-white text-xl hover:bg-yellow-600"
|
||||||
>
|
>
|
||||||
Browser Anda tidak mendukung elemen audio.
|
{playing ? "❚❚" : "▶"}
|
||||||
</audio>
|
</button>
|
||||||
|
|
||||||
|
{/* Loading sebelum waveform siap */}
|
||||||
|
{!audioLoaded && (
|
||||||
|
<BarWave
|
||||||
|
color="#ffc831"
|
||||||
|
width="60px"
|
||||||
|
height="25px"
|
||||||
|
duration="2s"
|
||||||
|
className="ml-5"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Waveform */}
|
||||||
|
<div ref={waveformRef} className="w-[300px] sm:w-[600px] ml-4"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pilihan file audio */}
|
{/* Volume control */}
|
||||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
{audioLoaded && (
|
||||||
|
<div className="flex items-center gap-2 mt-2">
|
||||||
|
<span>🔊</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.05"
|
||||||
|
value={volume}
|
||||||
|
onChange={handleVolumeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 🔸 Daftar File Audio */}
|
||||||
|
<div className="py-4 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={() => setSelectedAudio(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 ${
|
||||||
selectedAudio === index ? "border-red-600 bg-gray-50" : ""
|
selectedAudio === index ? "border-yellow-600 bg-yellow-50" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
🎵 {file?.fileName || `Audio ${index + 1}`}
|
🎧 {file?.fileName || `Audio ${index + 1}`}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Informasi artikel */}
|
{/* 🗓️ Informasi Artikel */}
|
||||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
<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?.userLevel?.name || "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" />
|
||||||
|
|
@ -173,65 +167,16 @@ export default function AudioDetail({ id }: { id: number }) {
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
{data.clickCount || 0}
|
{data.clickCount || 0}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-black">
|
<span className="text-black">Creator: {data.creatorName}</span>
|
||||||
Creator: {data.creatorGroupLevelName}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Deskripsi */}
|
{/* 📝 Deskripsi */}
|
||||||
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
<div className="mt-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
|
||||||
<p>{data.description}</p>
|
className="text-base text-gray-700 leading-relaxed space-y-3 mt-2"
|
||||||
</div>
|
dangerouslySetInnerHTML={{ __html: data.htmlDescription }}
|
||||||
|
/>
|
||||||
{/* Tombol aksi bawah */}
|
|
||||||
<div className="flex flex-wrap 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 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 z-10">
|
|
||||||
<SocialItem icon={<FaFacebookF />} label="Facebook" />
|
|
||||||
<SocialItem icon={<FaTiktok />} label="TikTok" />
|
|
||||||
<SocialItem icon={<FaYoutube />} label="YouTube" />
|
|
||||||
<SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
|
||||||
<SocialItem icon={<FaInstagram />} label="Instagram" />
|
|
||||||
<SocialItem icon={<FaTwitter />} label="Twitter" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Link href={`/content/audio/comment/${id}`}>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
|
||||||
>
|
|
||||||
💬
|
|
||||||
</Button>
|
|
||||||
COMMENT
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -70,14 +70,26 @@ export default function DocumentDetail({ id }: { id: number }) {
|
||||||
uploadedBy: {
|
uploadedBy: {
|
||||||
publisher: article.createdByName || "MABES POLRI",
|
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,
|
||||||
|
// })) || [],
|
||||||
files:
|
files:
|
||||||
article.files?.map((f: any) => ({
|
article.files?.map((f: any) => ({
|
||||||
id: f.id,
|
id: f.id,
|
||||||
fileName: f.file_name,
|
fileName: f.fileName || f.file_name,
|
||||||
url: f.file_url,
|
url: f.fileUrl || f.file_url,
|
||||||
secondaryUrl: f.file_url,
|
secondaryUrl: f.fileUrl || f.file_url, // 🟢 gunakan field yang benar
|
||||||
fileThumbnail: f.file_thumbnail,
|
fileThumbnail: f.fileThumbnail || f.file_thumbnail,
|
||||||
fileAlt: f.file_alt,
|
fileAlt: f.fileAlt || f.file_alt,
|
||||||
size: f.size,
|
size: f.size,
|
||||||
createdAt: f.created_at,
|
createdAt: f.created_at,
|
||||||
updatedAt: f.updated_at,
|
updatedAt: f.updated_at,
|
||||||
|
|
@ -125,13 +137,45 @@ export default function DocumentDetail({ id }: { id: number }) {
|
||||||
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">
|
||||||
{/* Viewer dokumen utama */}
|
{/* Viewer dokumen utama */}
|
||||||
{/* <div className="relative">
|
<div className="relative">
|
||||||
|
{data?.files?.[selectedDoc]?.secondaryUrl ? (
|
||||||
|
<>
|
||||||
|
{console.log(
|
||||||
|
"📄 File URL:",
|
||||||
|
data?.files?.[selectedDoc]?.secondaryUrl
|
||||||
|
)}
|
||||||
|
|
||||||
<iframe
|
<iframe
|
||||||
src={data?.files?.[selectedDoc]?.secondaryUrl || ""}
|
key={selectedDoc}
|
||||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full"
|
src={`https://docs.google.com/gview?url=${encodeURIComponent(
|
||||||
|
data?.files?.[selectedDoc]?.secondaryUrl || ""
|
||||||
|
)}&embedded=true`}
|
||||||
|
className="rounded-lg w-full h-[600px] border"
|
||||||
|
onError={(e) => {
|
||||||
|
console.error("❌ Gagal load iframe:", e);
|
||||||
|
window.open(data?.files?.[selectedDoc]?.secondaryUrl, "_blank");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div> */}
|
|
||||||
<div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
<p className="text-center text-xs text-gray-500 mt-2">
|
||||||
|
Jika preview tidak muncul,{" "}
|
||||||
|
<a
|
||||||
|
href={data?.files?.[selectedDoc]?.secondaryUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="text-blue-600 underline"
|
||||||
|
>
|
||||||
|
klik di sini untuk buka file
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-[300px] flex items-center justify-center bg-gray-100 text-gray-500 rounded-lg">
|
||||||
|
Tidak ada file untuk ditampilkan.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="bg-[#e0c350] flex items-center justify-center h-[170px] text-white">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="150"
|
width="150"
|
||||||
|
|
@ -143,7 +187,7 @@ export default function DocumentDetail({ id }: { id: number }) {
|
||||||
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"
|
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>
|
</svg>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Pilihan dokumen */}
|
{/* 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">
|
||||||
|
|
|
||||||
|
|
@ -160,9 +160,11 @@ export default function ImageDetail({ id }: { id: number }) {
|
||||||
width={2560}
|
width={2560}
|
||||||
height={1440}
|
height={1440}
|
||||||
src={
|
src={
|
||||||
data?.files?.[selectedImage]?.url ||
|
data?.files?.[selectedImage]?.url?.trim()
|
||||||
data?.thumbnailUrl || // ✅ fallback ke thumbnailUrl
|
? data.files[selectedImage].url
|
||||||
"/nodata.png"
|
: data?.thumbnailUrl?.trim()
|
||||||
|
? data.thumbnailUrl
|
||||||
|
: "/assets/empty-data.png"
|
||||||
}
|
}
|
||||||
alt="Main"
|
alt="Main"
|
||||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,13 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
const response = await getArticleDetail(id);
|
const response = await getArticleDetail(id);
|
||||||
console.log("Article Detail API response:", response);
|
console.log("Article Detail API response:", response);
|
||||||
|
|
||||||
if (response?.error) {
|
// if (response?.error) {
|
||||||
console.error("Articles API failed, falling back to old API");
|
// console.error("Articles API failed, falling back to old API");
|
||||||
// Fallback to old API
|
// // Fallback to old API
|
||||||
const fallbackResponse = await getDetail(id);
|
// const fallbackResponse = await getDetail(id);
|
||||||
setData(fallbackResponse?.data?.data);
|
// setData(fallbackResponse?.data?.data);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Handle new API response structure
|
// Handle new API response structure
|
||||||
const articleData = response?.data?.data;
|
const articleData = response?.data?.data;
|
||||||
|
|
@ -77,9 +77,10 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
clickCount: articleData.viewCount,
|
clickCount: articleData.viewCount,
|
||||||
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||||
uploadedBy: {
|
uploadedBy: {
|
||||||
publisher: articleData.createdByName || "MABES POLRI"
|
publisher: articleData.createdByName || "MABES POLRI",
|
||||||
},
|
},
|
||||||
files: articleData.files?.map((file: any) => ({
|
files:
|
||||||
|
articleData.files?.map((file: any) => ({
|
||||||
id: file.id,
|
id: file.id,
|
||||||
url: file.file_url,
|
url: file.file_url,
|
||||||
fileName: file.file_name,
|
fileName: file.file_name,
|
||||||
|
|
@ -92,10 +93,11 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
downloadCount: file.download_count,
|
downloadCount: file.download_count,
|
||||||
createdAt: file.created_at,
|
createdAt: file.created_at,
|
||||||
updatedAt: file.updated_at,
|
updatedAt: file.updated_at,
|
||||||
thumbnailFileUrl: file.file_thumbnail || articleData.thumbnailUrl,
|
thumbnailFileUrl:
|
||||||
...file
|
file.file_thumbnail || articleData.thumbnailUrl,
|
||||||
|
...file,
|
||||||
})) || [],
|
})) || [],
|
||||||
...articleData
|
...articleData,
|
||||||
};
|
};
|
||||||
|
|
||||||
setData(transformedData);
|
setData(transformedData);
|
||||||
|
|
@ -104,8 +106,8 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
console.error("Error fetching detail:", error);
|
console.error("Error fetching detail:", error);
|
||||||
// Try fallback to old API if new API fails
|
// Try fallback to old API if new API fails
|
||||||
try {
|
try {
|
||||||
const fallbackResponse = await getDetail(id);
|
// const fallbackResponse = await getDetail(id);
|
||||||
setData(fallbackResponse?.data?.data);
|
// setData(fallbackResponse?.data?.data);
|
||||||
} catch (fallbackError) {
|
} catch (fallbackError) {
|
||||||
console.error("Fallback API also failed:", fallbackError);
|
console.error("Fallback API also failed:", fallbackError);
|
||||||
}
|
}
|
||||||
|
|
@ -134,28 +136,48 @@ export default function VideoDetail({ 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">
|
||||||
|
{/* Bagian Video Utama */}
|
||||||
<div className="relative max-h-screen overflow-hidden">
|
<div className="relative max-h-screen overflow-hidden">
|
||||||
<div className="w-full max-h-screen aspect-video">
|
<div className="w-full max-h-screen aspect-video">
|
||||||
<div className="w-full h-full object-contain">
|
<div className="w-full h-full object-contain">
|
||||||
<VideoPlayer url={data?.files[selectedVideo]?.url} />
|
<VideoPlayer
|
||||||
|
url={
|
||||||
|
// data?.files?.[selectedVideo]?.secondaryUrl?.trim()
|
||||||
|
// ? data.files[selectedVideo].secondaryUrl
|
||||||
|
// : data?.files?.[selectedVideo]?.fileUrl?.includes("viewer/")
|
||||||
|
// ? `${process.env.NEXT_PUBLIC_API}/media/view?id=${data.files[selectedVideo].id}&operation=file&type=video`
|
||||||
|
// : data?.files?.[selectedVideo]?.fileUrl?.trim()
|
||||||
|
// ?
|
||||||
|
data.files[selectedVideo].fileUrl
|
||||||
|
// : "/notfound.mp4"
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-4 left-4"></div>
|
<div className="absolute top-4 left-4"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-2 flex flex-row gap-3">
|
{/* Thumbnail bawah */}
|
||||||
|
<div className="py-2 flex flex-row gap-3 flex-wrap">
|
||||||
{data?.files?.map((file: any, index: number) => (
|
{data?.files?.map((file: any, index: number) => (
|
||||||
<div
|
<div
|
||||||
key={file?.id}
|
key={file?.id}
|
||||||
onClick={() => setSelectedVideo(index)}
|
onClick={() => setSelectedVideo(index)}
|
||||||
className="cursor-pointer flex flex-col items-center gap-1"
|
className="cursor-pointer flex flex-col items-center gap-1"
|
||||||
>
|
>
|
||||||
<img
|
<Image
|
||||||
src={file?.thumbnailFileUrl}
|
src={
|
||||||
alt={file?.fileName}
|
file?.thumbnailFileUrl?.trim()
|
||||||
className="w-32 h-20 object-cover rounded"
|
? file.thumbnailFileUrl
|
||||||
|
: "/notfound.png"
|
||||||
|
}
|
||||||
|
alt={file?.fileName || "thumbnail"}
|
||||||
|
width={160}
|
||||||
|
height={90}
|
||||||
|
className={`w-32 h-20 object-cover rounded-md hover:ring-2 ${
|
||||||
|
selectedVideo === index ? "ring-2 ring-red-600" : ""
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
{/* <p className="text-sm text-center">{file?.fileName}</p> */}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -197,7 +219,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
|
|
||||||
<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 */}
|
{/* Sidebar actions */}
|
||||||
<div className="hidden md:flex flex-col gap-4 relative z-10">
|
{/* <div className="hidden md:flex flex-col gap-4 relative z-10">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
|
|
@ -253,7 +275,7 @@ export default function VideoDetail({ id }: { id: string }) {
|
||||||
COMMENT
|
COMMENT
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 space-y-4">
|
<div className="flex-1 space-y-4">
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@
|
||||||
"tus-js-client": "^4.3.1",
|
"tus-js-client": "^4.3.1",
|
||||||
"use-places-autocomplete": "^4.0.1",
|
"use-places-autocomplete": "^4.0.1",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
|
"wavesurfer.js": "^7.11.0",
|
||||||
"yup": "^1.6.1",
|
"yup": "^1.6.1",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
|
|
@ -18923,6 +18924,8 @@
|
||||||
},
|
},
|
||||||
"node_modules/react-cssfx-loading": {
|
"node_modules/react-cssfx-loading": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-cssfx-loading/-/react-cssfx-loading-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-0SnS6HpaeLSaTxNuND6sAKTQmoKgjwFb9G2ltyEMmA5ARNN6TRQfiJ8PfaYM9RwVEOhDxIzGI7whb2zeI1VRxw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"goober": "^2.1.10"
|
"goober": "^2.1.10"
|
||||||
|
|
@ -22558,9 +22561,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wavesurfer.js": {
|
"node_modules/wavesurfer.js": {
|
||||||
"version": "7.8.4",
|
"version": "7.11.0",
|
||||||
"license": "BSD-3-Clause",
|
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.11.0.tgz",
|
||||||
"peer": true
|
"integrity": "sha512-LOGdIBIKv/roYuQYClhoqhwbIdQL1GfobLnS2vx0heoLD9lu57OUHWE2DIsCNXBvCsmmbkUvJq9W8bPLPbikGw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/wcwidth": {
|
"node_modules/wcwidth": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@
|
||||||
"tus-js-client": "^4.3.1",
|
"tus-js-client": "^4.3.1",
|
||||||
"use-places-autocomplete": "^4.0.1",
|
"use-places-autocomplete": "^4.0.1",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
|
"wavesurfer.js": "^7.11.0",
|
||||||
"yup": "^1.6.1",
|
"yup": "^1.6.1",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
|
|
@ -30,7 +30,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ url }) => {
|
||||||
controls
|
controls
|
||||||
playing
|
playing
|
||||||
muted
|
muted
|
||||||
pip={false} //
|
pip={false}
|
||||||
config={{
|
config={{
|
||||||
file: {
|
file: {
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue