From 2e86d97aee13310179da2367cab2d40e33f56781 Mon Sep 17 00:00:00 2001 From: Sabda Yagra Date: Mon, 13 Oct 2025 09:57:44 +0700 Subject: [PATCH] fix: button save in landing page --- .../content/video/components/columns.tsx | 123 +++-- components/landing-page/header.tsx | 504 +++++++++++++++--- 2 files changed, 520 insertions(+), 107 deletions(-) diff --git a/app/[locale]/(admin)/admin/content/video/components/columns.tsx b/app/[locale]/(admin)/admin/content/video/components/columns.tsx index 9a419eb..2ac4292 100644 --- a/app/[locale]/(admin)/admin/content/video/components/columns.tsx +++ b/app/[locale]/(admin)/admin/content/video/components/columns.tsx @@ -78,12 +78,21 @@ const useTableColumns = () => { return {formattedDate}; }, }, + // { + // accessorKey: "creatorName", + // header: "Creator Group", + // cell: ({ row }) => ( + // + // {row.getValue("creatorName") || row.getValue("createdByName")} + // + // ), + // }, { accessorKey: "creatorName", header: "Creator Group", cell: ({ row }) => ( - {row.getValue("creatorName") || row.getValue("createdByName")} + {row.original.creatorName || row.original.createdByName || "-"} ), }, @@ -130,33 +139,90 @@ const useTableColumns = () => { }, }, + // { + // accessorKey: "statusName", + // header: "Status", + // cell: ({ row }) => { + // const statusColors: Record = { + // diterima: "bg-green-100 text-green-600", + // "menunggu review": "bg-orange-100 text-orange-600", + // }; + + // const colors = [ + // "bg-orange-100 text-orange-600", + // "bg-orange-100 text-orange-600", + // "bg-green-100 text-green-600", + // "bg-blue-100 text-blue-600", + // "bg-red-200 text-red-600", + // ]; + + // const status = + // Number(row.original?.statusId) == 2 && + // row.original?.reviewedAtLevel !== null && + // !row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) && + // Number(row.original?.creatorGroupLevelId) != Number(userLevelId) + // ? "1" + // : row.original?.statusId; + // const statusStyles = + // colors[Number(status)] || "bg-red-200 text-red-600"; + // // const statusStyles = statusColors[status] || "bg-red-200 text-red-600"; + + // return ( + // + // {(Number(row.original?.statusId) == 2 && + // !row.original?.reviewedAtLevel !== null && + // !row.original?.reviewedAtLevel?.includes( + // `:${Number(userLevelId)}:` + // ) && + // Number(row.original?.creatorGroupLevelId) != + // Number(userLevelId)) || + // (Number(row.original?.statusId) == 1 && + // Number(row.original?.needApprovalFromLevel) == + // Number(userLevelId)) + // ? "Menunggu Review" + // : row.original?.statusName}{" "} + // + // ); + // }, + // }, { accessorKey: "statusName", header: "Status", cell: ({ row }) => { - const statusColors: Record = { - diterima: "bg-green-100 text-green-600", - "menunggu review": "bg-orange-100 text-orange-600", + const statusId = Number(row.original?.statusId); + const reviewedAtLevel = row.original?.reviewedAtLevel || ""; + const creatorGroupLevelId = Number(row.original?.creatorGroupLevelId); + const needApprovalFromLevel = Number( + row.original?.needApprovalFromLevel + ); + + const userHasReviewed = reviewedAtLevel.includes(`:${userLevelId}:`); + const isCreator = creatorGroupLevelId === Number(userLevelId); + + const isWaitingForReview = + statusId === 2 && !userHasReviewed && !isCreator; + const isApprovalNeeded = + statusId === 1 && needApprovalFromLevel === Number(userLevelId); + + const label = + isWaitingForReview || isApprovalNeeded + ? "Menunggu Review" + : statusId === 2 + ? "Diterima" + : row.original?.statusName; + + const colors: Record = { + "Menunggu Review": "bg-orange-100 text-orange-600", + Diterima: "bg-green-100 text-green-600", + default: "bg-red-200 text-red-600", }; - const colors = [ - "bg-orange-100 text-orange-600", - "bg-orange-100 text-orange-600", - "bg-green-100 text-green-600", - "bg-blue-100 text-blue-600", - "bg-red-200 text-red-600", - ]; - - const status = - Number(row.original?.statusId) == 2 && - row.original?.reviewedAtLevel !== null && - !row.original?.reviewedAtLevel?.includes(`:${userLevelId}:`) && - Number(row.original?.creatorGroupLevelId) != Number(userLevelId) - ? "1" - : row.original?.statusId; - const statusStyles = - colors[Number(status)] || "bg-red-200 text-red-600"; - // const statusStyles = statusColors[status] || "bg-red-200 text-red-600"; + const statusStyles = colors[label] || colors.default; return ( { statusStyles )} > - {(Number(row.original?.statusId) == 2 && - !row.original?.reviewedAtLevel !== null && - !row.original?.reviewedAtLevel?.includes( - `:${Number(userLevelId)}:` - ) && - Number(row.original?.creatorGroupLevelId) != - Number(userLevelId)) || - (Number(row.original?.statusId) == 1 && - Number(row.original?.needApprovalFromLevel) == - Number(userLevelId)) - ? "Menunggu Review" - : row.original?.statusName}{" "} + {label} ); }, diff --git a/components/landing-page/header.tsx b/components/landing-page/header.tsx index 9d20264..10e75e9 100644 --- a/components/landing-page/header.tsx +++ b/components/landing-page/header.tsx @@ -14,17 +14,16 @@ import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; export default function Header() { const [data, setData] = useState([]); const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); + const [userId, setUserId] = useState(getCookiesDecrypt("uie") || null); const router = useRouter(); const params = useParams(); const MySwal = withReactContent(Swal); - - // Get slug from URL params const slug = params?.slug as string; + // ✅ Ambil data artikel useEffect(() => { const fetchData = async () => { try { - // 🔹 Ambil artikel const response = await listArticles( 1, 5, @@ -38,8 +37,7 @@ export default function Header() { let articlesData: any[] = []; if (response?.error) { - // fallback ke API lama - const fallbackResponse = await listData( + const fallback = await listData( "", "", "", @@ -50,12 +48,11 @@ export default function Header() { "", "" ); - articlesData = fallbackResponse?.data?.data?.content || []; + articlesData = fallback?.data?.data?.content || []; } else { articlesData = response?.data?.data || []; } - // 🔹 Transform agar seragam const transformed = articlesData.map((article: any) => ({ id: article.id, title: article.title, @@ -81,65 +78,68 @@ export default function Header() { })); setData(transformed); - - const roleId = Number(getCookiesDecrypt("urie")); - if (roleId && !isNaN(roleId)) { - const saved = localStorage.getItem("bookmarkedIds"); - let localSet = new Set(); - if (saved) { - localSet = new Set(JSON.parse(saved)); - setBookmarkedIds(localSet); - } - - const res = await getBookmarkSummaryForUser(); - const bookmarks = - res?.data?.data?.recentBookmarks || - res?.data?.data?.bookmarks || - res?.data?.data || - []; - - const ids = new Set( - (Array.isArray(bookmarks) ? bookmarks : []) - .map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id)) - .filter((x) => !isNaN(x)) - ); - - const merged = new Set([...localSet, ...ids]); - setBookmarkedIds(merged); - localStorage.setItem( - "bookmarkedIds", - JSON.stringify(Array.from(merged)) - ); - } } catch (error) { console.error("Gagal memuat data:", error); } }; fetchData(); - }, []); + }, [slug]); - // Simpan setiap kali state berubah + // ✅ Ambil data bookmark dari backend (bukan localStorage) useEffect(() => { - if (bookmarkedIds.size > 0) { - localStorage.setItem( - "bookmarkedIds", - JSON.stringify(Array.from(bookmarkedIds)) - ); - } - }, [bookmarkedIds]); + const fetchBookmarks = async () => { + const currentUserId: any = getCookiesDecrypt("uie"); + setUserId(currentUserId); + + if (!currentUserId) { + setBookmarkedIds(new Set()); // logout: reset + return; + } + + try { + const res = await getBookmarkSummaryForUser(); + const bookmarks = + res?.data?.data?.recentBookmarks || + res?.data?.data?.bookmarks || + res?.data?.data || + []; + + const ids = new Set( + (Array.isArray(bookmarks) ? bookmarks : []) + .map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id)) + .filter((x) => !isNaN(x)) + ); + + setBookmarkedIds(ids); + } catch (err) { + console.error("Gagal memuat bookmark user:", err); + setBookmarkedIds(new Set()); + } + }; + + fetchBookmarks(); + }, [userId]); // ← otomatis re-fetch kalau cookie user berubah return (
{data.length > 0 && ( setBookmarkedIds((prev) => new Set([...prev, Number(id)])) } + onRemoved={(id) => + setBookmarkedIds((prev) => { + const next = new Set(prev); + next.delete(Number(id)); + return next; + }) + } /> )} @@ -152,6 +152,13 @@ export default function Header() { onSaved={(id) => setBookmarkedIds((prev) => new Set([...prev, Number(id)])) } + onRemoved={(id) => + setBookmarkedIds((prev) => { + const next = new Set(prev); + next.delete(Number(id)); + return next; + }) + } /> ))}
@@ -174,11 +181,13 @@ function Card({ isBig = false, isInitiallyBookmarked = false, onSaved, + onRemoved, }: { item: any; isBig?: boolean; isInitiallyBookmarked?: boolean; onSaved?: (id: number) => void; + onRemoved?: (id: number) => void; }) { const router = useRouter(); const MySwal = withReactContent(Swal); @@ -204,9 +213,8 @@ function Card({ } }; - const handleSave = async () => { + const handleToggleBookmark = async () => { const roleId = Number(getCookiesDecrypt("urie")); - if (!roleId || isNaN(roleId)) { MySwal.fire({ icon: "warning", @@ -226,26 +234,23 @@ function Card({ MySwal.fire({ icon: "error", title: "Gagal", - text: "Gagal menyimpan artikel.", + text: "Gagal memperbarui bookmark.", confirmButtonColor: "#d33", }); } else { - setIsBookmarked(true); - onSaved?.(item.id); + // ✅ Toggle berhasil di server + const nowBookmarked = !isBookmarked; + setIsBookmarked(nowBookmarked); - // 🔹 Simpan ke localStorage - const saved = localStorage.getItem("bookmarkedIds"); - const newSet = new Set(saved ? JSON.parse(saved) : []); - newSet.add(Number(item.id)); - localStorage.setItem( - "bookmarkedIds", - JSON.stringify(Array.from(newSet)) - ); + if (nowBookmarked) onSaved?.(item.id); + else onRemoved?.(item.id); MySwal.fire({ icon: "success", - title: "Berhasil", - text: "Artikel berhasil disimpan ke bookmark.", + title: nowBookmarked ? "Disimpan!" : "Dihapus!", + text: nowBookmarked + ? "Artikel berhasil disimpan ke bookmark." + : "Artikel dihapus dari bookmark.", confirmButtonColor: "#3085d6", timer: 1500, showConfirmButton: false, @@ -321,26 +326,24 @@ function Card({
- - + +
@@ -348,3 +351,358 @@ function Card({ ); } + +// "use client"; + +// import Image from "next/image"; +// import Link from "next/link"; +// import { ThumbsUp, ThumbsDown } from "lucide-react"; +// import { useEffect, useState } from "react"; +// import { useParams, useRouter } from "next/navigation"; +// import Swal from "sweetalert2"; +// import withReactContent from "sweetalert2-react-content"; +// import { listData, listArticles } from "@/service/landing/landing"; +// import { getCookiesDecrypt } from "@/lib/utils"; +// import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; + +// export default function Header() { +// const [data, setData] = useState([]); +// const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); +// const router = useRouter(); +// const params = useParams(); +// const MySwal = withReactContent(Swal); + +// // Get slug from URL params +// const slug = params?.slug as string; + +// useEffect(() => { +// const fetchData = async () => { +// try { +// // 🔹 Ambil artikel +// const response = await listArticles( +// 1, +// 5, +// undefined, +// undefined, +// undefined, +// "createdAt", +// slug +// ); + +// let articlesData: any[] = []; + +// if (response?.error) { +// // fallback ke API lama +// const fallbackResponse = await listData( +// "", +// "", +// "", +// 5, +// 0, +// "createdAt", +// "", +// "", +// "" +// ); +// articlesData = fallbackResponse?.data?.data?.content || []; +// } else { +// articlesData = response?.data?.data || []; +// } + +// // 🔹 Transform agar seragam +// const transformed = articlesData.map((article: any) => ({ +// id: article.id, +// title: article.title, +// categoryName: +// article.categoryName || +// (article.categories && article.categories[0]?.title) || +// "", +// createdAt: article.createdAt, +// smallThumbnailLink: article.thumbnailUrl, +// fileTypeId: article.typeId, +// clientName: article.clientName, +// categories: article.categories, +// label: +// article.typeId === 1 +// ? "Image" +// : article.typeId === 2 +// ? "Video" +// : article.typeId === 3 +// ? "Text" +// : article.typeId === 4 +// ? "Audio" +// : "", +// })); + +// setData(transformed); + +// const roleId = Number(getCookiesDecrypt("urie")); +// if (roleId && !isNaN(roleId)) { +// // const saved = localStorage.getItem("bookmarkedIds"); +// const userId = getCookiesDecrypt("uie"); +// const localKey = `bookmarkedIds_${userId || "guest"}`; +// const saved = localStorage.getItem(localKey); + +// let localSet = new Set(); +// if (saved) { +// localSet = new Set(JSON.parse(saved)); +// setBookmarkedIds(localSet); +// } + +// const res = await getBookmarkSummaryForUser(); +// const bookmarks = +// res?.data?.data?.recentBookmarks || +// res?.data?.data?.bookmarks || +// res?.data?.data || +// []; + +// const ids = new Set( +// (Array.isArray(bookmarks) ? bookmarks : []) +// .map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id)) +// .filter((x) => !isNaN(x)) +// ); + +// const merged = new Set([...localSet, ...ids]); +// setBookmarkedIds(merged); +// localStorage.setItem( +// "bookmarkedIds", +// JSON.stringify(Array.from(merged)) +// ); +// } +// } catch (error) { +// console.error("Gagal memuat data:", error); +// } +// }; + +// fetchData(); +// }, []); + +// // Simpan setiap kali state berubah +// useEffect(() => { +// if (bookmarkedIds.size > 0) { +// localStorage.setItem( +// "bookmarkedIds", +// JSON.stringify(Array.from(bookmarkedIds)) +// ); +// } +// }, [bookmarkedIds]); + +// return ( +//
+//
+// {data.length > 0 && ( +// +// setBookmarkedIds((prev) => new Set([...prev, Number(id)])) +// } +// /> +// )} + +//
+// {data.slice(1, 5).map((item) => ( +// +// setBookmarkedIds((prev) => new Set([...prev, Number(id)])) +// } +// /> +// ))} +//
+//
+ +//
+// {"pps"} +//
+//
+// ); +// } + +// function Card({ +// item, +// isBig = false, +// isInitiallyBookmarked = false, +// onSaved, +// }: { +// item: any; +// isBig?: boolean; +// isInitiallyBookmarked?: boolean; +// onSaved?: (id: number) => void; +// }) { +// const router = useRouter(); +// const MySwal = withReactContent(Swal); +// const [isSaving, setIsSaving] = useState(false); +// const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked); + +// useEffect(() => { +// setIsBookmarked(isInitiallyBookmarked); +// }, [isInitiallyBookmarked]); + +// const getLink = () => { +// switch (item?.fileTypeId) { +// case 1: +// return `/content/image/detail/${item?.id}`; +// case 2: +// return `/content/video/detail/${item?.id}`; +// case 3: +// return `/content/text/detail/${item?.id}`; +// case 4: +// return `/content/audio/detail/${item?.id}`; +// default: +// return "#"; +// } +// }; + +// const handleSave = async () => { +// const roleId = Number(getCookiesDecrypt("urie")); + +// if (!roleId || isNaN(roleId)) { +// MySwal.fire({ +// icon: "warning", +// title: "Login diperlukan", +// text: "Silakan login terlebih dahulu untuk menyimpan artikel.", +// confirmButtonText: "Login Sekarang", +// confirmButtonColor: "#d33", +// }).then(() => router.push("/auth")); +// return; +// } + +// try { +// setIsSaving(true); +// const res = await toggleBookmark(item.id); + +// if (res?.error) { +// MySwal.fire({ +// icon: "error", +// title: "Gagal", +// text: "Gagal menyimpan artikel.", +// confirmButtonColor: "#d33", +// }); +// } else { +// setIsBookmarked(true); +// onSaved?.(item.id); + +// // 🔹 Simpan ke localStorage +// const saved = localStorage.getItem("bookmarkedIds"); +// const newSet = new Set(saved ? JSON.parse(saved) : []); +// newSet.add(Number(item.id)); +// localStorage.setItem( +// "bookmarkedIds", +// JSON.stringify(Array.from(newSet)) +// ); + +// MySwal.fire({ +// icon: "success", +// title: "Berhasil", +// text: "Artikel berhasil disimpan ke bookmark.", +// confirmButtonColor: "#3085d6", +// timer: 1500, +// showConfirmButton: false, +// }); +// } +// } catch (err) { +// console.error("Toggle bookmark error:", err); +// MySwal.fire({ +// icon: "error", +// title: "Kesalahan", +// text: "Terjadi kesalahan saat menyimpan artikel.", +// confirmButtonColor: "#d33", +// }); +// } finally { +// setIsSaving(false); +// } +// }; + +// return ( +//
+//
+//
+// +// {item.title} +// +//
+ +//
+//
+// +// {item.clientName} +// +// +// {item.categories?.map((cat: any) => cat.title).join(", ")} +// +//
+ +//
+// {new Date(item.createdAt) +// .toLocaleString("id-ID", { +// day: "2-digit", +// month: "short", +// year: "numeric", +// hour: "2-digit", +// minute: "2-digit", +// hour12: false, +// timeZone: "Asia/Jakarta", +// }) +// .replace(".", ":")}{" "} +// WIB +//
+ +// +//

+// {item.title} +//

+// + +//
+//
+// +// +//
+ +// +//
+//
+//
+//
+// ); +// }