update details

This commit is contained in:
Anang Yusman 2026-02-02 13:54:10 +08:00
parent c2da413522
commit 187fe3a7c9
4 changed files with 113 additions and 64 deletions

View File

@ -5,6 +5,7 @@ import Link from "next/link";
import { import {
getArticleById, getArticleById,
getArticleBySlug, getArticleBySlug,
getArticleFiles,
getListArticle, getListArticle,
} from "@/service/article"; } from "@/service/article";
import { close, error, loading } from "@/config/swal"; import { close, error, loading } from "@/config/swal";
@ -75,13 +76,13 @@ export default function DetailContent() {
startDate: null, startDate: null,
endDate: null, endDate: null,
}); });
const [detailfiles, setDetailFiles] = useState<any>([]); const [detailFiles, setDetailFiles] = useState<any[]>([]);
const [mainImage, setMainImage] = useState(0); const [mainImage, setMainImage] = useState(0);
const [thumbnail, setThumbnail] = useState("-"); const [thumbnail, setThumbnail] = useState("-");
const [diseId, setDiseId] = useState(0); const [diseId, setDiseId] = useState(0);
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]); const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number | null>( const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
null null,
); );
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
@ -297,18 +298,39 @@ export default function DetailContent() {
initStateData(); initStateData();
}, [listCategory]); }, [listCategory]);
useEffect(() => {
setSelectedIndex(0);
}, [detailFiles]);
async function initStateData() { async function initStateData() {
loading(); loading();
const res = await getArticleBySlug(slug); try {
const data = res?.data?.data; // 1⃣ Ambil artikel by slug
const res = await getArticleBySlug(slug);
const data = res?.data?.data;
setThumbnail(data?.thumbnailUrl); if (!data?.id) return;
setDiseId(data?.aiArticleId);
setDetailFiles(data?.files); setArticleDetail(data);
setArticleDetail(data); // <-- Add this setThumbnail(data.thumbnailUrl);
close(); setDiseId(data.aiArticleId);
// 2⃣ Ambil SEMUA article files
const filesRes = await getArticleFiles();
const allFiles = filesRes?.data?.data ?? [];
// 3⃣ FILTER sesuai articleId
const filteredFiles = allFiles.filter(
(file: any) => file.articleId === data.id,
);
setDetailFiles(filteredFiles);
} catch (error) {
console.error("Init state detail error:", error);
} finally {
close();
}
} }
// if (!articleDetail?.files || articleDetail?.files?.length === 0) { // if (!articleDetail?.files || articleDetail?.files?.length === 0) {
// return ( // return (
// <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg"> // <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
@ -367,15 +389,18 @@ export default function DetailContent() {
</span> </span>
<span></span> <span></span>
<span> <span>
<span> {new Date(articleDetail?.publishedAt ?? articleDetail?.createdAt)
{new Date( .toLocaleString("id-ID", {
articleDetail?.publishedAt ?? articleDetail?.createdAt
).toLocaleDateString("id-ID", {
day: "numeric", day: "numeric",
month: "long", month: "long",
year: "numeric", year: "numeric",
})} hour: "2-digit",
</span> minute: "2-digit",
hour12: false,
timeZone: "Asia/Jakarta",
})
.replace("pukul ", "")}{" "}
WIB
</span> </span>
<span></span> <span></span>
<span>{articleDetail?.categories?.[0]?.title}</span> <span>{articleDetail?.categories?.[0]?.title}</span>
@ -383,36 +408,30 @@ export default function DetailContent() {
<div className="w-full h-auto mb-6"> <div className="w-full h-auto mb-6">
{/* Gambar utama */} {/* Gambar utama */}
{articleDetail?.files && articleDetail.files.length > 0 ? ( {detailFiles.length > 0 ? (
<> <>
{/* Gambar utama */} <Image
<div className="w-full"> src={detailFiles[selectedIndex]?.fileUrl}
<Image alt={detailFiles[selectedIndex]?.fileAlt || "Berita"}
src={articleDetail.files[selectedIndex]?.fileUrl} width={800}
alt={ height={400}
articleDetail.files[selectedIndex]?.fileAlt || "Berita" className="rounded-lg w-full object-cover"
} />
width={800}
height={400}
className="rounded-lg w-full object-cover"
/>
</div>
{/* Thumbnail */}
<div className="flex gap-2 mt-3 overflow-x-auto"> <div className="flex gap-2 mt-3 overflow-x-auto">
{articleDetail.files.map((file: any, index: number) => ( {detailFiles.map((file, index) => (
<button <button
key={file?.id || index} key={file.id}
onClick={() => setSelectedIndex(index)} onClick={() => setSelectedIndex(index)}
className={`border-2 rounded-lg overflow-hidden ${ className={`border-2 rounded-lg ${
selectedIndex === index selectedIndex === index
? "border-red-500" ? "border-red-500"
: "border-transparent" : "border-transparent"
}`} }`}
> >
<Image <Image
src={file?.fileUrl} src={file.fileUrl}
alt={file?.fileAlt || "Thumbnail"} alt={file.fileAlt || "Thumbnail"}
width={100} width={100}
height={80} height={80}
className="object-cover" className="object-cover"
@ -422,9 +441,8 @@ export default function DetailContent() {
</div> </div>
</> </>
) : ( ) : (
// Jika file kosong/null tapi tetap render data lainnya <div className="h-[400px] flex items-center justify-center bg-gray-100 rounded-lg">
<div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg"> <p className="text-gray-400">Gambar tidak tersedia</p>
<p className="text-gray-400 text-sm">Gambar tidak tersedia</p>
</div> </div>
)} )}
@ -504,7 +522,7 @@ export default function DetailContent() {
<div <div
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: decodeHtmlString( __html: decodeHtmlString(
articleDetail?.htmlDescription || "" articleDetail?.htmlDescription || "",
), ),
}} }}
/> />
@ -814,7 +832,7 @@ export default function DetailContent() {
day: "2-digit", day: "2-digit",
month: "long", month: "long",
year: "numeric", year: "numeric",
} },
)} )}
</p> </p>
</div> </div>
@ -846,7 +864,7 @@ export default function DetailContent() {
day: "2-digit", day: "2-digit",
month: "long", month: "long",
year: "numeric", year: "numeric",
} },
)} )}
</p> </p>
</div> </div>

View File

@ -514,7 +514,7 @@ export default function CreateArticleForm() {
id="title" id="title"
type="text" type="text"
placeholder="Masukkan judul artikel" placeholder="Masukkan judul artikel"
className="w-full border rounded-lg dark:border-gray-400" className="h-16 px-4 text-2xl leading-tight"
{...field} {...field}
/> />
)} )}

View File

@ -23,6 +23,7 @@ import {
deleteArticleFiles, deleteArticleFiles,
getArticleByCategory, getArticleByCategory,
getArticleById, getArticleById,
getArticleFiles,
submitApproval, submitApproval,
unPublishArticle, unPublishArticle,
updateArticle, updateArticle,
@ -196,27 +197,49 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
async function initState() { async function initState() {
loading(); loading();
const res = await getArticleById(id); try {
const data = res.data?.data; // 1⃣ Ambil ARTICLE
setDetailData(data); const articleRes = await getArticleById(id);
setValue("title", data?.title); const articleData = articleRes.data?.data;
setValue("customCreatorName", data?.customCreatorName);
setValue("slug", data?.slug);
setValue("source", data?.source);
const cleanDescription = data?.htmlDescription
? data.htmlDescription
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n", "\\")
.trim()
: "";
setValue("description", cleanDescription);
setValue("tags", data?.tags ? data.tags.split(",") : []);
setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId);
setDetailFiles(data?.files);
setupInitCategory(data?.categories); if (!articleData) return;
close();
// ===== ARTICLE DATA =====
setDetailData(articleData);
setValue("title", articleData.title);
setValue("customCreatorName", articleData.customCreatorName);
setValue("slug", articleData.slug);
setValue("source", articleData.source);
const cleanDescription = articleData.htmlDescription
? articleData.htmlDescription
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n")
.trim()
: "";
setValue("description", cleanDescription);
setValue("tags", articleData.tags ? articleData.tags.split(",") : []);
setThumbnail(articleData.thumbnailUrl);
setDiseId(articleData.aiArticleId);
setupInitCategory(articleData.categories);
// 2⃣ Ambil SEMUA article files
const filesRes = await getArticleFiles();
const allFiles = filesRes.data?.data ?? [];
// 3⃣ FILTER berdasarkan ARTICLE ID yang sedang dibuka
const filteredFiles = allFiles.filter(
(file: any) => file.articleId === articleData.id
);
setDetailFiles(filteredFiles);
} catch (error) {
console.error("Init state error:", error);
} finally {
close();
}
} }
const setupInitCategory = (data: any) => { const setupInitCategory = (data: any) => {
@ -667,9 +690,10 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
name="title" name="title"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<div className="w-full"> <div className="w-full">
<label htmlFor="title" className="block text-sm font-medium mb-1"> <label htmlFor="title" className="block text-xl font-medium mb-2">
Judul Judul
</label> </label>
<Input <Input
type="text" type="text"
id="title" id="title"
@ -677,7 +701,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
value={value ?? ""} value={value ?? ""}
readOnly={isDetail} readOnly={isDetail}
onChange={onChange} onChange={onChange}
className="w-full border rounded-lg" className="h-16 px-4 text-2xl leading-tight"
/> />
</div> </div>
)} )}

View File

@ -136,6 +136,13 @@ export async function uploadArticleFile(id: string, data: any) {
return await httpPostInterceptor(`/article-files/${id}`, data, headers); return await httpPostInterceptor(`/article-files/${id}`, data, headers);
} }
export async function getArticleFiles() {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/article-files`, headers);
}
export async function uploadArticleThumbnail(id: string, data: any) { export async function uploadArticleThumbnail(id: string, data: any) {
const headers = { const headers = {
"content-type": "multipart/form-data", "content-type": "multipart/form-data",