diff --git a/app/[locale]/(admin)/admin/settings/tenant/component/table-user-level.tsx b/app/[locale]/(admin)/admin/settings/tenant/component/tenant-settings-content-table.tsx similarity index 100% rename from app/[locale]/(admin)/admin/settings/tenant/component/table-user-level.tsx rename to app/[locale]/(admin)/admin/settings/tenant/component/tenant-settings-content-table.tsx diff --git a/app/[locale]/content/audio/detail/[id]/page.tsx b/app/[locale]/(public)/content/audio/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/audio/detail/[id]/page.tsx rename to app/[locale]/(public)/content/audio/detail/[id]/page.tsx diff --git a/app/[locale]/content/image/detail/[id]/page.tsx b/app/[locale]/(public)/content/image/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/image/detail/[id]/page.tsx rename to app/[locale]/(public)/content/image/detail/[id]/page.tsx diff --git a/app/[locale]/(public)/content/image/filter/page.tsx b/app/[locale]/(public)/content/image/filter/page.tsx new file mode 100644 index 0000000..b78d67d --- /dev/null +++ b/app/[locale]/(public)/content/image/filter/page.tsx @@ -0,0 +1,912 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { Card, CardContent } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Icon } from "@iconify/react/dist/iconify.js"; +import { + formatDateToIndonesian, + getOnlyDate, + getOnlyMonthAndYear, +} from "@/utils/globals"; +import { useParams, usePathname, useSearchParams } from "next/navigation"; +import { + getPublicCategoryData, + getUserLevelListByParent, + listCategory, + listData, + listDataRegional, +} from "@/service/landing/landing"; +import { + ColumnDef, + ColumnFiltersState, + PaginationState, + SortingState, + VisibilityState, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { Link, useRouter } from "@/i18n/routing"; +import { Input } from "@/components/ui/input"; +import ReactDatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import { close, loading } from "@/config/swal"; +import { useTranslations } from "next-intl"; +import ImageBlurry from "@/components/ui/image-blurry"; +import { Skeleton } from "@/components/ui/skeleton"; +import Image from "next/image"; +import LandingPagination from "@/components/landing-page/pagination"; + +const columns: ColumnDef[] = [ + { + accessorKey: "no", + header: "No", + cell: ({ row }) => {row.getValue("no")}, + }, +]; + +const FilterPage = () => { + const router = useRouter(); + const asPath = usePathname(); + const params = useParams(); + const searchParams = useSearchParams(); + const locale = params?.locale; + const [isLoading, setIsLoading] = useState(true); + const [imageData, setImageData] = useState(); + const [totalData, setTotalData] = React.useState(1); + const [totalPage, setTotalPage] = React.useState(1); + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [] + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const [pagination, setPagination] = React.useState({ + pageIndex: 0, + pageSize: 10, + }); + const [page, setPage] = useState(1); + const [totalContent, setTotalContent] = useState(); + const [change, setChange] = useState(false); + const sortBy = searchParams?.get("sortBy"); + const title = searchParams?.get("title"); + const categorie = searchParams?.get("category"); + const group = searchParams?.get("group"); + const [contentImage, setContentImage] = useState([]); + const [, setGetTotalPage] = useState(); + let typingTimer: any; + const doneTypingInterval = 1500; + const [search, setSearch] = useState(); + const [categoryFilter, setCategoryFilter] = useState([]); + const [monthYearFilter, setMonthYearFilter] = useState(); + const [searchTitle, setSearchTitle] = useState(""); + const [sortByOpt, setSortByOpt] = useState( + sortBy === "popular" ? "clickCount" : "createdAt" + ); + const isRegional = asPath?.includes("regional"); + const isSatker = asPath?.includes("satker"); + const [formatFilter, setFormatFilter] = useState([]); + const pages = page ? page - 1 : 0; + const [startDateString, setStartDateString] = useState(); + const [endDateString, setEndDateString] = useState(); + const [dateRange, setDateRange] = useState([null, null]); + const [calenderState, setCalenderState] = useState(false); + const [handleClose, setHandleClose] = useState(false); + const [categories, setCategories] = useState([]); + const [userLevels, setUserLevels] = useState([]); + const t = useTranslations("FilterPage"); + const [isFilterOpen, setIsFilterOpen] = useState(true); + const poldaName = params?.polda_name; + const satkerName = params?.satker_name; + const [categoryPage, setCategoryPage] = useState(1); + const [categoryTotalPages, setCategoryTotalPages] = useState(1); + + // const [startDate, endDate] = dateRange; + + useEffect(() => { + const timer = setTimeout(() => { + setIsLoading(false); + }, 3000); + + return () => clearTimeout(timer); + }, []); + + React.useEffect(() => { + const pageFromUrl = searchParams?.get("page"); + if (pageFromUrl) { + setPage(Number(pageFromUrl)); + } + }, [searchParams]); + + useEffect(() => { + async function initState() { + // getCategories(); + // getSelectedCategory(); + if (isSatker) { + getUserLevels(); + } + } + + initState(); + }, []); + + useEffect(() => { + if (categorie) { + setCategoryFilter( + categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie] + ); + console.log( + "Kategori", + categorie, + categorie?.split("&")?.length > 1 ? categorie?.split("&") : [categorie] + ); + } + }, [categorie]); + + // useEffect(() => { + // fetchData(); + // }, [page, sortBy, sortByOpt, title]); + + useEffect(() => { + async function initState() { + if (isRegional) { + getDataRegional(); + } else { + getDataAll(); + } + } + console.log(monthYearFilter, "monthFilter"); + initState(); + }, [ + change, + asPath, + monthYearFilter, + page, + sortBy, + sortByOpt, + title, + startDateString, + endDateString, + categorie, + formatFilter, + ]); + + // async function getCategories() { + // const category = await listCategory("1"); + // const resCategory = category?.data?.data?.content; + // setCategories(resCategory); + // } + + // useEffect(() => { + // initFetch(); + // }, []); + // const initFetch = async () => { + // const response = await getPublicCategoryData( + // poldaName && String(poldaName)?.length > 1 + // ? poldaName + // : satkerName && String(satkerName)?.length > 1 + // ? "satker-" + satkerName + // : "", + // "", + // locale == "en" ? true : false + // ); + // console.log("category", response); + // setCategories(response?.data?.data?.content); + // }; + + useEffect(() => { + fetchCategories(categoryPage); + }, [categoryPage]); + + const fetchCategories = async (pageNumber: number) => { + const groupParam = + poldaName && poldaName.length > 1 + ? poldaName + : satkerName && satkerName.length > 1 + ? "satker-" + satkerName + : ""; + + const isInt = locale === "en"; + + const response = await getPublicCategoryData( + groupParam, + "", + isInt, + pageNumber + ); + + const content = response?.data?.data?.content || []; + const total = response?.data?.data?.totalPages || 1; + + setCategories(content); + setCategoryTotalPages(total); + }; + + useEffect(() => { + function initState() { + if (dateRange[0] != null && dateRange[1] != null) { + setStartDateString(getOnlyDate(dateRange[0])); + setEndDateString(getOnlyDate(dateRange[1])); + setHandleClose(true); + console.log("date range", dateRange, getOnlyDate(dateRange[0])); + } + } + initState(); + }, [calenderState]); + + async function getDataAll() { + if (asPath?.includes("/polda/") == true) { + if (asPath?.split("/")[2] !== "[polda_name]") { + const filter = + categoryFilter?.length > 0 + ? categoryFilter?.sort().join(",") + : categorie || ""; + + const name = title == undefined ? "" : title; + const format = formatFilter == undefined ? "" : formatFilter?.join(","); + const filterGroup = group == undefined ? asPath.split("/")[2] : group; + loading(); + const response = await listData( + "1", + name, + filter, + 12, + pages, + sortByOpt, + format, + "", + filterGroup, + startDateString, + endDateString, + monthYearFilter + ? getOnlyMonthAndYear(monthYearFilter) + ?.split("/")[0] + ?.replace("", "") + : "", + monthYearFilter + ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] + : "", + locale == "en" ? true : false + ); + close(); + // setGetTotalPage(response?.data?.data?.totalPages); + // setContentImage(response?.data?.data?.content); + // setTotalContent(response?.data?.data?.totalElements); + const data = response?.data?.data; + const contentData = data?.content; + setImageData(contentData); + setTotalData(data?.totalElements); + setTotalPage(data?.totalPages); + setTotalContent(response?.data?.data?.totalElements); + } + } else { + const filter = + categoryFilter?.length > 0 + ? categoryFilter?.sort().join(",") + : categorie || ""; + + const name = title == undefined ? "" : title; + const format = formatFilter == undefined ? "" : formatFilter?.join(","); + loading(); + const response = await listData( + "1", + name, + filter, + 12, + pages, + sortByOpt, + format, + "", + "", + startDateString, + endDateString, + monthYearFilter + ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") + : "", + monthYearFilter + ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] + : "", + locale == "en" ? true : false + ); + close(); + // setGetTotalPage(response?.data?.data?.totalPages); + // setContentImage(response?.data?.data?.content); + // setTotalContent(response?.data?.data?.totalElements); + const data = response?.data?.data; + const contentData = data?.content; + setImageData(contentData); + setTotalData(data?.totalElements); + setTotalPage(data?.totalPages); + setTotalContent(response?.data?.data?.totalElements); + } + } + + const handleCategoryFilter = (e: boolean, id: string) => { + let filter = [...categoryFilter]; + + if (e) { + filter = [...filter, String(id)]; + } else { + filter = filter.filter((item) => item !== String(id)); + } + + console.log("checkbox filter", filter); + setCategoryFilter(filter); + router.push(`?category=${filter.join("&")}`); + }; + + const handleFormatFilter = (e: boolean, id: string) => { + let filter = [...formatFilter]; + + if (e) { + filter = [...formatFilter, id]; + } else { + filter.splice(formatFilter.indexOf(id), 1); + } + console.log("Format filter", filter); + setFormatFilter(filter); + }; + + const cleanCheckbox = () => { + setCategoryFilter([]); + setFormatFilter([]); + router.push(`?category=&title=`); + setDateRange([null, null]); + setMonthYearFilter(null); + setChange(!change); + }; + + async function getDataRegional() { + const filter = + categoryFilter?.length > 0 + ? categoryFilter?.sort().join(",") + : categorie || ""; + + const name = title == undefined ? "" : title; + const format = formatFilter == undefined ? "" : formatFilter?.join(","); + loading(); + const response = await listDataRegional( + "1", + name, + filter, + format, + "", + startDateString, + endDateString, + monthYearFilter + ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]?.replace("", "") + : "", + monthYearFilter + ? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1] + : "", + 12, + pages, + sortByOpt + ); + close(); + + setGetTotalPage(response?.data?.data?.totalPages); + setContentImage(response?.data?.data?.content); + setTotalContent(response?.data?.data?.totalElements); + } + + const table = useReactTable({ + data: imageData, + columns: columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + onPaginationChange: setPagination, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + pagination, + }, + }); + + function getSelectedCategory() { + const filter = []; + + if (categorie) { + const categoryArr = categorie.split(","); + + for (const element of categoryArr) { + filter.push(Number(element)); + } + + setCategoryFilter(filter); + } + } + + const handleDeleteDate = () => { + setDateRange([null, null]); + setStartDateString(""); + setEndDateString(""); + setHandleClose(false); + }; + + const handleSorting = (e: any) => { + console.log(e.target.value); + if (e.target.value == "terbaru") { + setSortByOpt("createdAt"); + } else { + setSortByOpt("clickCount"); + } + + setChange(!change); + }; + + async function getUserLevels() { + const res = await getUserLevelListByParent(761); + const userLevelList = res?.data?.data; + + if (userLevelList !== null) { + let optionArr: any = []; + + userLevelList?.map((option: any) => { + let optionData = { + id: option.id, + label: option.name, + value: option.id, + }; + + optionArr.push(optionData); + }); + + setUserLevels(optionArr); + } + } + + const handleKeyUp = () => { + clearTimeout(typingTimer); + typingTimer = setTimeout(doneTyping, doneTypingInterval); + }; + + async function doneTyping() { + if (searchTitle == "" || searchTitle == undefined) { + router.push("?title="); + } else { + router.push(`?title=${searchTitle.toLowerCase()}`); + } + } + + const handleKeyDown = () => { + clearTimeout(typingTimer); + }; + + // const shimmer = (w: number, h: number) => ` + // + // + // + // + // + // + // + // + // + // + // + // `; + + // const toBase64 = (str: string) => + // typeof window === "undefined" + // ? Buffer.from(str).toString("base64") + // : window.btoa(str); + + return ( +
+ {/* Header */} +
+

{t("image", { defaultValue: "Image" })}

+ {">"} +

+ + {t("allImage", { defaultValue: "All Image" })} + +

+

|

+ {!title ? ( +

+ {`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t( + "downloadableImage", + { defaultValue: "artikel berisi Foto yang dapat diunduh" } + )}`} +

+ ) : ( + <> +

+ {t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "} + "{title}" +

+ + )} +
+ +
+ {/* Left */} +
+ +
+ + {isFilterOpen && ( +
+

+ + Filter +

+
+
+
+ + setSearchTitle(e.target.value)} + onKeyUp={handleKeyUp} + onKeyDown={handleKeyDown} + type="text" + id="search" + placeholder={t("searchTitle", { + defaultValue: "Search Title", + })} + className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500" + /> +
+ +
+ + setMonthYearFilter(date)} + dateFormat="MM | yyyy" + placeholderText={t("selectYear", { + defaultValue: "Select Year", + })} + showMonthYearPicker + /> +
+ +
+ +
+ { + setDateRange(update); + }} + placeholderText={t("selectDate", { + defaultValue: "Select Date", + })} + onCalendarClose={() => setCalenderState(!calenderState)} + /> +
+ {handleClose ? ( + + ) : ( + "" + )} +
+
+
+ +
+

+ {t("categories", { defaultValue: "Categories" })} +

+
    + {categories.map((category: any) => ( +
  • + +
  • + ))} +
    + + + {Array.from({ length: categoryTotalPages }, (_, i) => ( + + ))} + + +
    +
+
+ {/* Garis */} +
+ {/* Garis */} +
+

+ Format +

+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ +
+
+ )} + + {/* Right */} +
+
+
+

+ {t("sortBy", { defaultValue: "Sort By" })} +

+ +
+ + {isLoading ? ( +
+
+ + + +
+
+ + + +
+
+ ) : ( + <> + {imageData?.length > 0 ? ( +
+ {imageData?.map((image: any) => ( + + + + {/* */} +
+ +
+ {/*
+ {formatDateToIndonesian( + new Date(image?.createdAt) + )}{" "} + {image?.timezone ? image?.timezone : "WIB"} +   | + + {image?.clickCount}{" "} +
+
+ {image?.title} +
*/} + +
+
+
+

+ {image?.categoryName?.toUpperCase() ?? + "Giat Pimpinan"} +

+

+ {formatDateToIndonesian( + new Date(image?.createdAt) + )}{" "} + {image?.timezone ?? "WIB"} | + {" "} + {image.clickCount} +

+
+ +

+ {image?.title} +

+
+
+ +
+
+ ))} +
+ ) : ( +

+ empty +

+ )} + + )} + {totalData > 1 && ( + + )} +
+
+
+
+ ); +}; + +export default FilterPage; diff --git a/app/[locale]/content/layout.tsx b/app/[locale]/(public)/content/layout.tsx similarity index 100% rename from app/[locale]/content/layout.tsx rename to app/[locale]/(public)/content/layout.tsx diff --git a/app/[locale]/content/text/detail/[id]/page.tsx b/app/[locale]/(public)/content/text/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/text/detail/[id]/page.tsx rename to app/[locale]/(public)/content/text/detail/[id]/page.tsx diff --git a/app/[locale]/content/video/comment/[id]/page.tsx b/app/[locale]/(public)/content/video/comment/[id]/page.tsx similarity index 100% rename from app/[locale]/content/video/comment/[id]/page.tsx rename to app/[locale]/(public)/content/video/comment/[id]/page.tsx diff --git a/app/[locale]/content/video/detail/[id]/page.tsx b/app/[locale]/(public)/content/video/detail/[id]/page.tsx similarity index 100% rename from app/[locale]/content/video/detail/[id]/page.tsx rename to app/[locale]/(public)/content/video/detail/[id]/page.tsx diff --git a/components/landing-page/media-update.tsx b/components/landing-page/media-update.tsx index ee2b6e1..29ddbd3 100644 --- a/components/landing-page/media-update.tsx +++ b/components/landing-page/media-update.tsx @@ -3,20 +3,21 @@ import { useState, useEffect } from "react"; import Image from "next/image"; import { Button } from "@/components/ui/button"; -import { ThumbsUp, ThumbsDown } from "lucide-react"; -import { Card } from "../ui/card"; -import Link from "next/link"; -import { listData, listArticles } from "@/service/landing/landing"; -import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; -import { getCookiesDecrypt } from "@/lib/utils"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card } from "@/components/ui/card"; import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import "swiper/css/navigation"; import { Navigation } from "swiper/modules"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; +import { listData, listArticles } from "@/service/landing/landing"; +import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; +import { getCookiesDecrypt } from "@/lib/utils"; +import Link from "next/link"; +import { ThumbsUp, ThumbsDown } from "lucide-react"; -// Format tanggal +// 🔹 Fungsi format tanggal ke WIB function formatTanggal(dateString: string) { if (!dateString) return ""; return ( @@ -35,82 +36,88 @@ function formatTanggal(dateString: string) { } export default function MediaUpdate() { - const [tab, setTab] = useState<"latest" | "popular">("latest"); - const [dataToRender, setDataToRender] = useState([]); + const [tipeKonten, setTipeKonten] = useState< + "image" | "video" | "text" | "audio" + >("image"); + const [urutan, setUrutan] = useState<"latest" | "popular">("latest"); + const [dataKonten, setDataKonten] = useState([]); const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); const [loading, setLoading] = useState(true); const MySwal = withReactContent(Swal); - useEffect(() => { - fetchData(tab); - }, [tab]); + // 🔹 Pemetaan tipe konten ke typeId API + const typeMap: Record = { + image: 1, + video: 2, + text: 3, + audio: 4, + }; - async function fetchData(section: "latest" | "popular") { + useEffect(() => { + ambilData(); + }, [tipeKonten, urutan]); + + async function ambilData() { try { setLoading(true); - // 🔹 Ambil data artikel + const typeId = typeMap[tipeKonten]; + const sortBy = urutan === "latest" ? "createdAt" : "viewCount"; + + // 🔹 Panggil API baru const response = await listArticles( 1, 20, - 1, // typeId = image + typeId, undefined, undefined, - section === "latest" ? "createdAt" : "viewCount" + sortBy ); - let articlesData: any[] = []; + let hasil: any[] = []; if (response?.error) { - console.error("Articles API failed, fallback ke old API"); - const fallbackRes = await listData( - "1", + console.error( + "Gagal ambil data dari listArticles, fallback ke listData" + ); + const fallback = await listData( + String(typeId), "", "", 20, 0, - section === "latest" ? "createdAt" : "clickCount", + urutan === "latest" ? "createdAt" : "clickCount", "", "", "" ); - articlesData = fallbackRes?.data?.data?.content || []; + hasil = fallback?.data?.data?.content || []; } else { - articlesData = response?.data?.data || []; + hasil = response?.data?.data || []; } - // 🔹 Normalisasi struktur data - const transformedData = articlesData.map((article: any) => ({ - id: article.id, - title: article.title, + // 🔹 Normalisasi data artikel + const dataBaru = hasil.map((a: any) => ({ + id: a.id, + title: a.title, category: - article.categoryName || - (article.categories && article.categories[0]?.title) || + a.categoryName || + (a.categories && a.categories[0]?.title) || "Tanpa Kategori", - createdAt: article.createdAt, - smallThumbnailLink: article.thumbnailUrl, - label: - article.typeId === 1 - ? "Image" - : article.typeId === 2 - ? "Video" - : article.typeId === 3 - ? "Text" - : article.typeId === 4 - ? "Audio" - : "", - ...article, + createdAt: a.createdAt, + smallThumbnailLink: a.thumbnailUrl, + typeId: a.typeId, })); - setDataToRender(transformedData); + setDataKonten(dataBaru); - // 🔹 Sinkronisasi bookmark + // 🔹 Sinkronisasi data bookmark const roleId = Number(getCookiesDecrypt("urie")); if (roleId && !isNaN(roleId)) { - const savedLocal = localStorage.getItem("bookmarkedIds"); + const simpananLocal = localStorage.getItem("bookmarkedIds"); let localSet = new Set(); - if (savedLocal) { - localSet = new Set(JSON.parse(savedLocal)); + if (simpananLocal) { + localSet = new Set(JSON.parse(simpananLocal)); setBookmarkedIds(localSet); } @@ -120,18 +127,17 @@ export default function MediaUpdate() { 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); + const gabungan = new Set([...localSet, ...ids]); + setBookmarkedIds(gabungan); localStorage.setItem( "bookmarkedIds", - JSON.stringify(Array.from(merged)) + JSON.stringify(Array.from(gabungan)) ); } } catch (err) { @@ -141,16 +147,7 @@ export default function MediaUpdate() { } } - // 🔹 Simpan perubahan bookmark ke localStorage - useEffect(() => { - if (bookmarkedIds.size > 0) { - localStorage.setItem( - "bookmarkedIds", - JSON.stringify(Array.from(bookmarkedIds)) - ); - } - }, [bookmarkedIds]); - + // 🔹 Simpan bookmark const handleSave = async (id: number) => { const roleId = Number(getCookiesDecrypt("urie")); if (!roleId || isNaN(roleId)) { @@ -170,7 +167,7 @@ export default function MediaUpdate() { MySwal.fire({ icon: "error", title: "Gagal", - text: "Gagal menyimpan artikel.", + text: "Tidak dapat menyimpan artikel.", confirmButtonColor: "#d33", }); } else { @@ -191,7 +188,7 @@ export default function MediaUpdate() { }); } } catch (err) { - console.error("Error saving bookmark:", err); + console.error("Error menyimpan bookmark:", err); MySwal.fire({ icon: "error", title: "Kesalahan", @@ -207,31 +204,54 @@ export default function MediaUpdate() { Media Update - {/* Tab */} + {/* 🔸 Tab Urutan */}
- {/* Slider */} + {/* 🔸 Tabs Tipe Konten */} + setTipeKonten(v)}> + + {["image", "video", "text", "audio"].map((tipe) => ( + + {tipe.charAt(0).toUpperCase() + tipe.slice(1)} + + ))} + + + + {/* 🔸 Konten */} {loading ? ( -

Loading...

+

Memuat konten...

) : ( - {dataToRender.map((item) => ( + {dataKonten.map((item) => (
@@ -259,8 +279,8 @@ export default function MediaUpdate() { {item.category || "Tanpa Kategori"} - - {item.label || ""} + + {tipeKonten}

@@ -286,7 +306,9 @@ export default function MediaUpdate() { : "bg-blue-600 text-white hover:bg-blue-700" }`} > - {bookmarkedIds.has(Number(item.id)) ? "Saved" : "Save"} + {bookmarkedIds.has(Number(item.id)) + ? "Tersimpan" + : "Simpan"}

@@ -296,21 +318,13 @@ export default function MediaUpdate() {
)} - {/* Lihat lebih banyak */} + {/* 🔸 Tombol Lihat Lebih Banyak */}
- + Lihat Lebih Banyak
@@ -327,10 +341,14 @@ export default function MediaUpdate() { // import { Card } from "../ui/card"; // import Link from "next/link"; // import { listData, listArticles } from "@/service/landing/landing"; +// import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content"; +// import { getCookiesDecrypt } from "@/lib/utils"; // import { Swiper, SwiperSlide } from "swiper/react"; // import "swiper/css"; // import "swiper/css/navigation"; // import { Navigation } from "swiper/modules"; +// import Swal from "sweetalert2"; +// import withReactContent from "sweetalert2-react-content"; // // Format tanggal // function formatTanggal(dateString: string) { @@ -353,7 +371,9 @@ export default function MediaUpdate() { // export default function MediaUpdate() { // const [tab, setTab] = useState<"latest" | "popular">("latest"); // const [dataToRender, setDataToRender] = useState([]); +// const [bookmarkedIds, setBookmarkedIds] = useState>(new Set()); // const [loading, setLoading] = useState(true); +// const MySwal = withReactContent(Swal); // useEffect(() => { // fetchData(tab); @@ -363,21 +383,19 @@ export default function MediaUpdate() { // try { // setLoading(true); -// // Use new Articles API // const response = await listArticles( // 1, // 20, -// 1, // typeId for images +// 1, // typeId = image // undefined, // undefined, // section === "latest" ? "createdAt" : "viewCount" // ); -// console.log("Media Update Articles API response:", response); +// let articlesData: any[] = []; // if (response?.error) { -// console.error("Articles API failed, falling back to old API"); -// // Fallback to old API +// console.error("Articles API failed, fallback ke old API"); // const fallbackRes = await listData( // "1", // "", @@ -389,49 +407,132 @@ export default function MediaUpdate() { // "", // "" // ); -// setDataToRender(fallbackRes?.data?.data?.content || []); -// return; +// articlesData = fallbackRes?.data?.data?.content || []; +// } else { +// articlesData = response?.data?.data || []; // } -// // Handle new API response structure -// const articlesData = response?.data?.data || []; - -// // Transform articles data to match old structure for backward compatibility +// // 🔹 Normalisasi struktur data // const transformedData = articlesData.map((article: any) => ({ // id: article.id, // title: article.title, -// category: article.categoryName || (article.categories && article.categories[0]?.title) || "Tanpa Kategori", +// category: +// article.categoryName || +// (article.categories && article.categories[0]?.title) || +// "Tanpa Kategori", // createdAt: article.createdAt, // smallThumbnailLink: article.thumbnailUrl, -// label: article.typeId === 1 ? "Image" : article.typeId === 2 ? "Video" : article.typeId === 3 ? "Text" : article.typeId === 4 ? "Audio" : "", -// ...article +// label: +// article.typeId === 1 +// ? "Image" +// : article.typeId === 2 +// ? "Video" +// : article.typeId === 3 +// ? "Text" +// : article.typeId === 4 +// ? "Audio" +// : "", +// ...article, // })); // setDataToRender(transformedData); + +// // 🔹 Sinkronisasi bookmark +// const roleId = Number(getCookiesDecrypt("urie")); +// if (roleId && !isNaN(roleId)) { +// 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 (err) { // console.error("Gagal memuat data:", err); -// // Try fallback to old API if new API fails -// try { -// const fallbackRes = await listData( -// "1", -// "", -// "", -// 20, -// 0, -// section === "latest" ? "createdAt" : "clickCount", -// "", -// "", -// "" -// ); -// setDataToRender(fallbackRes?.data?.data?.content || []); -// } catch (fallbackError) { -// console.error("Fallback API also failed:", fallbackError); -// } // } finally { // setLoading(false); // } // } +// // 🔹 Simpan perubahan bookmark ke localStorage +// useEffect(() => { +// if (bookmarkedIds.size > 0) { +// localStorage.setItem( +// "bookmarkedIds", +// JSON.stringify(Array.from(bookmarkedIds)) +// ); +// } +// }, [bookmarkedIds]); + +// 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.", +// }); +// } +// }; + // return ( //
//
@@ -508,11 +609,17 @@ export default function MediaUpdate() { // //
// // // diff --git a/components/landing-page/pagination.tsx b/components/landing-page/pagination.tsx new file mode 100644 index 0000000..ddadba3 --- /dev/null +++ b/components/landing-page/pagination.tsx @@ -0,0 +1,112 @@ +import { Button } from '@/components/ui/button'; +import { useMediaQuery } from '@/hooks/use-media-query'; +import { Table } from '@tanstack/react-table'; +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'; +import { useSearchParams, useRouter } from 'next/navigation'; +import React, { useEffect, useState } from 'react'; + +interface DataTablePaginationProps { + table: Table; + totalPage: number; // Total jumlah halaman + totalData: number; // Total jumlah data + visiblePageCount?: number; // Jumlah halaman yang ditampilkan (default 5) +} + +const LandingPagination = ({ + table, + totalPage, + totalData, + visiblePageCount = 5, +}: DataTablePaginationProps) => { + const router = useRouter(); + const searchParams = useSearchParams(); + + const [currentPageIndex, setCurrentPageIndex] = useState(1); + + useEffect(() => { + const pageFromUrl = searchParams?.get('page'); + if (pageFromUrl) { + const pageIndex = Math.min(Math.max(1, Number(pageFromUrl)), totalPage); + setCurrentPageIndex(pageIndex); + table.setPageIndex(pageIndex - 1); // Sinkronisasi tabel dengan URL + } + }, [searchParams, totalPage, table]); + + const handlePageChange = (pageIndex: number) => { + const clampedPageIndex = Math.min(Math.max(1, pageIndex), totalPage); + const searchParams = new URLSearchParams(window.location.search); + searchParams.set('page', clampedPageIndex.toString()); + + router.push(`${window.location.pathname}?${searchParams.toString()}`); + setCurrentPageIndex(clampedPageIndex); + table.setPageIndex(clampedPageIndex - 1); // Perbarui tabel dengan index berbasis 0 + }; + + const generatePageNumbers = () => { + const halfVisible = Math.floor(visiblePageCount / 2); + let startPage = Math.max(1, currentPageIndex - halfVisible); + let endPage = Math.min(totalPage, startPage + visiblePageCount - 1); + + if (endPage - startPage + 1 < visiblePageCount) { + startPage = Math.max(1, endPage - visiblePageCount + 1); + } + + return Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i); + }; + + return ( +
+
+ + + {generatePageNumbers().map((pageIndex) => ( + + ))} + + +
+
+ ); +}; + +export default LandingPagination; \ No newline at end of file diff --git a/components/main/content/image-detail.tsx b/components/main/content/image-detail.tsx index d8fbde1..5e8347c 100644 --- a/components/main/content/image-detail.tsx +++ b/components/main/content/image-detail.tsx @@ -1,6 +1,6 @@ "use client"; import Image from "next/image"; -import { Calendar, Clock, Eye } from "lucide-react"; +import { Calendar, Eye } from "lucide-react"; import { Button } from "@/components/ui/button"; import Link from "next/link"; import { useState, useEffect } from "react"; @@ -15,10 +15,10 @@ import { FaLink, FaShareAlt, } from "react-icons/fa"; -import { getDetail, getArticleDetail } from "@/service/landing/landing"; -import VideoPlayer from "@/utils/video-player"; +import { getDetail } from "@/service/landing/landing"; import { toBase64, shimmer } from "@/utils/globals"; import { Skeleton } from "@/components/ui/skeleton"; +import { getArticleDetail } from "@/service/content/content"; export default function ImageDetail({ id }: { id: string }) { const [copied, setCopied] = useState(false); @@ -28,21 +28,22 @@ export default function ImageDetail({ id }: { id: string }) { const [selectedImage, setSelectedImage] = useState(0); const [isLoading, setIsLoading] = useState(true); + // animasi skeleton loading useEffect(() => { const timer = setTimeout(() => { setIsLoading(false); }, 3000); - return () => clearTimeout(timer); }, []); + // salin link ke clipboard const handleCopyLink = async () => { try { await navigator.clipboard.writeText(window.location.href); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { - console.error("Failed to copy: ", err); + console.error("Gagal menyalin:", err); } }; @@ -59,82 +60,81 @@ export default function ImageDetail({ id }: { id: string }) { ); + // 🔹 Ambil data detail dari API useEffect(() => { const fetchDetail = async () => { try { setLoading(true); - - // Try new Articles API first - const response = await getArticleDetail(id); - console.log("Article Detail API response:", response); - - if (response?.error) { - console.error("Articles API failed, falling back to old API"); - // Fallback to old API - const fallbackResponse = await getDetail(id); - setData(fallbackResponse?.data?.data); - return; - } - // Handle new API response structure - const articleData = response?.data?.data; - if (articleData) { - // Transform article data to match old structure for backward compatibility - const transformedData = { - id: articleData.id, - title: articleData.title, - description: articleData.description, - createdAt: articleData.createdAt, - clickCount: articleData.viewCount, - creatorGroupLevelName: articleData.createdByName || "Unknown", - uploadedBy: { - publisher: articleData.createdByName || "MABES POLRI" - }, - files: articleData.files?.map((file: any) => ({ - id: file.id, - url: file.file_url, - fileName: file.file_name, - filePath: file.file_path, - fileThumbnail: file.file_thumbnail, - fileAlt: file.file_alt, - widthPixel: file.width_pixel, - heightPixel: file.height_pixel, - size: file.size, - downloadCount: file.download_count, - createdAt: file.created_at, - updatedAt: file.updated_at, - ...file + // 1️⃣ Coba ambil dari API baru /articles/{id} + const res = await getArticleDetail(id); + console.log("Response dari /articles/{id}:", res); + + // if (res?.error || !res?.data?.data) { + // console.warn("Gagal ambil dari API baru, coba fallback ke API lama"); + // const fallback = await getArticleDetail(id); + // setData(fallback?.data?.data); + // return; + // } + + // 2️⃣ Transformasi struktur agar kompatibel dengan UI lama + const article = res?.data?.data; + const mappedData = { + id: article.id, + title: article.title, + description: article.description, + createdAt: article.createdAt, + clickCount: article.viewCount, + creatorGroupLevelName: article.createdByName || "Unknown", + thumbnailUrl: article.thumbnailUrl || "", // ✅ tambahkan ini + uploadedBy: { + publisher: article.createdByName || "MABES POLRI", + }, + files: + article.files?.map((f: any) => ({ + id: f.id, + url: f.file_url, + fileName: f.file_name, + filePath: f.file_path, + fileThumbnail: f.file_thumbnail, + fileAlt: f.file_alt, + widthPixel: f.width_pixel, + heightPixel: f.height_pixel, + size: f.size, + downloadCount: f.download_count, + createdAt: f.created_at, + updatedAt: f.updated_at, })) || [], - }; + }; - console.log("transformedData : ", transformedData.files); - - setData(transformedData); - } - } catch (error) { - console.error("Error fetching detail:", error); - // Try fallback to old API if new API fails + setData(mappedData); + } catch (err) { + console.error("Gagal ambil detail artikel:", err); + // fallback ke API lama try { - const fallbackResponse = await getDetail(id); - setData(fallbackResponse?.data?.data); - } catch (fallbackError) { - console.error("Fallback API also failed:", fallbackError); + const fallback = await getDetail(id); + setData(fallback?.data?.data); + } catch (err2) { + console.error("Fallback API juga gagal:", err2); } } finally { setLoading(false); } }; + if (id) fetchDetail(); }, [id]); + // 🔹 UI Loading if (loading) { return (
-

Loading...

+

Memuat data...

); } + // 🔹 Jika data kosong if (!data) { return (
@@ -143,8 +143,10 @@ export default function ImageDetail({ id }: { id: string }) { ); } + // 🔹 Render konten utama return (
+ {/* Gambar Utama */} {isLoading ? (
@@ -157,16 +159,18 @@ export default function ImageDetail({ id }: { id: string }) { )}`} width={2560} height={1440} - src={data?.files[selectedImage]?.url || "/nodata.png"} + src={ + data?.files?.[selectedImage]?.url || + data?.thumbnailUrl || // ✅ fallback ke thumbnailUrl + "/nodata.png" + } alt="Main" className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain" /> - -
)} - {/* Gambar bawah Kecil */} + {/* Thumbnail bawah */} {isLoading ? (
@@ -174,22 +178,6 @@ export default function ImageDetail({ id }: { id: string }) {
) : ( - //
- // {detailDataImage?.files?.map((file: any, index: number) => ( - // setSelectedImage(index)} key={file?.id}> - // image-small - // - // ))} - //
)} + {/* Informasi artikel */}
- + {/* by {data?.uploadedBy?.publisher || "MABES POLRI"} - + */} {new Date(data.createdAt) @@ -228,92 +217,28 @@ export default function ImageDetail({ id }: { id: string }) { timeZone: "Asia/Jakarta", }) .replace(".", ":")}{" "} - WIB + WIB | - {/* - - {data.time || "-"} - */} - {data.clickCount || 0} + {data.viewCount || 0} - {" "} Creator: {data.creatorGroupLevelName}
+ {/* Konten artikel */}
- {/* Sidebar actions */} - {/*
-
- - COPY LINK -
- -
- - SHARE - - {showShareMenu && ( -
- } label="Facebook" /> - } label="TikTok" /> - } label="YouTube" /> - } label="WhatsApp" /> - } label="Instagram" /> - } label="Twitter" /> -
- )} -
- -
- - - COMMENT - -
-
*/} - - {/* Content */}

{data.title}

{data.description}

- {/* Actions bawah */} -
+ {/* Tombol aksi bawah */} +
COMMENT @@ -362,3 +277,368 @@ export default function ImageDetail({ id }: { id: string }) {
); } + +// "use client"; +// import Image from "next/image"; +// import { Calendar, Clock, Eye } from "lucide-react"; +// import { Button } from "@/components/ui/button"; +// import Link from "next/link"; +// import { useState, useEffect } from "react"; +// import { +// FaFacebookF, +// FaTiktok, +// FaYoutube, +// FaWhatsapp, +// FaInstagram, +// FaTwitter, +// FaCheck, +// FaLink, +// FaShareAlt, +// } from "react-icons/fa"; +// import { getDetail, getArticleDetail } from "@/service/landing/landing"; +// import VideoPlayer from "@/utils/video-player"; +// import { toBase64, shimmer } from "@/utils/globals"; +// import { Skeleton } from "@/components/ui/skeleton"; + +// export default function ImageDetail({ id }: { id: string }) { +// const [copied, setCopied] = useState(false); +// const [showShareMenu, setShowShareMenu] = useState(false); +// const [data, setData] = useState(null); +// const [loading, setLoading] = useState(true); +// const [selectedImage, setSelectedImage] = useState(0); +// const [isLoading, setIsLoading] = useState(true); + +// useEffect(() => { +// const timer = setTimeout(() => { +// setIsLoading(false); +// }, 3000); + +// return () => clearTimeout(timer); +// }, []); + +// const handleCopyLink = async () => { +// try { +// await navigator.clipboard.writeText(window.location.href); +// setCopied(true); +// setTimeout(() => setCopied(false), 2000); +// } catch (err) { +// console.error("Failed to copy: ", err); +// } +// }; + +// const SocialItem = ({ +// icon, +// label, +// }: { +// icon: React.ReactNode; +// label: string; +// }) => ( +//
+//
{icon}
+// {label} +//
+// ); + +// useEffect(() => { +// const fetchDetail = async () => { +// try { +// setLoading(true); + +// // Try new Articles API first +// const response = await getArticleDetail(id); +// console.log("Article Detail API response:", response); + +// if (response?.error) { +// console.error("Articles API failed, falling back to old API"); +// // Fallback to old API +// const fallbackResponse = await getDetail(id); +// setData(fallbackResponse?.data?.data); +// return; +// } + +// // Handle new API response structure +// const articleData = response?.data?.data; +// if (articleData) { +// // Transform article data to match old structure for backward compatibility +// const transformedData = { +// id: articleData.id, +// title: articleData.title, +// description: articleData.description, +// createdAt: articleData.createdAt, +// clickCount: articleData.viewCount, +// creatorGroupLevelName: articleData.createdByName || "Unknown", +// uploadedBy: { +// publisher: articleData.createdByName || "MABES POLRI" +// }, +// files: articleData.files?.map((file: any) => ({ +// id: file.id, +// url: file.file_url, +// fileName: file.file_name, +// filePath: file.file_path, +// fileThumbnail: file.file_thumbnail, +// fileAlt: file.file_alt, +// widthPixel: file.width_pixel, +// heightPixel: file.height_pixel, +// size: file.size, +// downloadCount: file.download_count, +// createdAt: file.created_at, +// updatedAt: file.updated_at, +// ...file +// })) || [], +// }; + +// console.log("transformedData : ", transformedData.files); + +// setData(transformedData); +// } +// } catch (error) { +// console.error("Error fetching detail:", error); +// // Try fallback to old API if new API fails +// try { +// const fallbackResponse = await getDetail(id); +// setData(fallbackResponse?.data?.data); +// } catch (fallbackError) { +// console.error("Fallback API also failed:", fallbackError); +// } +// } finally { +// setLoading(false); +// } +// }; +// if (id) fetchDetail(); +// }, [id]); + +// if (loading) { +// return ( +//
+//

Loading...

+//
+// ); +// } + +// if (!data) { +// return ( +//
+//

Data tidak ditemukan

+//
+// ); +// } + +// return ( +//
+// {isLoading ? ( +//
+// +//
+// ) : ( +//
+// Main + +//
+//
+// )} + +// {/* Gambar bawah Kecil */} +// {isLoading ? ( +//
+// +// +// +//
+// ) : ( +// //
+//
+// {data?.files?.map((file: any, index: number) => ( +// setSelectedImage(index)} key={file?.id}> +// image-small +// +// ))} +//
+// )} + +//
+//
+// +// by {data?.uploadedBy?.publisher || "MABES POLRI"} +// +// +// +// {new Date(data.createdAt) +// .toLocaleString("id-ID", { +// day: "2-digit", +// month: "short", +// year: "numeric", +// hour: "2-digit", +// minute: "2-digit", +// hour12: false, +// timeZone: "Asia/Jakarta", +// }) +// .replace(".", ":")}{" "} +// WIB +// +// {/* +// +// {data.time || "-"} +// */} +// +// +// {data.clickCount || 0} +// +// +// {" "} +// Creator: {data.creatorGroupLevelName} +// +//
+//
+ +//
+// {/* Sidebar actions */} +// {/*
+//
+// +// COPY LINK +//
+ +//
+// +// SHARE + +// {showShareMenu && ( +//
+// } label="Facebook" /> +// } label="TikTok" /> +// } label="YouTube" /> +// } label="WhatsApp" /> +// } label="Instagram" /> +// } label="Twitter" /> +//
+// )} +//
+ +//
+// +// +// COMMENT +// +//
+//
*/} + +// {/* Content */} +//
+//

{data.title}

+//
+//

{data.description}

+//
+ +// {/* Actions bawah */} +//
+//
+// +// COPY LINK +//
+//
+// +// SHARE +//
+//
+// +// +// COMMENT +// +//
+//
+//
+//
+//
+// ); +// } diff --git a/service/content/content.ts b/service/content/content.ts index d5b94cb..6187000 100644 --- a/service/content/content.ts +++ b/service/content/content.ts @@ -578,7 +578,7 @@ export interface ArticleDetailResponse { } // Function to fetch article detail -export async function getArticleDetail(id: number) { +export async function getArticleDetail(id: string) { const url = `articles/${id}`; return await httpGetInterceptor(url); } diff --git a/service/landing/landing.ts b/service/landing/landing.ts index ed6cf7b..75d5676 100644 --- a/service/landing/landing.ts +++ b/service/landing/landing.ts @@ -116,6 +116,7 @@ export async function getDetail(slug: string) { return await httpGetInterceptor(`media/public?slug=${slug}&state=mabes`); } + export async function getDetailMetaData(slug: string) { return await httpGetInterceptorForMetadata( `media/public?slug=${slug}&state=mabes&isSummary=true`