diff --git a/app/[locale]/(public)/tenant/[slug]/content/audio/detail/[id]/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/audio/detail/[id]/page.tsx new file mode 100644 index 0000000..542f952 --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/audio/detail/[id]/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { useParams } from "next/navigation"; +import AudioDetail from "@/components/main/content/audio-detail"; + +export default function DetailAudioInfo() { + const params = useParams(); + const id = params?.id as string; + + return ; +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/audio/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/audio/page.tsx new file mode 100644 index 0000000..0bd296c --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/audio/page.tsx @@ -0,0 +1,504 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Image from "next/image"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { ThumbsUp, ThumbsDown, Search, Filter, Calendar, Tag } from "lucide-react"; +import Link from "next/link"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { listArticles } from "@/service/landing/landing"; +import { getArticleCategories } from "@/service/categories/article-categories"; +import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; +import { getCookiesDecrypt } from "@/lib/utils"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; + +// Format tanggal +function formatTanggal(dateString: string) { + if (!dateString) return ""; + return ( + new Date(dateString) + .toLocaleString("id-ID", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Asia/Jakarta", + }) + .replace(/\./g, ":") + " WIB" + ); +} + +// Function to get link based on typeId +function getLink(item: any) { + switch (item?.typeId) { + 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 "#"; + } +} + +export default function ArticleListPage() { + const [articles, setArticles] = useState([]); + const [categories, setCategories] = useState([]); + const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedCategory, setSelectedCategory] = useState("all"); + const [selectedType, setSelectedType] = useState("4"); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalData, setTotalData] = useState(0); + const params = useParams(); + const slug = params?.slug as string; + + const router = useRouter(); + const searchParams = useSearchParams(); + const MySwal = withReactContent(Swal); + + const itemsPerPage = 6; + + // Load categories on component mount + useEffect(() => { + loadCategories(); + }, []); + + // Load articles when filters change + useEffect(() => { + loadArticles(); + }, [currentPage, selectedCategory, selectedType, searchTerm, startDate, endDate]); + + // Sync bookmarks + useEffect(() => { + syncBookmarks(); + }, []); + + async function loadCategories() { + try { + const response = await getArticleCategories(); + if (response?.data?.data) { + setCategories(response.data.data); + } + } catch (error) { + console.error("Error loading categories:", error); + } + } + + async function loadArticles() { + try { + setLoading(true); + + const categoryId = selectedCategory === "all" ? undefined : selectedCategory; + const typeId = selectedType === "all" ? undefined : parseInt(selectedType); + + const response = await listArticles( + currentPage, + itemsPerPage, + typeId, + searchTerm || undefined, + categoryId, + "createdAt", + slug + ); + + if (response?.data?.data) { + const articlesData = response.data.data; + setArticles(articlesData); + setTotalPages(response.data.meta.totalPage || 1); + setTotalData(response.data.meta.count || 0); + } + } catch (error) { + console.error("Error loading articles:", error); + } finally { + setLoading(false); + } + } + + async function syncBookmarks() { + const roleId = Number(getCookiesDecrypt("urie")); + if (roleId && !isNaN(roleId)) { + try { + const savedLocal = localStorage.getItem("bookmarkedIds"); + let localSet = new Set(); + if (savedLocal) { + localSet = new Set(JSON.parse(savedLocal)); + 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("Error syncing bookmarks:", error); + } + } + } + + const handleSave = async (id: number) => { + 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", + }); + return; + } + + try { + const res = await toggleBookmark(id); + if (res?.error) { + MySwal.fire({ + icon: "error", + title: "Gagal", + text: "Gagal menyimpan artikel.", + confirmButtonColor: "#d33", + }); + } else { + const updated = new Set(bookmarkedIds); + updated.add(Number(id)); + setBookmarkedIds(updated); + localStorage.setItem( + "bookmarkedIds", + JSON.stringify(Array.from(updated)) + ); + + MySwal.fire({ + icon: "success", + title: "Berhasil", + text: "Artikel berhasil disimpan ke bookmark.", + timer: 1500, + showConfirmButton: false, + }); + } + } catch (err) { + console.error("Error saving bookmark:", err); + MySwal.fire({ + icon: "error", + title: "Kesalahan", + text: "Terjadi kesalahan saat menyimpan artikel.", + }); + } + }; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setCurrentPage(1); + loadArticles(); + }; + + const handleFilterChange = () => { + setCurrentPage(1); + loadArticles(); + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + const getTypeLabel = (typeId: number) => { + switch (typeId) { + case 1: return "📸 Image"; + case 2: return "🎬 Video"; + case 3: return "📝 Text"; + case 4: return "🎵 Audio"; + default: return "📄 Content"; + } + }; + + return ( +
+
+
+ {/* Filter Sidebar */} +
+ +
+
+ +

Filter Artikel

+
+ + {/* Search */} +
+ +
+ setSearchTerm(e.target.value)} + className="flex-1" + /> + +
+
+ + {/* Category Filter */} +
+ + +
+ + {/* Type Filter */} +
+ + +
+ + {/* Date Filter */} +
+ +
+ { + setStartDate(e.target.value); + handleFilterChange(); + }} + /> + { + setEndDate(e.target.value); + handleFilterChange(); + }} + /> +
+
+ + {/* Clear Filters */} + +
+
+
+ + {/* Main Content */} +
+
+

Daftar Artikel

+

+ Menampilkan {totalData} artikel + {searchTerm && ` untuk "${searchTerm}"`} + {selectedCategory !== "all" && ` dalam kategori "${categories.find(c => c.id === parseInt(selectedCategory))?.title || selectedCategory}"`} +

+
+ + {/* Articles Grid */} + {loading ? ( +
+ {[...Array(6)].map((_, i) => ( + +
+
+
+
+
+
+
+ ))} +
+ ) : articles.length > 0 ? ( +
+ {articles.map((article) => ( + +
+ + {article.title + +
+
+
+ + {article.clientName} + + + {article.categoryName || "Tanpa Kategori"} + +
+

+ {formatTanggal(article.createdAt)} +

+ +

+ {article.title} +

+ + {/*

+ {article.description || article.content || ""} +

*/} + +
+
+ + +
+ +
+
+
+ ))} +
+ ) : ( + +
+

Tidak ada artikel ditemukan

+

Coba ubah filter atau kata kunci pencarian

+
+
+ )} + + {/* Pagination */} + {totalPages > 1 && ( +
+ + + + handlePageChange(currentPage - 1)} + className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + const pageNumber = Math.max(1, Math.min(totalPages - 4, currentPage - 2)) + i; + if (pageNumber > totalPages) return null; + + return ( + + handlePageChange(pageNumber)} + isActive={currentPage === pageNumber} + className="cursor-pointer" + > + {pageNumber} + + + ); + })} + + + handlePageChange(currentPage + 1)} + className={currentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + +
+ )} +
+
+
+
+ ); +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/image/detail/[id]/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/image/detail/[id]/page.tsx new file mode 100644 index 0000000..074790a --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/image/detail/[id]/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { useParams } from "next/navigation"; +import ImageDetail from "@/components/main/content/image-detail"; + +export default function DetailImageInfo() { + const params = useParams(); + const id = params?.id as string; + + return ; +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/image/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/image/page.tsx new file mode 100644 index 0000000..728697d --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/image/page.tsx @@ -0,0 +1,504 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Image from "next/image"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { ThumbsUp, ThumbsDown, Search, Filter, Calendar, Tag } from "lucide-react"; +import Link from "next/link"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { listArticles } from "@/service/landing/landing"; +import { getArticleCategories } from "@/service/categories/article-categories"; +import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; +import { getCookiesDecrypt } from "@/lib/utils"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; + +// Format tanggal +function formatTanggal(dateString: string) { + if (!dateString) return ""; + return ( + new Date(dateString) + .toLocaleString("id-ID", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Asia/Jakarta", + }) + .replace(/\./g, ":") + " WIB" + ); +} + +// Function to get link based on typeId +function getLink(item: any) { + switch (item?.typeId) { + 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 "#"; + } +} + +export default function ArticleListPage() { + const [articles, setArticles] = useState([]); + const [categories, setCategories] = useState([]); + const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedCategory, setSelectedCategory] = useState("all"); + const [selectedType, setSelectedType] = useState("all"); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalData, setTotalData] = useState(0); + const params = useParams(); + const slug = params?.slug as string; + + const router = useRouter(); + const searchParams = useSearchParams(); + const MySwal = withReactContent(Swal); + + const itemsPerPage = 6; + + // Load categories on component mount + useEffect(() => { + loadCategories(); + }, []); + + // Load articles when filters change + useEffect(() => { + loadArticles(); + }, [currentPage, selectedCategory, selectedType, searchTerm, startDate, endDate]); + + // Sync bookmarks + useEffect(() => { + syncBookmarks(); + }, []); + + async function loadCategories() { + try { + const response = await getArticleCategories(); + if (response?.data?.data) { + setCategories(response.data.data); + } + } catch (error) { + console.error("Error loading categories:", error); + } + } + + async function loadArticles() { + try { + setLoading(true); + + const categoryId = selectedCategory === "all" ? undefined : selectedCategory; + const typeId = selectedType === "all" ? undefined : parseInt(selectedType); + + const response = await listArticles( + currentPage, + itemsPerPage, + typeId, + searchTerm || undefined, + categoryId, + "createdAt", + slug + ); + + if (response?.data?.data) { + const articlesData = response.data.data; + setArticles(articlesData); + setTotalPages(response.data.meta.totalPage || 1); + setTotalData(response.data.meta.count || 0); + } + } catch (error) { + console.error("Error loading articles:", error); + } finally { + setLoading(false); + } + } + + async function syncBookmarks() { + const roleId = Number(getCookiesDecrypt("urie")); + if (roleId && !isNaN(roleId)) { + try { + const savedLocal = localStorage.getItem("bookmarkedIds"); + let localSet = new Set(); + if (savedLocal) { + localSet = new Set(JSON.parse(savedLocal)); + 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("Error syncing bookmarks:", error); + } + } + } + + const handleSave = async (id: number) => { + 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", + }); + return; + } + + try { + const res = await toggleBookmark(id); + if (res?.error) { + MySwal.fire({ + icon: "error", + title: "Gagal", + text: "Gagal menyimpan artikel.", + confirmButtonColor: "#d33", + }); + } else { + const updated = new Set(bookmarkedIds); + updated.add(Number(id)); + setBookmarkedIds(updated); + localStorage.setItem( + "bookmarkedIds", + JSON.stringify(Array.from(updated)) + ); + + MySwal.fire({ + icon: "success", + title: "Berhasil", + text: "Artikel berhasil disimpan ke bookmark.", + timer: 1500, + showConfirmButton: false, + }); + } + } catch (err) { + console.error("Error saving bookmark:", err); + MySwal.fire({ + icon: "error", + title: "Kesalahan", + text: "Terjadi kesalahan saat menyimpan artikel.", + }); + } + }; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setCurrentPage(1); + loadArticles(); + }; + + const handleFilterChange = () => { + setCurrentPage(1); + loadArticles(); + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + const getTypeLabel = (typeId: number) => { + switch (typeId) { + case 1: return "📸 Image"; + case 2: return "🎬 Video"; + case 3: return "📝 Text"; + case 4: return "🎵 Audio"; + default: return "📄 Content"; + } + }; + + return ( +
+
+
+ {/* Filter Sidebar */} +
+ +
+
+ +

Filter Artikel

+
+ + {/* Search */} +
+ +
+ setSearchTerm(e.target.value)} + className="flex-1" + /> + +
+
+ + {/* Category Filter */} +
+ + +
+ + {/* Type Filter */} +
+ + +
+ + {/* Date Filter */} +
+ +
+ { + setStartDate(e.target.value); + handleFilterChange(); + }} + /> + { + setEndDate(e.target.value); + handleFilterChange(); + }} + /> +
+
+ + {/* Clear Filters */} + +
+
+
+ + {/* Main Content */} +
+
+

Daftar Artikel

+

+ Menampilkan {totalData} artikel + {searchTerm && ` untuk "${searchTerm}"`} + {selectedCategory !== "all" && ` dalam kategori "${categories.find(c => c.id === parseInt(selectedCategory))?.title || selectedCategory}"`} +

+
+ + {/* Articles Grid */} + {loading ? ( +
+ {[...Array(6)].map((_, i) => ( + +
+
+
+
+
+
+
+ ))} +
+ ) : articles.length > 0 ? ( +
+ {articles.map((article) => ( + +
+ + {article.title + +
+
+
+ + {article.clientName} + + + {article.categoryName || "Tanpa Kategori"} + +
+

+ {formatTanggal(article.createdAt)} +

+ +

+ {article.title} +

+ + {/*

+ {article.description || article.content || ""} +

*/} + +
+
+ + +
+ +
+
+
+ ))} +
+ ) : ( + +
+

Tidak ada artikel ditemukan

+

Coba ubah filter atau kata kunci pencarian

+
+
+ )} + + {/* Pagination */} + {totalPages > 1 && ( +
+ + + + handlePageChange(currentPage - 1)} + className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + const pageNumber = Math.max(1, Math.min(totalPages - 4, currentPage - 2)) + i; + if (pageNumber > totalPages) return null; + + return ( + + handlePageChange(pageNumber)} + isActive={currentPage === pageNumber} + className="cursor-pointer" + > + {pageNumber} + + + ); + })} + + + handlePageChange(currentPage + 1)} + className={currentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + +
+ )} +
+
+
+
+ ); +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/text/detail/[id]/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/text/detail/[id]/page.tsx new file mode 100644 index 0000000..a1ff3af --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/text/detail/[id]/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { useParams } from "next/navigation"; +import DocumentDetail from "@/components/main/content/document-detail"; + +export default function DetailDocumentInfo() { + const params = useParams(); + const id = params?.id as string; + + return ; +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/text/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/text/page.tsx new file mode 100644 index 0000000..bff7f73 --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/text/page.tsx @@ -0,0 +1,504 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Image from "next/image"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { ThumbsUp, ThumbsDown, Search, Filter, Calendar, Tag } from "lucide-react"; +import Link from "next/link"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { listArticles } from "@/service/landing/landing"; +import { getArticleCategories } from "@/service/categories/article-categories"; +import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; +import { getCookiesDecrypt } from "@/lib/utils"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; + +// Format tanggal +function formatTanggal(dateString: string) { + if (!dateString) return ""; + return ( + new Date(dateString) + .toLocaleString("id-ID", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Asia/Jakarta", + }) + .replace(/\./g, ":") + " WIB" + ); +} + +// Function to get link based on typeId +function getLink(item: any) { + switch (item?.typeId) { + 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 "#"; + } +} + +export default function ArticleListPage() { + const [articles, setArticles] = useState([]); + const [categories, setCategories] = useState([]); + const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedCategory, setSelectedCategory] = useState("all"); + const [selectedType, setSelectedType] = useState("3"); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalData, setTotalData] = useState(0); + const params = useParams(); + const slug = params?.slug as string; + + const router = useRouter(); + const searchParams = useSearchParams(); + const MySwal = withReactContent(Swal); + + const itemsPerPage = 6; + + // Load categories on component mount + useEffect(() => { + loadCategories(); + }, []); + + // Load articles when filters change + useEffect(() => { + loadArticles(); + }, [currentPage, selectedCategory, selectedType, searchTerm, startDate, endDate]); + + // Sync bookmarks + useEffect(() => { + syncBookmarks(); + }, []); + + async function loadCategories() { + try { + const response = await getArticleCategories(); + if (response?.data?.data) { + setCategories(response.data.data); + } + } catch (error) { + console.error("Error loading categories:", error); + } + } + + async function loadArticles() { + try { + setLoading(true); + + const categoryId = selectedCategory === "all" ? undefined : selectedCategory; + const typeId = selectedType === "all" ? undefined : parseInt(selectedType); + + const response = await listArticles( + currentPage, + itemsPerPage, + typeId, + searchTerm || undefined, + categoryId, + "createdAt", + slug + ); + + if (response?.data?.data) { + const articlesData = response.data.data; + setArticles(articlesData); + setTotalPages(response.data.meta.totalPage || 1); + setTotalData(response.data.meta.count || 0); + } + } catch (error) { + console.error("Error loading articles:", error); + } finally { + setLoading(false); + } + } + + async function syncBookmarks() { + const roleId = Number(getCookiesDecrypt("urie")); + if (roleId && !isNaN(roleId)) { + try { + const savedLocal = localStorage.getItem("bookmarkedIds"); + let localSet = new Set(); + if (savedLocal) { + localSet = new Set(JSON.parse(savedLocal)); + 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("Error syncing bookmarks:", error); + } + } + } + + const handleSave = async (id: number) => { + 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", + }); + return; + } + + try { + const res = await toggleBookmark(id); + if (res?.error) { + MySwal.fire({ + icon: "error", + title: "Gagal", + text: "Gagal menyimpan artikel.", + confirmButtonColor: "#d33", + }); + } else { + const updated = new Set(bookmarkedIds); + updated.add(Number(id)); + setBookmarkedIds(updated); + localStorage.setItem( + "bookmarkedIds", + JSON.stringify(Array.from(updated)) + ); + + MySwal.fire({ + icon: "success", + title: "Berhasil", + text: "Artikel berhasil disimpan ke bookmark.", + timer: 1500, + showConfirmButton: false, + }); + } + } catch (err) { + console.error("Error saving bookmark:", err); + MySwal.fire({ + icon: "error", + title: "Kesalahan", + text: "Terjadi kesalahan saat menyimpan artikel.", + }); + } + }; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setCurrentPage(1); + loadArticles(); + }; + + const handleFilterChange = () => { + setCurrentPage(1); + loadArticles(); + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + const getTypeLabel = (typeId: number) => { + switch (typeId) { + case 1: return "📸 Image"; + case 2: return "🎬 Video"; + case 3: return "📝 Text"; + case 4: return "🎵 Audio"; + default: return "📄 Content"; + } + }; + + return ( +
+
+
+ {/* Filter Sidebar */} +
+ +
+
+ +

Filter Artikel

+
+ + {/* Search */} +
+ +
+ setSearchTerm(e.target.value)} + className="flex-1" + /> + +
+
+ + {/* Category Filter */} +
+ + +
+ + {/* Type Filter */} +
+ + +
+ + {/* Date Filter */} +
+ +
+ { + setStartDate(e.target.value); + handleFilterChange(); + }} + /> + { + setEndDate(e.target.value); + handleFilterChange(); + }} + /> +
+
+ + {/* Clear Filters */} + +
+
+
+ + {/* Main Content */} +
+
+

Daftar Artikel

+

+ Menampilkan {totalData} artikel + {searchTerm && ` untuk "${searchTerm}"`} + {selectedCategory !== "all" && ` dalam kategori "${categories.find(c => c.id === parseInt(selectedCategory))?.title || selectedCategory}"`} +

+
+ + {/* Articles Grid */} + {loading ? ( +
+ {[...Array(6)].map((_, i) => ( + +
+
+
+
+
+
+
+ ))} +
+ ) : articles.length > 0 ? ( +
+ {articles.map((article) => ( + +
+ + {article.title + +
+
+
+ + {article.clientName} + + + {article.categoryName || "Tanpa Kategori"} + +
+

+ {formatTanggal(article.createdAt)} +

+ +

+ {article.title} +

+ + {/*

+ {article.description || article.content || ""} +

*/} + +
+
+ + +
+ +
+
+
+ ))} +
+ ) : ( + +
+

Tidak ada artikel ditemukan

+

Coba ubah filter atau kata kunci pencarian

+
+
+ )} + + {/* Pagination */} + {totalPages > 1 && ( +
+ + + + handlePageChange(currentPage - 1)} + className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + const pageNumber = Math.max(1, Math.min(totalPages - 4, currentPage - 2)) + i; + if (pageNumber > totalPages) return null; + + return ( + + handlePageChange(pageNumber)} + isActive={currentPage === pageNumber} + className="cursor-pointer" + > + {pageNumber} + + + ); + })} + + + handlePageChange(currentPage + 1)} + className={currentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + +
+ )} +
+
+
+
+ ); +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/video/comment/[id]/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/video/comment/[id]/page.tsx new file mode 100644 index 0000000..cdec591 --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/video/comment/[id]/page.tsx @@ -0,0 +1,5 @@ +import DetailCommentVideo from "@/components/main/comment-detail-video"; + +export default async function DetailCommentInfo() { + return ; +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/video/detail/[id]/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/video/detail/[id]/page.tsx new file mode 100644 index 0000000..a1ad7fd --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/video/detail/[id]/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { useParams } from "next/navigation"; +import DetailVideo from "@/components/main/content/video-detail"; + +export default function DetailVideoInfo() { + const params = useParams(); + const id = params?.id as string; + + return ; +} diff --git a/app/[locale]/(public)/tenant/[slug]/content/video/page.tsx b/app/[locale]/(public)/tenant/[slug]/content/video/page.tsx new file mode 100644 index 0000000..6e7eaab --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/content/video/page.tsx @@ -0,0 +1,504 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Image from "next/image"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { ThumbsUp, ThumbsDown, Search, Filter, Calendar, Tag } from "lucide-react"; +import Link from "next/link"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { listArticles } from "@/service/landing/landing"; +import { getArticleCategories } from "@/service/categories/article-categories"; +import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; +import { getCookiesDecrypt } from "@/lib/utils"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; + +// Format tanggal +function formatTanggal(dateString: string) { + if (!dateString) return ""; + return ( + new Date(dateString) + .toLocaleString("id-ID", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false, + timeZone: "Asia/Jakarta", + }) + .replace(/\./g, ":") + " WIB" + ); +} + +// Function to get link based on typeId +function getLink(item: any) { + switch (item?.typeId) { + 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 "#"; + } +} + +export default function ArticleListPage() { + const [articles, setArticles] = useState([]); + const [categories, setCategories] = useState([]); + const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedCategory, setSelectedCategory] = useState("all"); + const [selectedType, setSelectedType] = useState("2"); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalData, setTotalData] = useState(0); + const params = useParams(); + const slug = params?.slug as string; + + const router = useRouter(); + const searchParams = useSearchParams(); + const MySwal = withReactContent(Swal); + + const itemsPerPage = 6; + + // Load categories on component mount + useEffect(() => { + loadCategories(); + }, []); + + // Load articles when filters change + useEffect(() => { + loadArticles(); + }, [currentPage, selectedCategory, selectedType, searchTerm, startDate, endDate]); + + // Sync bookmarks + useEffect(() => { + syncBookmarks(); + }, []); + + async function loadCategories() { + try { + const response = await getArticleCategories(); + if (response?.data?.data) { + setCategories(response.data.data); + } + } catch (error) { + console.error("Error loading categories:", error); + } + } + + async function loadArticles() { + try { + setLoading(true); + + const categoryId = selectedCategory === "all" ? undefined : selectedCategory; + const typeId = selectedType === "all" ? undefined : parseInt(selectedType); + + const response = await listArticles( + currentPage, + itemsPerPage, + typeId, + searchTerm || undefined, + categoryId, + "createdAt", + slug + ); + + if (response?.data?.data) { + const articlesData = response.data.data; + setArticles(articlesData); + setTotalPages(response.data.meta.totalPage || 1); + setTotalData(response.data.meta.count || 0); + } + } catch (error) { + console.error("Error loading articles:", error); + } finally { + setLoading(false); + } + } + + async function syncBookmarks() { + const roleId = Number(getCookiesDecrypt("urie")); + if (roleId && !isNaN(roleId)) { + try { + const savedLocal = localStorage.getItem("bookmarkedIds"); + let localSet = new Set(); + if (savedLocal) { + localSet = new Set(JSON.parse(savedLocal)); + 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("Error syncing bookmarks:", error); + } + } + } + + const handleSave = async (id: number) => { + 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", + }); + return; + } + + try { + const res = await toggleBookmark(id); + if (res?.error) { + MySwal.fire({ + icon: "error", + title: "Gagal", + text: "Gagal menyimpan artikel.", + confirmButtonColor: "#d33", + }); + } else { + const updated = new Set(bookmarkedIds); + updated.add(Number(id)); + setBookmarkedIds(updated); + localStorage.setItem( + "bookmarkedIds", + JSON.stringify(Array.from(updated)) + ); + + MySwal.fire({ + icon: "success", + title: "Berhasil", + text: "Artikel berhasil disimpan ke bookmark.", + timer: 1500, + showConfirmButton: false, + }); + } + } catch (err) { + console.error("Error saving bookmark:", err); + MySwal.fire({ + icon: "error", + title: "Kesalahan", + text: "Terjadi kesalahan saat menyimpan artikel.", + }); + } + }; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setCurrentPage(1); + loadArticles(); + }; + + const handleFilterChange = () => { + setCurrentPage(1); + loadArticles(); + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + const getTypeLabel = (typeId: number) => { + switch (typeId) { + case 1: return "📸 Image"; + case 2: return "🎬 Video"; + case 3: return "📝 Text"; + case 4: return "🎵 Audio"; + default: return "📄 Content"; + } + }; + + return ( +
+
+
+ {/* Filter Sidebar */} +
+ +
+
+ +

Filter Artikel

+
+ + {/* Search */} +
+ +
+ setSearchTerm(e.target.value)} + className="flex-1" + /> + +
+
+ + {/* Category Filter */} +
+ + +
+ + {/* Type Filter */} +
+ + +
+ + {/* Date Filter */} +
+ +
+ { + setStartDate(e.target.value); + handleFilterChange(); + }} + /> + { + setEndDate(e.target.value); + handleFilterChange(); + }} + /> +
+
+ + {/* Clear Filters */} + +
+
+
+ + {/* Main Content */} +
+
+

Daftar Artikel

+

+ Menampilkan {totalData} artikel + {searchTerm && ` untuk "${searchTerm}"`} + {selectedCategory !== "all" && ` dalam kategori "${categories.find(c => c.id === parseInt(selectedCategory))?.title || selectedCategory}"`} +

+
+ + {/* Articles Grid */} + {loading ? ( +
+ {[...Array(6)].map((_, i) => ( + +
+
+
+
+
+
+
+ ))} +
+ ) : articles.length > 0 ? ( +
+ {articles.map((article) => ( + +
+ + {article.title + +
+
+
+ + {article.clientName} + + + {article.categoryName || "Tanpa Kategori"} + +
+

+ {formatTanggal(article.createdAt)} +

+ +

+ {article.title} +

+ + {/*

+ {article.description || article.content || ""} +

*/} + +
+
+ + +
+ +
+
+
+ ))} +
+ ) : ( + +
+

Tidak ada artikel ditemukan

+

Coba ubah filter atau kata kunci pencarian

+
+
+ )} + + {/* Pagination */} + {totalPages > 1 && ( +
+ + + + handlePageChange(currentPage - 1)} + className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + const pageNumber = Math.max(1, Math.min(totalPages - 4, currentPage - 2)) + i; + if (pageNumber > totalPages) return null; + + return ( + + handlePageChange(pageNumber)} + isActive={currentPage === pageNumber} + className="cursor-pointer" + > + {pageNumber} + + + ); + })} + + + handlePageChange(currentPage + 1)} + className={currentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"} + /> + + + +
+ )} +
+
+
+
+ ); +} diff --git a/app/[locale]/(public)/tenant/[slug]/layout.tsx b/app/[locale]/(public)/tenant/[slug]/layout.tsx new file mode 100644 index 0000000..e2ca7e0 --- /dev/null +++ b/app/[locale]/(public)/tenant/[slug]/layout.tsx @@ -0,0 +1,14 @@ +import Footer from "@/components/landing-page/footer"; +import Navbar from "@/components/landing-page/navbar"; + +const layout = async ({ children }: { children: React.ReactNode }) => { + return ( + <> + + {children} +