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() {
|
export default function Header() {
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
|
const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
|
||||||
const [userId, setUserId] = useState<string | null>(
|
|
||||||
getCookiesDecrypt("uie") || null
|
|
||||||
);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
@ -29,7 +26,7 @@ export default function Header() {
|
||||||
const response = await listArticles(
|
const response = await listArticles(
|
||||||
1,
|
1,
|
||||||
5,
|
5,
|
||||||
1, // ⬅️ tambahkan typeId = 1 (image only)
|
1, // hanya typeId = 1 (image)
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
"createdAt",
|
"createdAt",
|
||||||
|
|
@ -39,7 +36,6 @@ export default function Header() {
|
||||||
let articlesData: any[] = [];
|
let articlesData: any[] = [];
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
// Jika gagal, fallback tetap filter typeId = 1
|
|
||||||
const fallback = await listData(
|
const fallback = await listData(
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
|
|
@ -49,7 +45,7 @@ export default function Header() {
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"1" // ⬅️ tambahkan filter typeId = 1 di fallback juga
|
"1"
|
||||||
);
|
);
|
||||||
articlesData = fallback?.data?.data?.content || [];
|
articlesData = fallback?.data?.data?.content || [];
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -69,17 +65,20 @@ export default function Header() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [slug]);
|
}, [slug]);
|
||||||
|
|
||||||
|
// ✅ Sinkronisasi bookmark: dari localStorage + backend user login
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchBookmarks = async () => {
|
const syncBookmarks = async () => {
|
||||||
const currentUserId: any = getCookiesDecrypt("uie");
|
try {
|
||||||
setUserId(currentUserId);
|
const roleId = Number(getCookiesDecrypt("urie"));
|
||||||
|
let localSet = new Set<number>();
|
||||||
|
|
||||||
if (!currentUserId) {
|
const simpananLocal = localStorage.getItem("bookmarkedIds");
|
||||||
setBookmarkedIds(new Set());
|
if (simpananLocal) {
|
||||||
return;
|
localSet = new Set(JSON.parse(simpananLocal));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Jika user login, gabungkan dengan data dari backend
|
||||||
|
if (roleId && !isNaN(roleId)) {
|
||||||
const res = await getBookmarkSummaryForUser();
|
const res = await getBookmarkSummaryForUser();
|
||||||
const bookmarks =
|
const bookmarks =
|
||||||
res?.data?.data?.recentBookmarks ||
|
res?.data?.data?.recentBookmarks ||
|
||||||
|
|
@ -93,15 +92,23 @@ export default function Header() {
|
||||||
.filter((x) => !isNaN(x))
|
.filter((x) => !isNaN(x))
|
||||||
);
|
);
|
||||||
|
|
||||||
setBookmarkedIds(ids);
|
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) {
|
} catch (err) {
|
||||||
console.error("Gagal memuat bookmark user:", err);
|
console.error("Gagal sinkronisasi bookmark:", err);
|
||||||
setBookmarkedIds(new Set());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchBookmarks();
|
syncBookmarks();
|
||||||
}, [userId]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="max-w-[1350px] mx-auto px-4">
|
<section className="max-w-[1350px] mx-auto px-4">
|
||||||
|
|
@ -111,17 +118,8 @@ export default function Header() {
|
||||||
key={data[0].id}
|
key={data[0].id}
|
||||||
item={data[0]}
|
item={data[0]}
|
||||||
isBig
|
isBig
|
||||||
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
|
bookmarkedIds={bookmarkedIds}
|
||||||
onSaved={(id) =>
|
setBookmarkedIds={setBookmarkedIds}
|
||||||
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
|
|
||||||
}
|
|
||||||
onRemoved={(id) =>
|
|
||||||
setBookmarkedIds((prev) => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
next.delete(Number(id));
|
|
||||||
return next;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -130,17 +128,8 @@ export default function Header() {
|
||||||
<Card
|
<Card
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
|
bookmarkedIds={bookmarkedIds}
|
||||||
onSaved={(id) =>
|
setBookmarkedIds={setBookmarkedIds}
|
||||||
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
|
|
||||||
}
|
|
||||||
onRemoved={(id) =>
|
|
||||||
setBookmarkedIds((prev) => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
next.delete(Number(id));
|
|
||||||
return next;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -158,7 +147,7 @@ export default function Header() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Helper function untuk transform data
|
// 🔹 Helper function
|
||||||
function itemTransform(article: any) {
|
function itemTransform(article: any) {
|
||||||
return {
|
return {
|
||||||
id: article.id,
|
id: article.id,
|
||||||
|
|
@ -185,27 +174,23 @@ function itemTransform(article: any) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔹 Komponen Card
|
||||||
function Card({
|
function Card({
|
||||||
item,
|
item,
|
||||||
isBig = false,
|
isBig = false,
|
||||||
isInitiallyBookmarked = false,
|
bookmarkedIds,
|
||||||
onSaved,
|
setBookmarkedIds,
|
||||||
onRemoved,
|
|
||||||
}: {
|
}: {
|
||||||
item: any;
|
item: any;
|
||||||
isBig?: boolean;
|
isBig?: boolean;
|
||||||
isInitiallyBookmarked?: boolean;
|
bookmarkedIds: Set<number>;
|
||||||
onSaved?: (id: number) => void;
|
setBookmarkedIds: React.Dispatch<React.SetStateAction<Set<number>>>;
|
||||||
onRemoved?: (id: number) => void;
|
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const isBookmarked = bookmarkedIds.has(Number(item.id));
|
||||||
setIsBookmarked(isInitiallyBookmarked);
|
|
||||||
}, [isInitiallyBookmarked]);
|
|
||||||
|
|
||||||
const getLink = () => `/content/image/detail/${item?.id}`;
|
const getLink = () => `/content/image/detail/${item?.id}`;
|
||||||
|
|
||||||
|
|
@ -234,18 +219,27 @@ function Card({
|
||||||
confirmButtonColor: "#d33",
|
confirmButtonColor: "#d33",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const nowBookmarked = !isBookmarked;
|
const updated = new Set(bookmarkedIds);
|
||||||
setIsBookmarked(nowBookmarked);
|
let pesan = "";
|
||||||
if (nowBookmarked) onSaved?.(item.id);
|
|
||||||
else onRemoved?.(item.id);
|
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({
|
MySwal.fire({
|
||||||
icon: "success",
|
icon: "success",
|
||||||
title: nowBookmarked ? "Disimpan!" : "Dihapus!",
|
title: isBookmarked ? "Dihapus!" : "Disimpan!",
|
||||||
text: nowBookmarked
|
text: pesan,
|
||||||
? "Artikel berhasil disimpan ke bookmark."
|
|
||||||
: "Artikel dihapus dari bookmark.",
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
timer: 1500,
|
timer: 1500,
|
||||||
showConfirmButton: false,
|
showConfirmButton: false,
|
||||||
});
|
});
|
||||||
|
|
@ -264,19 +258,14 @@ function Card({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
|
||||||
<div
|
<div
|
||||||
className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white ${
|
className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white flex flex-col ${
|
||||||
isBig
|
isBig
|
||||||
? "w-full lg:max-w-[670px] lg:min-h-[680px]"
|
? "w-full lg:max-w-[670px] h-[680px]"
|
||||||
: "w-full h-[350px] md:h-[330px]"
|
: "w-full h-[360px] md:h-[340px]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div className={`relative ${isBig ? "h-[420px]" : "h-[180px]"} w-full`}>
|
||||||
className={`relative ${
|
|
||||||
isBig ? "aspect-[3/2] lg:h-[525px]" : "aspect-video"
|
|
||||||
} w-full`}
|
|
||||||
>
|
|
||||||
<Link href={getLink()}>
|
<Link href={getLink()}>
|
||||||
<Image
|
<Image
|
||||||
src={item.smallThumbnailLink || "/contributor.png"}
|
src={item.smallThumbnailLink || "/contributor.png"}
|
||||||
|
|
@ -287,7 +276,8 @@ function Card({
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</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">
|
<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">
|
<span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
|
||||||
{item.clientName}
|
{item.clientName}
|
||||||
|
|
@ -317,6 +307,7 @@ function Card({
|
||||||
{item.title}
|
{item.title}
|
||||||
</h3>
|
</h3>
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between items-center pt-4">
|
<div className="flex justify-between items-center pt-4">
|
||||||
<div className="flex gap-2 text-gray-500">
|
<div className="flex gap-2 text-gray-500">
|
||||||
|
|
@ -332,10 +323,10 @@ function Card({
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleToggleBookmark}
|
onClick={handleToggleBookmark}
|
||||||
disabled={isSaving}
|
disabled={isSaving || isBookmarked}
|
||||||
className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
|
className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
|
||||||
isBookmarked
|
isBookmarked
|
||||||
? "bg-gray-400 text-white hover:bg-gray-500"
|
? "bg-gray-400 text-white cursor-not-allowed"
|
||||||
: "bg-[#F60100] text-white hover:bg-[#c90000]"
|
: "bg-[#F60100] text-white hover:bg-[#c90000]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -344,7 +335,6 @@ function Card({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { getPublicClients, PublicClient } from "@/service/client/public-clients";
|
import {
|
||||||
|
getPublicClients,
|
||||||
|
PublicClient,
|
||||||
|
} from "@/service/client/public-clients";
|
||||||
|
|
||||||
type CategoryTabsProps = {
|
type CategoryTabsProps = {
|
||||||
selectedCategory: string;
|
selectedCategory: string;
|
||||||
|
|
@ -15,17 +18,21 @@ export default function CategoryTabs({
|
||||||
const [clients, setClients] = useState<PublicClient[]>([]);
|
const [clients, setClients] = useState<PublicClient[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
// Fetch public clients
|
// ✅ Fetch public clients dari API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchClients() {
|
async function fetchClients() {
|
||||||
try {
|
try {
|
||||||
const response = await getPublicClients();
|
const response = await getPublicClients();
|
||||||
if (response?.data?.success && response.data.data) {
|
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) {
|
} catch (error) {
|
||||||
console.error("Error fetching public clients:", error);
|
console.error("Error fetching public clients:", error);
|
||||||
// Fallback to empty array if API fails
|
// Fallback ke data statis jika API gagal
|
||||||
setClients([]);
|
setClients([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
@ -35,9 +42,22 @@ export default function CategoryTabs({
|
||||||
fetchClients();
|
fetchClients();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Create categories array with "SEMUA" first, then client names
|
// 🔹 Fallback jika API gagal
|
||||||
const categories = ["SEMUA", ...clients.map(client => client.name)];
|
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) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-2 overflow-x-auto">
|
<div className="flex flex-wrap gap-2 overflow-x-auto">
|
||||||
|
|
@ -51,6 +71,7 @@ export default function CategoryTabs({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔹 Render kategori client
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap gap-2 overflow-x-auto">
|
<div className="flex flex-wrap gap-2 overflow-x-auto">
|
||||||
{categories.map((cat, idx) => (
|
{categories.map((cat, idx) => (
|
||||||
|
|
@ -59,8 +80,8 @@ export default function CategoryTabs({
|
||||||
onClick={() => onCategoryChange(cat)}
|
onClick={() => onCategoryChange(cat)}
|
||||||
className={`px-4 py-1 text-sm rounded font-medium border transition-colors ${
|
className={`px-4 py-1 text-sm rounded font-medium border transition-colors ${
|
||||||
selectedCategory === cat
|
selectedCategory === cat
|
||||||
? "bg-[#C6A455] text-white"
|
? "bg-[#C6A455] text-white border-[#C6A455]"
|
||||||
: "bg-white text-gray-800 hover:bg-gray-50"
|
: "bg-white text-gray-800 hover:bg-gray-50 border-gray-300"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{cat.toUpperCase()}
|
{cat.toUpperCase()}
|
||||||
|
|
@ -69,3 +90,75 @@ export default function CategoryTabs({
|
||||||
</div>
|
</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