fix: button save and category in page for-you
This commit is contained in:
parent
ef4887cfc1
commit
28c1b79812
|
|
@ -14,9 +14,6 @@ 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);
|
||||
|
|
@ -29,7 +26,7 @@ export default function Header() {
|
|||
const response = await listArticles(
|
||||
1,
|
||||
5,
|
||||
1, // ⬅️ tambahkan typeId = 1 (image only)
|
||||
1, // hanya typeId = 1 (image)
|
||||
undefined,
|
||||
undefined,
|
||||
"createdAt",
|
||||
|
|
@ -39,7 +36,6 @@ export default function Header() {
|
|||
let articlesData: any[] = [];
|
||||
|
||||
if (response?.error) {
|
||||
// Jika gagal, fallback tetap filter typeId = 1
|
||||
const fallback = await listData(
|
||||
"",
|
||||
"",
|
||||
|
|
@ -49,7 +45,7 @@ export default function Header() {
|
|||
"createdAt",
|
||||
"",
|
||||
"",
|
||||
"1" // ⬅️ tambahkan filter typeId = 1 di fallback juga
|
||||
"1"
|
||||
);
|
||||
articlesData = fallback?.data?.data?.content || [];
|
||||
} else {
|
||||
|
|
@ -69,39 +65,50 @@ export default function Header() {
|
|||
fetchData();
|
||||
}, [slug]);
|
||||
|
||||
// ✅ Sinkronisasi bookmark: dari localStorage + backend user login
|
||||
useEffect(() => {
|
||||
const fetchBookmarks = async () => {
|
||||
const currentUserId: any = getCookiesDecrypt("uie");
|
||||
setUserId(currentUserId);
|
||||
|
||||
if (!currentUserId) {
|
||||
setBookmarkedIds(new Set());
|
||||
return;
|
||||
}
|
||||
|
||||
const syncBookmarks = async () => {
|
||||
try {
|
||||
const res = await getBookmarkSummaryForUser();
|
||||
const bookmarks =
|
||||
res?.data?.data?.recentBookmarks ||
|
||||
res?.data?.data?.bookmarks ||
|
||||
res?.data?.data ||
|
||||
[];
|
||||
const roleId = Number(getCookiesDecrypt("urie"));
|
||||
let localSet = new Set<number>();
|
||||
|
||||
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 simpananLocal = localStorage.getItem("bookmarkedIds");
|
||||
if (simpananLocal) {
|
||||
localSet = new Set(JSON.parse(simpananLocal));
|
||||
}
|
||||
|
||||
setBookmarkedIds(ids);
|
||||
// Jika user login, gabungkan dengan data dari backend
|
||||
if (roleId && !isNaN(roleId)) {
|
||||
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 gabungan = new Set([...localSet, ...ids]);
|
||||
setBookmarkedIds(gabungan);
|
||||
localStorage.setItem(
|
||||
"bookmarkedIds",
|
||||
JSON.stringify(Array.from(gabungan))
|
||||
);
|
||||
} else {
|
||||
// Jika belum login, pakai local saja
|
||||
setBookmarkedIds(localSet);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal memuat bookmark user:", err);
|
||||
setBookmarkedIds(new Set());
|
||||
console.error("Gagal sinkronisasi bookmark:", err);
|
||||
}
|
||||
};
|
||||
|
||||
fetchBookmarks();
|
||||
}, [userId]);
|
||||
syncBookmarks();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="max-w-[1350px] mx-auto px-4">
|
||||
|
|
@ -111,17 +118,8 @@ export default function Header() {
|
|||
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;
|
||||
})
|
||||
}
|
||||
bookmarkedIds={bookmarkedIds}
|
||||
setBookmarkedIds={setBookmarkedIds}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -130,17 +128,8 @@ export default function Header() {
|
|||
<Card
|
||||
key={item.id}
|
||||
item={item}
|
||||
isInitiallyBookmarked={bookmarkedIds.has(Number(item.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;
|
||||
})
|
||||
}
|
||||
bookmarkedIds={bookmarkedIds}
|
||||
setBookmarkedIds={setBookmarkedIds}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -158,7 +147,7 @@ export default function Header() {
|
|||
);
|
||||
}
|
||||
|
||||
// 🔹 Helper function untuk transform data
|
||||
// 🔹 Helper function
|
||||
function itemTransform(article: any) {
|
||||
return {
|
||||
id: article.id,
|
||||
|
|
@ -185,29 +174,25 @@ function itemTransform(article: any) {
|
|||
};
|
||||
}
|
||||
|
||||
// 🔹 Komponen Card
|
||||
function Card({
|
||||
item,
|
||||
isBig = false,
|
||||
isInitiallyBookmarked = false,
|
||||
onSaved,
|
||||
onRemoved,
|
||||
bookmarkedIds,
|
||||
setBookmarkedIds,
|
||||
}: {
|
||||
item: any;
|
||||
isBig?: boolean;
|
||||
isInitiallyBookmarked?: boolean;
|
||||
onSaved?: (id: number) => void;
|
||||
onRemoved?: (id: number) => void;
|
||||
bookmarkedIds: Set<number>;
|
||||
setBookmarkedIds: React.Dispatch<React.SetStateAction<Set<number>>>;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const MySwal = withReactContent(Swal);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
|
||||
|
||||
useEffect(() => {
|
||||
setIsBookmarked(isInitiallyBookmarked);
|
||||
}, [isInitiallyBookmarked]);
|
||||
const isBookmarked = bookmarkedIds.has(Number(item.id));
|
||||
|
||||
const getLink = () => `/content/image/detail/${item?.id}`;
|
||||
const getLink = () => `/content/image/detail/${item?.id}`;
|
||||
|
||||
const handleToggleBookmark = async () => {
|
||||
const roleId = Number(getCookiesDecrypt("urie"));
|
||||
|
|
@ -234,18 +219,27 @@ function Card({
|
|||
confirmButtonColor: "#d33",
|
||||
});
|
||||
} else {
|
||||
const nowBookmarked = !isBookmarked;
|
||||
setIsBookmarked(nowBookmarked);
|
||||
if (nowBookmarked) onSaved?.(item.id);
|
||||
else onRemoved?.(item.id);
|
||||
const updated = new Set(bookmarkedIds);
|
||||
let pesan = "";
|
||||
|
||||
if (isBookmarked) {
|
||||
updated.delete(Number(item.id));
|
||||
pesan = "Dihapus dari bookmark.";
|
||||
} else {
|
||||
updated.add(Number(item.id));
|
||||
pesan = "Artikel disimpan ke bookmark.";
|
||||
}
|
||||
|
||||
setBookmarkedIds(updated);
|
||||
localStorage.setItem(
|
||||
"bookmarkedIds",
|
||||
JSON.stringify(Array.from(updated))
|
||||
);
|
||||
|
||||
MySwal.fire({
|
||||
icon: "success",
|
||||
title: nowBookmarked ? "Disimpan!" : "Dihapus!",
|
||||
text: nowBookmarked
|
||||
? "Artikel berhasil disimpan ke bookmark."
|
||||
: "Artikel dihapus dari bookmark.",
|
||||
confirmButtonColor: "#3085d6",
|
||||
title: isBookmarked ? "Dihapus!" : "Disimpan!",
|
||||
text: pesan,
|
||||
timer: 1500,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
|
|
@ -264,30 +258,26 @@ function Card({
|
|||
};
|
||||
|
||||
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={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white flex flex-col ${
|
||||
isBig
|
||||
? "w-full lg:max-w-[670px] h-[680px]"
|
||||
: "w-full h-[360px] md:h-[340px]"
|
||||
}`}
|
||||
>
|
||||
<div className={`relative ${isBig ? "h-[420px]" : "h-[180px]"} w-full`}>
|
||||
<Link href={getLink()}>
|
||||
<Image
|
||||
src={item.smallThumbnailLink || "/contributor.png"}
|
||||
alt={item.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-2">
|
||||
<div className="p-4 flex flex-col justify-between flex-1">
|
||||
<div className="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}
|
||||
|
|
@ -317,31 +307,31 @@ function Card({
|
|||
{item.title}
|
||||
</h3>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center pt-4">
|
||||
<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={handleToggleBookmark}
|
||||
disabled={isSaving}
|
||||
className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
|
||||
isBookmarked
|
||||
? "bg-gray-400 text-white hover:bg-gray-500"
|
||||
: "bg-[#F60100] text-white hover:bg-[#c90000]"
|
||||
}`}
|
||||
>
|
||||
{isSaving ? "Menyimpan" : isBookmarked ? "Tersimpan" : "Simpan"}
|
||||
</button>
|
||||
<div className="flex justify-between items-center pt-4">
|
||||
<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={handleToggleBookmark}
|
||||
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 ? "Menyimpan" : isBookmarked ? "Tersimpan" : "Simpan"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { getPublicClients, PublicClient } from "@/service/client/public-clients";
|
||||
import {
|
||||
getPublicClients,
|
||||
PublicClient,
|
||||
} from "@/service/client/public-clients";
|
||||
|
||||
type CategoryTabsProps = {
|
||||
selectedCategory: string;
|
||||
|
|
@ -15,17 +18,21 @@ export default function CategoryTabs({
|
|||
const [clients, setClients] = useState<PublicClient[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Fetch public clients
|
||||
// ✅ Fetch public clients dari API
|
||||
useEffect(() => {
|
||||
async function fetchClients() {
|
||||
try {
|
||||
const response = await getPublicClients();
|
||||
if (response?.data?.success && response.data.data) {
|
||||
setClients(response.data.data);
|
||||
// 🔹 Filter hanya client aktif (boolean true)
|
||||
const activeClients = response.data.data.filter(
|
||||
(client: PublicClient) => client.isActive === true
|
||||
);
|
||||
setClients(activeClients);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching public clients:", error);
|
||||
// Fallback to empty array if API fails
|
||||
// Fallback ke data statis jika API gagal
|
||||
setClients([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
@ -35,9 +42,22 @@ export default function CategoryTabs({
|
|||
fetchClients();
|
||||
}, []);
|
||||
|
||||
// Create categories array with "SEMUA" first, then client names
|
||||
const categories = ["SEMUA", ...clients.map(client => client.name)];
|
||||
// 🔹 Fallback jika API gagal
|
||||
const fallbackClients = [
|
||||
{ id: 1, name: "DIV HUMAS POLRI" },
|
||||
{ id: 2, name: "POLDASUMUT" },
|
||||
{ id: 3, name: "POLDAMETROJAYA" },
|
||||
{ id: 4, name: "POLDARIYAD" },
|
||||
{ id: 5, name: "POLDABALI" },
|
||||
];
|
||||
|
||||
const displayClients =
|
||||
clients.length > 0 ? clients : (fallbackClients as any);
|
||||
|
||||
// 🔹 Susun kategori: “SEMUA” di awal
|
||||
const categories = ["SEMUA", ...displayClients.map((c: any) => c.name)];
|
||||
|
||||
// 🔹 Tampilkan skeleton saat loading
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2 overflow-x-auto">
|
||||
|
|
@ -51,6 +71,7 @@ export default function CategoryTabs({
|
|||
);
|
||||
}
|
||||
|
||||
// 🔹 Render kategori client
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2 overflow-x-auto">
|
||||
{categories.map((cat, idx) => (
|
||||
|
|
@ -59,8 +80,8 @@ export default function CategoryTabs({
|
|||
onClick={() => onCategoryChange(cat)}
|
||||
className={`px-4 py-1 text-sm rounded font-medium border transition-colors ${
|
||||
selectedCategory === cat
|
||||
? "bg-[#C6A455] text-white"
|
||||
: "bg-white text-gray-800 hover:bg-gray-50"
|
||||
? "bg-[#C6A455] text-white border-[#C6A455]"
|
||||
: "bg-white text-gray-800 hover:bg-gray-50 border-gray-300"
|
||||
}`}
|
||||
>
|
||||
{cat.toUpperCase()}
|
||||
|
|
@ -69,3 +90,75 @@ export default function CategoryTabs({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// "use client";
|
||||
|
||||
// import { useState, useEffect } from "react";
|
||||
// import { getPublicClients, PublicClient } from "@/service/client/public-clients";
|
||||
|
||||
// type CategoryTabsProps = {
|
||||
// selectedCategory: string;
|
||||
// onCategoryChange: (category: string) => void;
|
||||
// };
|
||||
|
||||
// export default function CategoryTabs({
|
||||
// selectedCategory,
|
||||
// onCategoryChange,
|
||||
// }: CategoryTabsProps) {
|
||||
// const [clients, setClients] = useState<PublicClient[]>([]);
|
||||
// const [loading, setLoading] = useState(true);
|
||||
|
||||
// // Fetch public clients
|
||||
// useEffect(() => {
|
||||
// async function fetchClients() {
|
||||
// try {
|
||||
// const response = await getPublicClients();
|
||||
// if (response?.data?.success && response.data.data) {
|
||||
// setClients(response.data.data);
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error("Error fetching public clients:", error);
|
||||
// // Fallback to empty array if API fails
|
||||
// setClients([]);
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// }
|
||||
|
||||
// fetchClients();
|
||||
// }, []);
|
||||
|
||||
// // Create categories array with "SEMUA" first, then client names
|
||||
// const categories = ["SEMUA", ...clients.map(client => client.name)];
|
||||
|
||||
// if (loading) {
|
||||
// return (
|
||||
// <div className="flex flex-wrap gap-2 overflow-x-auto">
|
||||
// {Array.from({ length: 6 }).map((_, idx) => (
|
||||
// <div
|
||||
// key={idx}
|
||||
// className="px-4 py-1 text-sm rounded font-medium border bg-gray-200 animate-pulse h-8 w-20"
|
||||
// ></div>
|
||||
// ))}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="flex flex-wrap gap-2 overflow-x-auto">
|
||||
// {categories.map((cat, idx) => (
|
||||
// <button
|
||||
// key={idx}
|
||||
// onClick={() => onCategoryChange(cat)}
|
||||
// className={`px-4 py-1 text-sm rounded font-medium border transition-colors ${
|
||||
// selectedCategory === cat
|
||||
// ? "bg-[#C6A455] text-white"
|
||||
// : "bg-white text-gray-800 hover:bg-gray-50"
|
||||
// }`}
|
||||
// >
|
||||
// {cat.toUpperCase()}
|
||||
// </button>
|
||||
// ))}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
|
|
|||
Loading…
Reference in New Issue