fix: button save in landing page

This commit is contained in:
Sabda Yagra 2025-10-13 09:57:44 +07:00
parent c003ef075e
commit 2e86d97aee
2 changed files with 520 additions and 107 deletions

View File

@ -78,12 +78,21 @@ const useTableColumns = () => {
return <span className="whitespace-nowrap">{formattedDate}</span>;
},
},
// {
// accessorKey: "creatorName",
// header: "Creator Group",
// cell: ({ row }) => (
// <span className="whitespace-nowrap">
// {row.getValue("creatorName") || row.getValue("createdByName")}
// </span>
// ),
// },
{
accessorKey: "creatorName",
header: "Creator Group",
cell: ({ row }) => (
<span className="whitespace-nowrap">
{row.getValue("creatorName") || row.getValue("createdByName")}
{row.original.creatorName || row.original.createdByName || "-"}
</span>
),
},
@ -130,33 +139,90 @@ const useTableColumns = () => {
},
},
// {
// accessorKey: "statusName",
// header: "Status",
// cell: ({ row }) => {
// const statusColors: Record<string, string> = {
// 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 (
// <Badge
// className={cn(
// "rounded-full px-5 w-full whitespace-nowrap",
// 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}{" "}
// </Badge>
// );
// },
// },
{
accessorKey: "statusName",
header: "Status",
cell: ({ row }) => {
const statusColors: Record<string, string> = {
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<string, string> = {
"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 (
<Badge
@ -165,18 +231,7 @@ const useTableColumns = () => {
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}
</Badge>
);
},

View File

@ -14,17 +14,16 @@ import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content";
export default function Header() {
const [data, setData] = useState<any[]>([]);
const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
const [userId, setUserId] = useState<string | null>(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,16 +78,26 @@ export default function Header() {
}));
setData(transformed);
} catch (error) {
console.error("Gagal memuat data:", error);
}
};
const roleId = Number(getCookiesDecrypt("urie"));
if (roleId && !isNaN(roleId)) {
const saved = localStorage.getItem("bookmarkedIds");
let localSet = new Set<number>();
if (saved) {
localSet = new Set(JSON.parse(saved));
setBookmarkedIds(localSet);
fetchData();
}, [slug]);
// ✅ Ambil data bookmark dari backend (bukan localStorage)
useEffect(() => {
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 ||
@ -104,42 +111,35 @@ export default function Header() {
.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);
setBookmarkedIds(ids);
} catch (err) {
console.error("Gagal memuat bookmark user:", err);
setBookmarkedIds(new Set());
}
};
fetchData();
}, []);
// Simpan setiap kali state berubah
useEffect(() => {
if (bookmarkedIds.size > 0) {
localStorage.setItem(
"bookmarkedIds",
JSON.stringify(Array.from(bookmarkedIds))
);
}
}, [bookmarkedIds]);
fetchBookmarks();
}, [userId]); // ← otomatis re-fetch kalau cookie user berubah
return (
<section className="max-w-[1350px] mx-auto px-4">
<div className="flex flex-col lg:flex-row gap-6 py-6">
{data.length > 0 && (
<Card
key={data[0].id}
item={data[0]}
isBig
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
onSaved={(id) =>
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;
})
}
/>
))}
</div>
@ -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<number>(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({
<div className="flex justify-between items-center pt-2">
<div className="flex gap-2 text-gray-500">
<ThumbsUp
size={18}
className="cursor-pointer hover:text-[#F60100]"
/>
<ThumbsDown
size={18}
className="cursor-pointer hover:text-red-600"
/>
<ThumbsUp size={18} className="cursor-pointer hover:text-[#F60100]" />
<ThumbsDown size={18} className="cursor-pointer hover:text-red-600" />
</div>
<button
onClick={handleSave}
disabled={isSaving || isBookmarked}
onClick={handleToggleBookmark}
disabled={isSaving}
className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
isBookmarked
? "bg-gray-400 text-white cursor-not-allowed"
? "bg-gray-400 text-white hover:bg-gray-500"
: "bg-[#F60100] text-white hover:bg-[#c90000]"
}`}
>
{isSaving ? "Saving..." : isBookmarked ? "Saved" : "Save"}
{isSaving
? "Saving..."
: isBookmarked
? "Saved"
: "Save"}
</button>
</div>
</div>
@ -348,3 +351,358 @@ function Card({
</div>
);
}
// "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<any[]>([]);
// const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(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<number>();
// 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<number>(
// (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 (
// <section className="max-w-[1350px] mx-auto px-4">
// <div className="flex flex-col lg:flex-row gap-6 py-6">
// {data.length > 0 && (
// <Card
// item={data[0]}
// isBig
// isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
// onSaved={(id) =>
// setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
// }
// />
// )}
// <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
// {data.slice(1, 5).map((item) => (
// <Card
// key={item.id}
// item={item}
// isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
// onSaved={(id) =>
// setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
// }
// />
// ))}
// </div>
// </div>
// <div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px] mt-4 rounded-xl">
// <Image
// src={"/PPS.png"}
// alt={"pps"}
// fill
// className="object-cover rounded-xl"
// />
// </div>
// </section>
// );
// }
// 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<number>(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 (
// <div>
// <div
// className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white ${
// isBig
// ? "w-full lg:max-w-[670px] lg:min-h-[680px]"
// : "w-full h-[350px] md:h-[330px]"
// }`}
// >
// <div
// className={`relative ${
// isBig ? "aspect-[3/2] lg:h-[525px]" : "aspect-video"
// } w-full`}
// >
// <Link href={getLink()}>
// <Image
// src={item.smallThumbnailLink || "/contributor.png"}
// alt={item.title}
// fill
// className="object-cover"
// />
// </Link>
// </div>
// <div className="p-4 space-y-2">
// <div className="flex items-center gap-2 text-xs font-semibold flex-wrap">
// <span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
// {item.clientName}
// </span>
// <span className="text-orange-600">
// {item.categories?.map((cat: any) => cat.title).join(", ")}
// </span>
// </div>
// <div className="text-xs text-gray-500">
// {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
// </div>
// <Link href={getLink()}>
// <h3 className="text-sm font-semibold leading-snug line-clamp-2">
// {item.title}
// </h3>
// </Link>
// <div className="flex justify-between items-center pt-2">
// <div className="flex gap-2 text-gray-500">
// <ThumbsUp
// size={18}
// className="cursor-pointer hover:text-[#F60100]"
// />
// <ThumbsDown
// size={18}
// className="cursor-pointer hover:text-red-600"
// />
// </div>
// <button
// onClick={handleSave}
// disabled={isSaving || isBookmarked}
// className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
// isBookmarked
// ? "bg-gray-400 text-white cursor-not-allowed"
// : "bg-[#F60100] text-white hover:bg-[#c90000]"
// }`}
// >
// {isSaving ? "Saving..." : isBookmarked ? "Saved" : "Save"}
// </button>
// </div>
// </div>
// </div>
// </div>
// );
// }