fix: detail content image
This commit is contained in:
parent
e8f49bdad4
commit
6e39ec873e
|
|
@ -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<any>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "no",
|
||||||
|
header: "No",
|
||||||
|
cell: ({ row }) => <span>{row.getValue("no")}</span>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const FilterPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const asPath = usePathname();
|
||||||
|
const params = useParams();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const locale = params?.locale;
|
||||||
|
const [isLoading, setIsLoading] = useState<any>(true);
|
||||||
|
const [imageData, setImageData] = useState<any>();
|
||||||
|
const [totalData, setTotalData] = React.useState<number>(1);
|
||||||
|
const [totalPage, setTotalPage] = React.useState<number>(1);
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const [pagination, setPagination] = React.useState<PaginationState>({
|
||||||
|
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<any>([]);
|
||||||
|
const [monthYearFilter, setMonthYearFilter] = useState<any>();
|
||||||
|
const [searchTitle, setSearchTitle] = useState<string>("");
|
||||||
|
const [sortByOpt, setSortByOpt] = useState<any>(
|
||||||
|
sortBy === "popular" ? "clickCount" : "createdAt"
|
||||||
|
);
|
||||||
|
const isRegional = asPath?.includes("regional");
|
||||||
|
const isSatker = asPath?.includes("satker");
|
||||||
|
const [formatFilter, setFormatFilter] = useState<any>([]);
|
||||||
|
const pages = page ? page - 1 : 0;
|
||||||
|
const [startDateString, setStartDateString] = useState<any>();
|
||||||
|
const [endDateString, setEndDateString] = useState<any>();
|
||||||
|
const [dateRange, setDateRange] = useState<any>([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) => `
|
||||||
|
// <svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
// <defs>
|
||||||
|
// <linearGradient id="g">
|
||||||
|
// <stop stop-color="#bcbcbd" offset="20%" />
|
||||||
|
// <stop stop-color="#f9fafb" offset="50%" />
|
||||||
|
// <stop stop-color="#bcbcbd" offset="70%" />
|
||||||
|
// </linearGradient>
|
||||||
|
// </defs>
|
||||||
|
// <rect width="${w}" height="${h}" fill="#bcbcbd" />
|
||||||
|
// <rect id="r" width="${w}" height="${h}" fill="url(#g)" />
|
||||||
|
// <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||||
|
// </svg>`;
|
||||||
|
|
||||||
|
// const toBase64 = (str: string) =>
|
||||||
|
// typeof window === "undefined"
|
||||||
|
// ? Buffer.from(str).toString("base64")
|
||||||
|
// : window.btoa(str);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-row md:flex-row items-start gap-3 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||||
|
<p> {t("image", { defaultValue: "Image" })}</p>
|
||||||
|
{">"}
|
||||||
|
<p>
|
||||||
|
<span className="font-bold">
|
||||||
|
{t("allImage", { defaultValue: "All Image" })}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className="font-bold">|</p>
|
||||||
|
{!title ? (
|
||||||
|
<p>
|
||||||
|
{`${t("thereIs", { defaultValue: "Terdapat" })} ${totalContent} ${t(
|
||||||
|
"downloadableImage",
|
||||||
|
{ defaultValue: "artikel berisi Foto yang dapat diunduh" }
|
||||||
|
)}`}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
{t("search-results", { defaultValue: "Hasil pencarian untuk" })}{" "}
|
||||||
|
<span className="font-bold">"{title}"</span>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||||
|
{/* Left */}
|
||||||
|
<div className="lg:hidden flex justify-end mb-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsFilterOpen(!isFilterOpen)}
|
||||||
|
className="text-sm text-white bg-[#bb3523] px-4 py-1 rounded-md shadow"
|
||||||
|
>
|
||||||
|
{isFilterOpen ? "Hide Filter" : "Show Filter"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isFilterOpen && (
|
||||||
|
<div className="h-fit min-w-full lg:min-w-[280px] max-w-full lg:max-w-[300px] bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
||||||
|
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
||||||
|
<Icon icon="stash:filter-light" fontSize={30} />
|
||||||
|
Filter
|
||||||
|
</h2>
|
||||||
|
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="search"
|
||||||
|
className="block text-sm font-medium text-gray-700 dark:text-white"
|
||||||
|
>
|
||||||
|
{t("search", { defaultValue: "Search" })}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={searchTitle}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||||
|
{t("monthYear", { defaultValue: "Month Year" })}
|
||||||
|
</label>
|
||||||
|
<ReactDatePicker
|
||||||
|
selected={monthYearFilter}
|
||||||
|
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||||
|
onChange={(date) => setMonthYearFilter(date)}
|
||||||
|
dateFormat="MM | yyyy"
|
||||||
|
placeholderText={t("selectYear", {
|
||||||
|
defaultValue: "Select Year",
|
||||||
|
})}
|
||||||
|
showMonthYearPicker
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||||
|
{t("date", { defaultValue: "Date" })}
|
||||||
|
</label>
|
||||||
|
<div className="flex flex-row justify justify-between gap-2">
|
||||||
|
<ReactDatePicker
|
||||||
|
selectsRange
|
||||||
|
className="mt-1 w-full border text-sm rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||||
|
startDate={dateRange[0]}
|
||||||
|
endDate={dateRange[1]}
|
||||||
|
onChange={(update) => {
|
||||||
|
setDateRange(update);
|
||||||
|
}}
|
||||||
|
placeholderText={t("selectDate", {
|
||||||
|
defaultValue: "Select Date",
|
||||||
|
})}
|
||||||
|
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{handleClose ? (
|
||||||
|
<Icon
|
||||||
|
icon="carbon:close-filled"
|
||||||
|
onClick={handleDeleteDate}
|
||||||
|
width="20"
|
||||||
|
inline
|
||||||
|
color="#216ba5"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||||
|
{t("categories", { defaultValue: "Categories" })}
|
||||||
|
</h3>
|
||||||
|
<ul className="mt-2 space-y-2">
|
||||||
|
{categories.map((category: any) => (
|
||||||
|
<li key={category?.id}>
|
||||||
|
<label
|
||||||
|
className="inline-flex items-center"
|
||||||
|
htmlFor={`${category.id}`}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
id={`${category.id}`}
|
||||||
|
value={category.id}
|
||||||
|
checked={categoryFilter.includes(String(category.id))}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleCategoryFilter(Boolean(e), category.id)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="ml-2 text-gray-700 dark:text-white">
|
||||||
|
{category?.name}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||||
|
}
|
||||||
|
disabled={categoryPage === 1}
|
||||||
|
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m13.15 16.15l-3.625-3.625q-.125-.125-.175-.25T9.3 12t.05-.275t.175-.25L13.15 7.85q.075-.075.163-.112T13.5 7.7q.2 0 .35.138T14 8.2v7.6q0 .225-.15.363t-.35.137q-.05 0-.35-.15"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => setCategoryPage(i + 1)}
|
||||||
|
className={`px-3 py-1 border rounded ${
|
||||||
|
categoryPage === i + 1
|
||||||
|
? "bg-[#bb3523] text-white"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{i + 1}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
setCategoryPage((prev) =>
|
||||||
|
Math.min(prev + 1, categoryTotalPages)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
disabled={categoryPage === categoryTotalPages}
|
||||||
|
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M10.5 16.3q-.2 0-.35-.137T10 15.8V8.2q0-.225.15-.362t.35-.138q.05 0 .35.15l3.625 3.625q.125.125.175.25t.05.275t-.05.275t-.175.25L10.85 16.15q-.075.075-.162.113t-.188.037"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/* Garis */}
|
||||||
|
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||||
|
{/* Garis */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||||
|
Format
|
||||||
|
</h3>
|
||||||
|
<ul className="mt-2 space-y-2">
|
||||||
|
<li>
|
||||||
|
<label className="inline-flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="png"
|
||||||
|
value="png"
|
||||||
|
checked={formatFilter.includes("png")}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleFormatFilter(Boolean(e), "png")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="ml-2 text-gray-700 dark:text-white">
|
||||||
|
PNG
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label className="inline-flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="jpeg"
|
||||||
|
value="jpeg"
|
||||||
|
checked={formatFilter.includes("jpeg")}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleFormatFilter(Boolean(e), "jpeg")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="ml-2 text-gray-700 dark:text-white">
|
||||||
|
JPEG
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label className="inline-flex items-center">
|
||||||
|
<Checkbox
|
||||||
|
id="jpg"
|
||||||
|
value="jpg"
|
||||||
|
checked={formatFilter.includes("jpg")}
|
||||||
|
onCheckedChange={(e) =>
|
||||||
|
handleFormatFilter(Boolean(e), "jpg")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className="ml-2 text-gray-700 dark:text-white">
|
||||||
|
JPG
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-black dark:border-white my-4"></div>
|
||||||
|
<div className="text-center">
|
||||||
|
<a
|
||||||
|
onClick={cleanCheckbox}
|
||||||
|
className="text-[#bb3523] cursor-pointer"
|
||||||
|
>
|
||||||
|
<b>Reset Filter</b>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Right */}
|
||||||
|
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex flex-col items-end mb-4">
|
||||||
|
<h2 className="text-lg font-semibold">
|
||||||
|
{t("sortBy", { defaultValue: "Sort By" })}
|
||||||
|
</h2>
|
||||||
|
<select
|
||||||
|
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
||||||
|
onChange={(e) => handleSorting(e)}
|
||||||
|
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||||
|
>
|
||||||
|
<option value="latest">
|
||||||
|
{t("latest", { defaultValue: "Latest" })}
|
||||||
|
</option>
|
||||||
|
<option value="popular">
|
||||||
|
{t("mostPopular", { defaultValue: "Most Popular" })}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||||
|
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||||
|
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||||
|
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col lg:flex-row space-y-3 w-full justify-center items-center gap-3">
|
||||||
|
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||||
|
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||||
|
<Skeleton className="h-[200px] w-[350px] rounded-xl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{imageData?.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{imageData?.map((image: any) => (
|
||||||
|
<Card
|
||||||
|
key={image?.id}
|
||||||
|
className="hover:scale-105 transition-transform duration-300"
|
||||||
|
>
|
||||||
|
<CardContent className="flex flex-col text-xs lg:text-sm w-full p-0">
|
||||||
|
<Link href={`/image/detail/${image?.slug}`}>
|
||||||
|
{/* <img src={image?.thumbnailLink} className="h-60 object-cover items-center justify-center cursor-pointer rounded-lg" /> */}
|
||||||
|
<div className="img-container h-60 bg-[#e9e9e9] cursor-pointer">
|
||||||
|
<ImageBlurry
|
||||||
|
src={
|
||||||
|
image?.smallThumbnailLink ||
|
||||||
|
image?.thumbnailLink
|
||||||
|
}
|
||||||
|
alt={image?.title}
|
||||||
|
style={{
|
||||||
|
objectFit: "contain",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* <div className="flex flex-row items-center gap-2 text-[10px] mx-1 mt-2">
|
||||||
|
{formatDateToIndonesian(
|
||||||
|
new Date(image?.createdAt)
|
||||||
|
)}{" "}
|
||||||
|
{image?.timezone ? image?.timezone : "WIB"}
|
||||||
|
|
|
||||||
|
<Icon
|
||||||
|
icon="formkit:eye"
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
/>
|
||||||
|
{image?.clickCount}{" "}
|
||||||
|
</div>
|
||||||
|
<div className="font-semibold pr-3 pb-3 mx-2 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible w-full">
|
||||||
|
{image?.title}
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
<div className="p-4 h-full flex flex-col justify-between">
|
||||||
|
<div className="flex flex-col gap-1 flex-grow">
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<p className="text-[9px] font-bold text-[#bb3523]">
|
||||||
|
{image?.categoryName?.toUpperCase() ??
|
||||||
|
"Giat Pimpinan"}
|
||||||
|
</p>
|
||||||
|
<p className="flex flex-row items-center text-[9px] gap-1 text-gray-600">
|
||||||
|
{formatDateToIndonesian(
|
||||||
|
new Date(image?.createdAt)
|
||||||
|
)}{" "}
|
||||||
|
{image?.timezone ?? "WIB"} |
|
||||||
|
<Icon
|
||||||
|
icon="formkit:eye"
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
/>{" "}
|
||||||
|
{image.clickCount}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm lg:text-base font-semibold text-black dark:text-white line-clamp-4">
|
||||||
|
{image?.title}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="flex items-center justify-center text-black">
|
||||||
|
<Image
|
||||||
|
width={1920}
|
||||||
|
height={1080}
|
||||||
|
src="/assets/empty-data.png"
|
||||||
|
alt="empty"
|
||||||
|
className="h-60 w-60 my-4"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{totalData > 1 && (
|
||||||
|
<LandingPagination
|
||||||
|
table={table}
|
||||||
|
totalData={totalData}
|
||||||
|
totalPage={totalPage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilterPage;
|
||||||
|
|
@ -3,20 +3,21 @@
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Card } from "../ui/card";
|
import { Card } from "@/components/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, SwiperSlide } from "swiper/react";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import { Navigation } from "swiper/modules";
|
import { Navigation } from "swiper/modules";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
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) {
|
function formatTanggal(dateString: string) {
|
||||||
if (!dateString) return "";
|
if (!dateString) return "";
|
||||||
return (
|
return (
|
||||||
|
|
@ -35,82 +36,88 @@ function formatTanggal(dateString: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MediaUpdate() {
|
export default function MediaUpdate() {
|
||||||
const [tab, setTab] = useState<"latest" | "popular">("latest");
|
const [tipeKonten, setTipeKonten] = useState<
|
||||||
const [dataToRender, setDataToRender] = useState<any[]>([]);
|
"image" | "video" | "text" | "audio"
|
||||||
|
>("image");
|
||||||
|
const [urutan, setUrutan] = useState<"latest" | "popular">("latest");
|
||||||
|
const [dataKonten, setDataKonten] = useState<any[]>([]);
|
||||||
const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
|
const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
useEffect(() => {
|
// 🔹 Pemetaan tipe konten ke typeId API
|
||||||
fetchData(tab);
|
const typeMap: Record<typeof tipeKonten, number> = {
|
||||||
}, [tab]);
|
image: 1,
|
||||||
|
video: 2,
|
||||||
|
text: 3,
|
||||||
|
audio: 4,
|
||||||
|
};
|
||||||
|
|
||||||
async function fetchData(section: "latest" | "popular") {
|
useEffect(() => {
|
||||||
|
ambilData();
|
||||||
|
}, [tipeKonten, urutan]);
|
||||||
|
|
||||||
|
async function ambilData() {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// 🔹 Ambil data artikel
|
const typeId = typeMap[tipeKonten];
|
||||||
|
const sortBy = urutan === "latest" ? "createdAt" : "viewCount";
|
||||||
|
|
||||||
|
// 🔹 Panggil API baru
|
||||||
const response = await listArticles(
|
const response = await listArticles(
|
||||||
1,
|
1,
|
||||||
20,
|
20,
|
||||||
1, // typeId = image
|
typeId,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
section === "latest" ? "createdAt" : "viewCount"
|
sortBy
|
||||||
);
|
);
|
||||||
|
|
||||||
let articlesData: any[] = [];
|
let hasil: any[] = [];
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
console.error("Articles API failed, fallback ke old API");
|
console.error(
|
||||||
const fallbackRes = await listData(
|
"Gagal ambil data dari listArticles, fallback ke listData"
|
||||||
"1",
|
);
|
||||||
|
const fallback = await listData(
|
||||||
|
String(typeId),
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
20,
|
20,
|
||||||
0,
|
0,
|
||||||
section === "latest" ? "createdAt" : "clickCount",
|
urutan === "latest" ? "createdAt" : "clickCount",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
articlesData = fallbackRes?.data?.data?.content || [];
|
hasil = fallback?.data?.data?.content || [];
|
||||||
} else {
|
} else {
|
||||||
articlesData = response?.data?.data || [];
|
hasil = response?.data?.data || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Normalisasi struktur data
|
// 🔹 Normalisasi data artikel
|
||||||
const transformedData = articlesData.map((article: any) => ({
|
const dataBaru = hasil.map((a: any) => ({
|
||||||
id: article.id,
|
id: a.id,
|
||||||
title: article.title,
|
title: a.title,
|
||||||
category:
|
category:
|
||||||
article.categoryName ||
|
a.categoryName ||
|
||||||
(article.categories && article.categories[0]?.title) ||
|
(a.categories && a.categories[0]?.title) ||
|
||||||
"Tanpa Kategori",
|
"Tanpa Kategori",
|
||||||
createdAt: article.createdAt,
|
createdAt: a.createdAt,
|
||||||
smallThumbnailLink: article.thumbnailUrl,
|
smallThumbnailLink: a.thumbnailUrl,
|
||||||
label:
|
typeId: a.typeId,
|
||||||
article.typeId === 1
|
|
||||||
? "Image"
|
|
||||||
: article.typeId === 2
|
|
||||||
? "Video"
|
|
||||||
: article.typeId === 3
|
|
||||||
? "Text"
|
|
||||||
: article.typeId === 4
|
|
||||||
? "Audio"
|
|
||||||
: "",
|
|
||||||
...article,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setDataToRender(transformedData);
|
setDataKonten(dataBaru);
|
||||||
|
|
||||||
// 🔹 Sinkronisasi bookmark
|
// 🔹 Sinkronisasi data bookmark
|
||||||
const roleId = Number(getCookiesDecrypt("urie"));
|
const roleId = Number(getCookiesDecrypt("urie"));
|
||||||
if (roleId && !isNaN(roleId)) {
|
if (roleId && !isNaN(roleId)) {
|
||||||
const savedLocal = localStorage.getItem("bookmarkedIds");
|
const simpananLocal = localStorage.getItem("bookmarkedIds");
|
||||||
let localSet = new Set<number>();
|
let localSet = new Set<number>();
|
||||||
if (savedLocal) {
|
if (simpananLocal) {
|
||||||
localSet = new Set(JSON.parse(savedLocal));
|
localSet = new Set(JSON.parse(simpananLocal));
|
||||||
setBookmarkedIds(localSet);
|
setBookmarkedIds(localSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,18 +127,17 @@ export default function MediaUpdate() {
|
||||||
res?.data?.data?.bookmarks ||
|
res?.data?.data?.bookmarks ||
|
||||||
res?.data?.data ||
|
res?.data?.data ||
|
||||||
[];
|
[];
|
||||||
|
|
||||||
const ids = new Set<number>(
|
const ids = new Set<number>(
|
||||||
(Array.isArray(bookmarks) ? bookmarks : [])
|
(Array.isArray(bookmarks) ? bookmarks : [])
|
||||||
.map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
|
.map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
|
||||||
.filter((x) => !isNaN(x))
|
.filter((x) => !isNaN(x))
|
||||||
);
|
);
|
||||||
|
|
||||||
const merged = new Set([...localSet, ...ids]);
|
const gabungan = new Set([...localSet, ...ids]);
|
||||||
setBookmarkedIds(merged);
|
setBookmarkedIds(gabungan);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"bookmarkedIds",
|
"bookmarkedIds",
|
||||||
JSON.stringify(Array.from(merged))
|
JSON.stringify(Array.from(gabungan))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -141,16 +147,7 @@ export default function MediaUpdate() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Simpan perubahan bookmark ke localStorage
|
// 🔹 Simpan bookmark
|
||||||
useEffect(() => {
|
|
||||||
if (bookmarkedIds.size > 0) {
|
|
||||||
localStorage.setItem(
|
|
||||||
"bookmarkedIds",
|
|
||||||
JSON.stringify(Array.from(bookmarkedIds))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [bookmarkedIds]);
|
|
||||||
|
|
||||||
const handleSave = async (id: number) => {
|
const handleSave = async (id: number) => {
|
||||||
const roleId = Number(getCookiesDecrypt("urie"));
|
const roleId = Number(getCookiesDecrypt("urie"));
|
||||||
if (!roleId || isNaN(roleId)) {
|
if (!roleId || isNaN(roleId)) {
|
||||||
|
|
@ -170,7 +167,7 @@ export default function MediaUpdate() {
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
title: "Gagal",
|
title: "Gagal",
|
||||||
text: "Gagal menyimpan artikel.",
|
text: "Tidak dapat menyimpan artikel.",
|
||||||
confirmButtonColor: "#d33",
|
confirmButtonColor: "#d33",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -191,7 +188,7 @@ export default function MediaUpdate() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error saving bookmark:", err);
|
console.error("Error menyimpan bookmark:", err);
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
title: "Kesalahan",
|
title: "Kesalahan",
|
||||||
|
|
@ -207,31 +204,54 @@ export default function MediaUpdate() {
|
||||||
Media Update
|
Media Update
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Tab */}
|
{/* 🔸 Tab Urutan */}
|
||||||
<div className="flex justify-center mb-8 bg-white">
|
<div className="flex justify-center mb-8 bg-white">
|
||||||
<Card className="bg-[#FFFFFF] rounded-xl flex flex-row p-3 gap-2">
|
<Card className="bg-[#FFFFFF] rounded-xl flex flex-row p-3 gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setTab("latest")}
|
onClick={() => setUrutan("latest")}
|
||||||
className={`px-5 py-2 rounded-lg text-sm font-medium ${
|
className={`px-5 py-2 rounded-lg text-sm font-medium ${
|
||||||
tab === "latest" ? "bg-[#C6A455] text-white" : "text-[#C6A455]"
|
urutan === "latest"
|
||||||
|
? "bg-[#C6A455] text-white"
|
||||||
|
: "text-[#C6A455]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Konten Terbaru
|
Terbaru
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setTab("popular")}
|
onClick={() => setUrutan("popular")}
|
||||||
className={`px-5 py-2 rounded-lg text-sm font-medium ${
|
className={`px-5 py-2 rounded-lg text-sm font-medium ${
|
||||||
tab === "popular" ? "bg-[#C6A455] text-white" : "text-[#C6A455]"
|
urutan === "popular"
|
||||||
|
? "bg-[#C6A455] text-white"
|
||||||
|
: "text-[#C6A455]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Konten Terpopuler
|
Terpopuler
|
||||||
</button>
|
</button>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Slider */}
|
{/* 🔸 Tabs Tipe Konten */}
|
||||||
|
<Tabs value={tipeKonten} onValueChange={(v: any) => setTipeKonten(v)}>
|
||||||
|
<TabsList className="flex mb-6 pb-2 bg-transparent">
|
||||||
|
{["image", "video", "text", "audio"].map((tipe) => (
|
||||||
|
<TabsTrigger
|
||||||
|
key={tipe}
|
||||||
|
value={tipe}
|
||||||
|
className={`px-5 py-2 rounded-lg text-sm font-medium mx-1 border ${
|
||||||
|
tipeKonten === tipe
|
||||||
|
? "bg-[#C6A455] text-white border-[#C6A455]"
|
||||||
|
: "text-[#C6A455] border-[#C6A455]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tipe.charAt(0).toUpperCase() + tipe.slice(1)}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
{/* 🔸 Konten */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-center">Loading...</p>
|
<p className="text-center">Memuat konten...</p>
|
||||||
) : (
|
) : (
|
||||||
<Swiper
|
<Swiper
|
||||||
modules={[Navigation]}
|
modules={[Navigation]}
|
||||||
|
|
@ -243,7 +263,7 @@ export default function MediaUpdate() {
|
||||||
1024: { slidesPerView: 4 },
|
1024: { slidesPerView: 4 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{dataToRender.map((item) => (
|
{dataKonten.map((item) => (
|
||||||
<SwiperSlide key={item.id}>
|
<SwiperSlide key={item.id}>
|
||||||
<div className="rounded-xl shadow-md overflow-hidden bg-white">
|
<div className="rounded-xl shadow-md overflow-hidden bg-white">
|
||||||
<div className="w-full h-[204px] relative">
|
<div className="w-full h-[204px] relative">
|
||||||
|
|
@ -259,8 +279,8 @@ export default function MediaUpdate() {
|
||||||
<span className="text-xs text-white px-2 py-0.5 rounded bg-blue-600">
|
<span className="text-xs text-white px-2 py-0.5 rounded bg-blue-600">
|
||||||
{item.category || "Tanpa Kategori"}
|
{item.category || "Tanpa Kategori"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs font-medium text-[#b3882e]">
|
<span className="text-xs font-medium text-[#b3882e] capitalize">
|
||||||
{item.label || ""}
|
{tipeKonten}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mb-1">
|
<p className="text-xs text-gray-500 mb-1">
|
||||||
|
|
@ -286,7 +306,9 @@ export default function MediaUpdate() {
|
||||||
: "bg-blue-600 text-white hover:bg-blue-700"
|
: "bg-blue-600 text-white hover:bg-blue-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{bookmarkedIds.has(Number(item.id)) ? "Saved" : "Save"}
|
{bookmarkedIds.has(Number(item.id))
|
||||||
|
? "Tersimpan"
|
||||||
|
: "Simpan"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -296,21 +318,13 @@ export default function MediaUpdate() {
|
||||||
</Swiper>
|
</Swiper>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Lihat lebih banyak */}
|
{/* 🔸 Tombol Lihat Lebih Banyak */}
|
||||||
<div className="text-center mt-10">
|
<div className="text-center mt-10">
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={`/${tipeKonten}/filter?sortBy=${urutan}`}
|
||||||
tab === "latest"
|
className="inline-block border border-[#b3882e] text-[#b3882e] px-6 py-2 rounded-md text-sm font-medium hover:bg-[#b3882e]/10 transition"
|
||||||
? "https://mediahub.polri.go.id/"
|
|
||||||
: "https://tribratanews.polri.go.id/"
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Button
|
Lihat Lebih Banyak
|
||||||
size={"lg"}
|
|
||||||
className="text-[#b3882e] bg-transparent border border-[#b3882e] px-6 py-2 rounded-s-sm text-sm font-medium hover:bg-[#b3882e]/10 transition"
|
|
||||||
>
|
|
||||||
Lihat Lebih Banyak
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -327,10 +341,14 @@ export default function MediaUpdate() {
|
||||||
// import { Card } from "../ui/card";
|
// import { Card } from "../ui/card";
|
||||||
// import Link from "next/link";
|
// import Link from "next/link";
|
||||||
// import { listData, listArticles } from "@/service/landing/landing";
|
// 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, SwiperSlide } from "swiper/react";
|
||||||
// import "swiper/css";
|
// import "swiper/css";
|
||||||
// import "swiper/css/navigation";
|
// import "swiper/css/navigation";
|
||||||
// import { Navigation } from "swiper/modules";
|
// import { Navigation } from "swiper/modules";
|
||||||
|
// import Swal from "sweetalert2";
|
||||||
|
// import withReactContent from "sweetalert2-react-content";
|
||||||
|
|
||||||
// // Format tanggal
|
// // Format tanggal
|
||||||
// function formatTanggal(dateString: string) {
|
// function formatTanggal(dateString: string) {
|
||||||
|
|
@ -353,7 +371,9 @@ export default function MediaUpdate() {
|
||||||
// export default function MediaUpdate() {
|
// export default function MediaUpdate() {
|
||||||
// const [tab, setTab] = useState<"latest" | "popular">("latest");
|
// const [tab, setTab] = useState<"latest" | "popular">("latest");
|
||||||
// const [dataToRender, setDataToRender] = useState<any[]>([]);
|
// const [dataToRender, setDataToRender] = useState<any[]>([]);
|
||||||
|
// const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
|
||||||
// const [loading, setLoading] = useState(true);
|
// const [loading, setLoading] = useState(true);
|
||||||
|
// const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// fetchData(tab);
|
// fetchData(tab);
|
||||||
|
|
@ -363,21 +383,19 @@ export default function MediaUpdate() {
|
||||||
// try {
|
// try {
|
||||||
// setLoading(true);
|
// setLoading(true);
|
||||||
|
|
||||||
// // Use new Articles API
|
|
||||||
// const response = await listArticles(
|
// const response = await listArticles(
|
||||||
// 1,
|
// 1,
|
||||||
// 20,
|
// 20,
|
||||||
// 1, // typeId for images
|
// 1, // typeId = image
|
||||||
// undefined,
|
// undefined,
|
||||||
// undefined,
|
// undefined,
|
||||||
// section === "latest" ? "createdAt" : "viewCount"
|
// section === "latest" ? "createdAt" : "viewCount"
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// console.log("Media Update Articles API response:", response);
|
// let articlesData: any[] = [];
|
||||||
|
|
||||||
// if (response?.error) {
|
// if (response?.error) {
|
||||||
// console.error("Articles API failed, falling back to old API");
|
// console.error("Articles API failed, fallback ke old API");
|
||||||
// // Fallback to old API
|
|
||||||
// const fallbackRes = await listData(
|
// const fallbackRes = await listData(
|
||||||
// "1",
|
// "1",
|
||||||
// "",
|
// "",
|
||||||
|
|
@ -389,49 +407,132 @@ export default function MediaUpdate() {
|
||||||
// "",
|
// "",
|
||||||
// ""
|
// ""
|
||||||
// );
|
// );
|
||||||
// setDataToRender(fallbackRes?.data?.data?.content || []);
|
// articlesData = fallbackRes?.data?.data?.content || [];
|
||||||
// return;
|
// } else {
|
||||||
|
// articlesData = response?.data?.data || [];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // Handle new API response structure
|
// // 🔹 Normalisasi struktur data
|
||||||
// const articlesData = response?.data?.data || [];
|
|
||||||
|
|
||||||
// // Transform articles data to match old structure for backward compatibility
|
|
||||||
// const transformedData = articlesData.map((article: any) => ({
|
// const transformedData = articlesData.map((article: any) => ({
|
||||||
// id: article.id,
|
// id: article.id,
|
||||||
// title: article.title,
|
// 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,
|
// createdAt: article.createdAt,
|
||||||
// smallThumbnailLink: article.thumbnailUrl,
|
// smallThumbnailLink: article.thumbnailUrl,
|
||||||
// label: article.typeId === 1 ? "Image" : article.typeId === 2 ? "Video" : article.typeId === 3 ? "Text" : article.typeId === 4 ? "Audio" : "",
|
// label:
|
||||||
// ...article
|
// article.typeId === 1
|
||||||
|
// ? "Image"
|
||||||
|
// : article.typeId === 2
|
||||||
|
// ? "Video"
|
||||||
|
// : article.typeId === 3
|
||||||
|
// ? "Text"
|
||||||
|
// : article.typeId === 4
|
||||||
|
// ? "Audio"
|
||||||
|
// : "",
|
||||||
|
// ...article,
|
||||||
// }));
|
// }));
|
||||||
|
|
||||||
// setDataToRender(transformedData);
|
// setDataToRender(transformedData);
|
||||||
|
|
||||||
|
// // 🔹 Sinkronisasi bookmark
|
||||||
|
// const roleId = Number(getCookiesDecrypt("urie"));
|
||||||
|
// if (roleId && !isNaN(roleId)) {
|
||||||
|
// const savedLocal = localStorage.getItem("bookmarkedIds");
|
||||||
|
// let localSet = new Set<number>();
|
||||||
|
// 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<number>(
|
||||||
|
// (Array.isArray(bookmarks) ? bookmarks : [])
|
||||||
|
// .map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
|
||||||
|
// .filter((x) => !isNaN(x))
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const merged = new Set([...localSet, ...ids]);
|
||||||
|
// setBookmarkedIds(merged);
|
||||||
|
// localStorage.setItem(
|
||||||
|
// "bookmarkedIds",
|
||||||
|
// JSON.stringify(Array.from(merged))
|
||||||
|
// );
|
||||||
|
// }
|
||||||
// } catch (err) {
|
// } catch (err) {
|
||||||
// console.error("Gagal memuat data:", 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 {
|
// } finally {
|
||||||
// setLoading(false);
|
// 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 (
|
// return (
|
||||||
// <section className="bg-white px-4 py-10 border max-w-[1350px] mx-auto rounded-md border-[#CDD5DF] my-10">
|
// <section className="bg-white px-4 py-10 border max-w-[1350px] mx-auto rounded-md border-[#CDD5DF] my-10">
|
||||||
// <div className="max-w-screen-xl mx-auto">
|
// <div className="max-w-screen-xl mx-auto">
|
||||||
|
|
@ -508,11 +609,17 @@ export default function MediaUpdate() {
|
||||||
// <ThumbsDown className="w-4 h-4 cursor-pointer" />
|
// <ThumbsDown className="w-4 h-4 cursor-pointer" />
|
||||||
// </div>
|
// </div>
|
||||||
// <Button
|
// <Button
|
||||||
|
// onClick={() => handleSave(item.id)}
|
||||||
|
// disabled={bookmarkedIds.has(Number(item.id))}
|
||||||
// variant="default"
|
// variant="default"
|
||||||
// size="sm"
|
// size="sm"
|
||||||
// className="text-white bg-blue-600 rounded px-4"
|
// className={`rounded px-4 ${
|
||||||
|
// bookmarkedIds.has(Number(item.id))
|
||||||
|
// ? "bg-gray-400 cursor-not-allowed text-white"
|
||||||
|
// : "bg-blue-600 text-white hover:bg-blue-700"
|
||||||
|
// }`}
|
||||||
// >
|
// >
|
||||||
// Save
|
// {bookmarkedIds.has(Number(item.id)) ? "Saved" : "Save"}
|
||||||
// </Button>
|
// </Button>
|
||||||
// </div>
|
// </div>
|
||||||
// </div>
|
// </div>
|
||||||
|
|
|
||||||
|
|
@ -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<any>;
|
||||||
|
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<number>(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 (
|
||||||
|
<div className="flex items-center justify-center space-y-4 mt-8 md:space-y-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(1)}
|
||||||
|
disabled={currentPageIndex === 1}
|
||||||
|
className="w-8 h-8"
|
||||||
|
>
|
||||||
|
<ChevronsLeft className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(currentPageIndex-1)}
|
||||||
|
disabled={currentPageIndex === 1}
|
||||||
|
className="w-8 h-8"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
{generatePageNumbers().map((pageIndex) => (
|
||||||
|
<Button
|
||||||
|
key={pageIndex}
|
||||||
|
onClick={() => handlePageChange(pageIndex)}
|
||||||
|
size="icon"
|
||||||
|
className="w-8 h-8"
|
||||||
|
variant={currentPageIndex === pageIndex ? 'default' : 'outline'}
|
||||||
|
>
|
||||||
|
{pageIndex}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(currentPageIndex+1)}
|
||||||
|
disabled={currentPageIndex === totalPage}
|
||||||
|
className="w-8 h-8"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handlePageChange(totalPage)}
|
||||||
|
disabled={currentPageIndex === totalPage}
|
||||||
|
className="w-8 h-8"
|
||||||
|
>
|
||||||
|
<ChevronsRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LandingPagination;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
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 { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
@ -15,10 +15,10 @@ import {
|
||||||
FaLink,
|
FaLink,
|
||||||
FaShareAlt,
|
FaShareAlt,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
import { getDetail } from "@/service/landing/landing";
|
||||||
import VideoPlayer from "@/utils/video-player";
|
|
||||||
import { toBase64, shimmer } from "@/utils/globals";
|
import { toBase64, shimmer } from "@/utils/globals";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { getArticleDetail } from "@/service/content/content";
|
||||||
|
|
||||||
export default function ImageDetail({ id }: { id: string }) {
|
export default function ImageDetail({ id }: { id: string }) {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
@ -28,21 +28,22 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
const [selectedImage, setSelectedImage] = useState(0);
|
const [selectedImage, setSelectedImage] = useState(0);
|
||||||
const [isLoading, setIsLoading] = useState<any>(true);
|
const [isLoading, setIsLoading] = useState<any>(true);
|
||||||
|
|
||||||
|
// animasi skeleton loading
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// salin link ke clipboard
|
||||||
const handleCopyLink = async () => {
|
const handleCopyLink = async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(window.location.href);
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to copy: ", err);
|
console.error("Gagal menyalin:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -59,82 +60,81 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🔹 Ambil data detail dari API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchDetail = async () => {
|
const fetchDetail = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
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
|
// 1️⃣ Coba ambil dari API baru /articles/{id}
|
||||||
const articleData = response?.data?.data;
|
const res = await getArticleDetail(id);
|
||||||
if (articleData) {
|
console.log("Response dari /articles/{id}:", res);
|
||||||
// Transform article data to match old structure for backward compatibility
|
|
||||||
const transformedData = {
|
// if (res?.error || !res?.data?.data) {
|
||||||
id: articleData.id,
|
// console.warn("Gagal ambil dari API baru, coba fallback ke API lama");
|
||||||
title: articleData.title,
|
// const fallback = await getArticleDetail(id);
|
||||||
description: articleData.description,
|
// setData(fallback?.data?.data);
|
||||||
createdAt: articleData.createdAt,
|
// return;
|
||||||
clickCount: articleData.viewCount,
|
// }
|
||||||
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
|
||||||
uploadedBy: {
|
// 2️⃣ Transformasi struktur agar kompatibel dengan UI lama
|
||||||
publisher: articleData.createdByName || "MABES POLRI"
|
const article = res?.data?.data;
|
||||||
},
|
const mappedData = {
|
||||||
files: articleData.files?.map((file: any) => ({
|
id: article.id,
|
||||||
id: file.id,
|
title: article.title,
|
||||||
url: file.file_url,
|
description: article.description,
|
||||||
fileName: file.file_name,
|
createdAt: article.createdAt,
|
||||||
filePath: file.file_path,
|
clickCount: article.viewCount,
|
||||||
fileThumbnail: file.file_thumbnail,
|
creatorGroupLevelName: article.createdByName || "Unknown",
|
||||||
fileAlt: file.file_alt,
|
thumbnailUrl: article.thumbnailUrl || "", // ✅ tambahkan ini
|
||||||
widthPixel: file.width_pixel,
|
uploadedBy: {
|
||||||
heightPixel: file.height_pixel,
|
publisher: article.createdByName || "MABES POLRI",
|
||||||
size: file.size,
|
},
|
||||||
downloadCount: file.download_count,
|
files:
|
||||||
createdAt: file.created_at,
|
article.files?.map((f: any) => ({
|
||||||
updatedAt: file.updated_at,
|
id: f.id,
|
||||||
...file
|
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(mappedData);
|
||||||
|
} catch (err) {
|
||||||
setData(transformedData);
|
console.error("Gagal ambil detail artikel:", err);
|
||||||
}
|
// fallback ke API lama
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching detail:", error);
|
|
||||||
// Try fallback to old API if new API fails
|
|
||||||
try {
|
try {
|
||||||
const fallbackResponse = await getDetail(id);
|
const fallback = await getDetail(id);
|
||||||
setData(fallbackResponse?.data?.data);
|
setData(fallback?.data?.data);
|
||||||
} catch (fallbackError) {
|
} catch (err2) {
|
||||||
console.error("Fallback API also failed:", fallbackError);
|
console.error("Fallback API juga gagal:", err2);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id) fetchDetail();
|
if (id) fetchDetail();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
// 🔹 UI Loading
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||||
<p>Loading...</p>
|
<p>Memuat data...</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔹 Jika data kosong
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||||
|
|
@ -143,8 +143,10 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔹 Render konten utama
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||||
|
{/* Gambar Utama */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Skeleton className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-[900px]" />
|
<Skeleton className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-[900px]" />
|
||||||
|
|
@ -157,16 +159,18 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
)}`}
|
)}`}
|
||||||
width={2560}
|
width={2560}
|
||||||
height={1440}
|
height={1440}
|
||||||
src={data?.files[selectedImage]?.url || "/nodata.png"}
|
src={
|
||||||
|
data?.files?.[selectedImage]?.url ||
|
||||||
|
data?.thumbnailUrl || // ✅ fallback ke thumbnailUrl
|
||||||
|
"/nodata.png"
|
||||||
|
}
|
||||||
alt="Main"
|
alt="Main"
|
||||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="absolute top-4 right-4"></div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Gambar bawah Kecil */}
|
{/* Thumbnail bawah */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="py-4 flex flex-row gap-3">
|
<div className="py-4 flex flex-row gap-3">
|
||||||
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||||
|
|
@ -174,22 +178,6 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
<Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// <div className="py-4 flex flex-row gap-3">
|
|
||||||
// {detailDataImage?.files?.map((file: any, index: number) => (
|
|
||||||
// <a onClick={() => setSelectedImage(index)} key={file?.id}>
|
|
||||||
// <Image
|
|
||||||
// placeholder={`data:image/svg+xml;base64,${toBase64(
|
|
||||||
// shimmer(700, 475)
|
|
||||||
// )}`}
|
|
||||||
// width={1920}
|
|
||||||
// height={1080}
|
|
||||||
// alt="image-small"
|
|
||||||
// src={file?.url}
|
|
||||||
// className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600"
|
|
||||||
// />
|
|
||||||
// </a>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
<div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||||
{data?.files?.map((file: any, index: number) => (
|
{data?.files?.map((file: any, index: number) => (
|
||||||
<a onClick={() => setSelectedImage(index)} key={file?.id}>
|
<a onClick={() => setSelectedImage(index)} key={file?.id}>
|
||||||
|
|
@ -210,11 +198,12 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Informasi artikel */}
|
||||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<span className="font-semibold text-black border-r-2 pr-2 border-black">
|
{/* <span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||||
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||||
</span>
|
</span> */}
|
||||||
<span className="flex items-center gap-1 text-black">
|
<span className="flex items-center gap-1 text-black">
|
||||||
<Calendar className="w-4 h-4" />
|
<Calendar className="w-4 h-4" />
|
||||||
{new Date(data.createdAt)
|
{new Date(data.createdAt)
|
||||||
|
|
@ -228,92 +217,28 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
timeZone: "Asia/Jakarta",
|
timeZone: "Asia/Jakarta",
|
||||||
})
|
})
|
||||||
.replace(".", ":")}{" "}
|
.replace(".", ":")}{" "}
|
||||||
WIB
|
WIB |
|
||||||
</span>
|
</span>
|
||||||
{/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
|
||||||
<Clock className="w-4 h-4" />
|
|
||||||
{data.time || "-"}
|
|
||||||
</span> */}
|
|
||||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
{data.clickCount || 0}
|
{data.viewCount || 0}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-black">
|
<span className="text-black">
|
||||||
{" "}
|
|
||||||
Creator: {data.creatorGroupLevelName}
|
Creator: {data.creatorGroupLevelName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Konten artikel */}
|
||||||
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
<div className="flex flex-col md:flex-row gap-6 mt-6">
|
||||||
{/* Sidebar actions */}
|
|
||||||
{/* <div className="hidden md:flex flex-col gap-4 relative z-10">
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Button
|
|
||||||
onClick={handleCopyLink}
|
|
||||||
size="lg"
|
|
||||||
className="justify-start bg-black text-white rounded-full"
|
|
||||||
>
|
|
||||||
{copied ? <FaCheck /> : <FaLink />}
|
|
||||||
</Button>
|
|
||||||
<span>COPY LINK</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-2 items-center relative">
|
|
||||||
<Button
|
|
||||||
onClick={() => setShowShareMenu(!showShareMenu)}
|
|
||||||
size="lg"
|
|
||||||
className="justify-start bg-[#C6A455] text-white rounded-full"
|
|
||||||
>
|
|
||||||
<FaShareAlt />
|
|
||||||
</Button>
|
|
||||||
<span>SHARE</span>
|
|
||||||
|
|
||||||
{showShareMenu && (
|
|
||||||
<div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48">
|
|
||||||
<SocialItem icon={<FaFacebookF />} label="Facebook" />
|
|
||||||
<SocialItem icon={<FaTiktok />} label="TikTok" />
|
|
||||||
<SocialItem icon={<FaYoutube />} label="YouTube" />
|
|
||||||
<SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
|
||||||
<SocialItem icon={<FaInstagram />} label="Instagram" />
|
|
||||||
<SocialItem icon={<FaTwitter />} label="Twitter" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<Link href={`/content/video/comment/${id}`}>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="15"
|
|
||||||
height="15"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Button>
|
|
||||||
COMMENT
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="flex-1 space-y-4">
|
<div className="flex-1 space-y-4">
|
||||||
<h1 className="text-xl font-bold">{data.title}</h1>
|
<h1 className="text-xl font-bold">{data.title}</h1>
|
||||||
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
<div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||||
<p>{data.description}</p>
|
<p>{data.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions bawah */}
|
{/* Tombol aksi bawah */}
|
||||||
<div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
<div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
|
|
@ -341,17 +266,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
size="lg"
|
size="lg"
|
||||||
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||||
>
|
>
|
||||||
<svg
|
💬
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="15"
|
|
||||||
height="15"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Button>
|
</Button>
|
||||||
COMMENT
|
COMMENT
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -362,3 +277,368 @@ export default function ImageDetail({ id }: { id: string }) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "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<any>(null);
|
||||||
|
// const [loading, setLoading] = useState(true);
|
||||||
|
// const [selectedImage, setSelectedImage] = useState(0);
|
||||||
|
// const [isLoading, setIsLoading] = useState<any>(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;
|
||||||
|
// }) => (
|
||||||
|
// <div className="flex items-center gap-3 cursor-pointer hover:opacity-80">
|
||||||
|
// <div className="bg-[#C6A455] p-2 rounded-full text-white">{icon}</div>
|
||||||
|
// <span className="text-sm">{label}</span>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
// <div className="max-w-6xl mx-auto px-4 py-6">
|
||||||
|
// <p>Loading...</p>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!data) {
|
||||||
|
// return (
|
||||||
|
// <div className="max-w-6xl mx-auto px-4 py-6">
|
||||||
|
// <p>Data tidak ditemukan</p>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||||
|
// {isLoading ? (
|
||||||
|
// <div className="relative">
|
||||||
|
// <Skeleton className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-[900px]" />
|
||||||
|
// </div>
|
||||||
|
// ) : (
|
||||||
|
// <div className="relative">
|
||||||
|
// <Image
|
||||||
|
// placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||||
|
// shimmer(700, 475)
|
||||||
|
// )}`}
|
||||||
|
// width={2560}
|
||||||
|
// height={1440}
|
||||||
|
// src={data?.files[selectedImage]?.url || "/nodata.png"}
|
||||||
|
// alt="Main"
|
||||||
|
// className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
||||||
|
// />
|
||||||
|
|
||||||
|
// <div className="absolute top-4 right-4"></div>
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
|
||||||
|
// {/* Gambar bawah Kecil */}
|
||||||
|
// {isLoading ? (
|
||||||
|
// <div className="py-4 flex flex-row gap-3">
|
||||||
|
// <Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||||
|
// <Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||||
|
// <Skeleton className="rounded-lg w-[120px] h-[90px]" />
|
||||||
|
// </div>
|
||||||
|
// ) : (
|
||||||
|
// // <div className="py-4 flex flex-row gap-3">
|
||||||
|
// // {detailDataImage?.files?.map((file: any, index: number) => (
|
||||||
|
// // <a onClick={() => setSelectedImage(index)} key={file?.id}>
|
||||||
|
// // <Image
|
||||||
|
// // placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||||
|
// // shimmer(700, 475)
|
||||||
|
// // )}`}
|
||||||
|
// // width={1920}
|
||||||
|
// // height={1080}
|
||||||
|
// // alt="image-small"
|
||||||
|
// // src={file?.url}
|
||||||
|
// // className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600"
|
||||||
|
// // />
|
||||||
|
// // </a>
|
||||||
|
// // ))}
|
||||||
|
// // </div>
|
||||||
|
// <div className="py-4 px-1 flex flex-row gap-3 flex-wrap">
|
||||||
|
// {data?.files?.map((file: any, index: number) => (
|
||||||
|
// <a onClick={() => setSelectedImage(index)} key={file?.id}>
|
||||||
|
// <Image
|
||||||
|
// placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||||
|
// shimmer(700, 475)
|
||||||
|
// )}`}
|
||||||
|
// width={1920}
|
||||||
|
// height={1080}
|
||||||
|
// alt="image-small"
|
||||||
|
// src={file?.url}
|
||||||
|
// className={`w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 ${
|
||||||
|
// selectedImage === index ? "ring-2 ring-red-600" : ""
|
||||||
|
// }`}
|
||||||
|
// />
|
||||||
|
// </a>
|
||||||
|
// ))}
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
|
||||||
|
// <div className="flex flex-col md:flex-row md:items-center md:justify-between text-sm text-muted-foreground">
|
||||||
|
// <div className="flex flex-wrap items-center gap-2">
|
||||||
|
// <span className="font-semibold text-black border-r-2 pr-2 border-black">
|
||||||
|
// by {data?.uploadedBy?.publisher || "MABES POLRI"}
|
||||||
|
// </span>
|
||||||
|
// <span className="flex items-center gap-1 text-black">
|
||||||
|
// <Calendar className="w-4 h-4" />
|
||||||
|
// {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
|
||||||
|
// </span>
|
||||||
|
// {/* <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||||
|
// <Clock className="w-4 h-4" />
|
||||||
|
// {data.time || "-"}
|
||||||
|
// </span> */}
|
||||||
|
// <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||||
|
// <Eye className="w-4 h-4" />
|
||||||
|
// {data.clickCount || 0}
|
||||||
|
// </span>
|
||||||
|
// <span className="text-black">
|
||||||
|
// {" "}
|
||||||
|
// Creator: {data.creatorGroupLevelName}
|
||||||
|
// </span>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="flex flex-col md:flex-row gap-6 mt-6">
|
||||||
|
// {/* Sidebar actions */}
|
||||||
|
// {/* <div className="hidden md:flex flex-col gap-4 relative z-10">
|
||||||
|
// <div className="flex gap-2 items-center">
|
||||||
|
// <Button
|
||||||
|
// onClick={handleCopyLink}
|
||||||
|
// size="lg"
|
||||||
|
// className="justify-start bg-black text-white rounded-full"
|
||||||
|
// >
|
||||||
|
// {copied ? <FaCheck /> : <FaLink />}
|
||||||
|
// </Button>
|
||||||
|
// <span>COPY LINK</span>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="flex gap-2 items-center relative">
|
||||||
|
// <Button
|
||||||
|
// onClick={() => setShowShareMenu(!showShareMenu)}
|
||||||
|
// size="lg"
|
||||||
|
// className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||||
|
// >
|
||||||
|
// <FaShareAlt />
|
||||||
|
// </Button>
|
||||||
|
// <span>SHARE</span>
|
||||||
|
|
||||||
|
// {showShareMenu && (
|
||||||
|
// <div className="absolute left-16 top-0 bg-white p-4 rounded-lg shadow-lg flex flex-col gap-3 w-48">
|
||||||
|
// <SocialItem icon={<FaFacebookF />} label="Facebook" />
|
||||||
|
// <SocialItem icon={<FaTiktok />} label="TikTok" />
|
||||||
|
// <SocialItem icon={<FaYoutube />} label="YouTube" />
|
||||||
|
// <SocialItem icon={<FaWhatsapp />} label="WhatsApp" />
|
||||||
|
// <SocialItem icon={<FaInstagram />} label="Instagram" />
|
||||||
|
// <SocialItem icon={<FaTwitter />} label="Twitter" />
|
||||||
|
// </div>
|
||||||
|
// )}
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="flex gap-2 items-center">
|
||||||
|
// <Link href={`/content/video/comment/${id}`}>
|
||||||
|
// <Button
|
||||||
|
// variant="default"
|
||||||
|
// size="lg"
|
||||||
|
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||||
|
// >
|
||||||
|
// <svg
|
||||||
|
// xmlns="http://www.w3.org/2000/svg"
|
||||||
|
// width="15"
|
||||||
|
// height="15"
|
||||||
|
// viewBox="0 0 24 24"
|
||||||
|
// >
|
||||||
|
// <path
|
||||||
|
// fill="currentColor"
|
||||||
|
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||||
|
// />
|
||||||
|
// </svg>
|
||||||
|
// </Button>
|
||||||
|
// COMMENT
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// </div> */}
|
||||||
|
|
||||||
|
// {/* Content */}
|
||||||
|
// <div className="flex-1 space-y-4">
|
||||||
|
// <h1 className="text-xl font-bold">{data.title}</h1>
|
||||||
|
// <div className="text-base text-gray-700 leading-relaxed space-y-3">
|
||||||
|
// <p>{data.description}</p>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Actions bawah */}
|
||||||
|
// <div className="flex flex-wrap md:flex-row justify-center gap-4 my-20">
|
||||||
|
// <div className="flex gap-2 items-center">
|
||||||
|
// <Button
|
||||||
|
// onClick={handleCopyLink}
|
||||||
|
// size="lg"
|
||||||
|
// className="justify-start bg-black text-white rounded-full"
|
||||||
|
// >
|
||||||
|
// {copied ? <FaCheck /> : <FaLink />}
|
||||||
|
// </Button>
|
||||||
|
// <span>COPY LINK</span>
|
||||||
|
// </div>
|
||||||
|
// <div className="flex gap-2 items-center">
|
||||||
|
// <Button
|
||||||
|
// onClick={() => setShowShareMenu(!showShareMenu)}
|
||||||
|
// size="lg"
|
||||||
|
// className="justify-start bg-[#C6A455] text-white rounded-full"
|
||||||
|
// >
|
||||||
|
// <FaShareAlt />
|
||||||
|
// </Button>
|
||||||
|
// <span>SHARE</span>
|
||||||
|
// </div>
|
||||||
|
// <div className="flex gap-2 items-center">
|
||||||
|
// <Link href={`/content/video/comment/${id}`}>
|
||||||
|
// <Button
|
||||||
|
// variant="default"
|
||||||
|
// size="lg"
|
||||||
|
// className="justify-start bg-[#FFAD10] rounded-full mr-2 text-white"
|
||||||
|
// >
|
||||||
|
// <svg
|
||||||
|
// xmlns="http://www.w3.org/2000/svg"
|
||||||
|
// width="15"
|
||||||
|
// height="15"
|
||||||
|
// viewBox="0 0 24 24"
|
||||||
|
// >
|
||||||
|
// <path
|
||||||
|
// fill="currentColor"
|
||||||
|
// d="M10 3h4a8 8 0 1 1 0 16v3.5c-5-2-12-5-12-11.5a8 8 0 0 1 8-8"
|
||||||
|
// />
|
||||||
|
// </svg>
|
||||||
|
// </Button>
|
||||||
|
// COMMENT
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -578,7 +578,7 @@ export interface ArticleDetailResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to fetch article detail
|
// Function to fetch article detail
|
||||||
export async function getArticleDetail(id: number) {
|
export async function getArticleDetail(id: string) {
|
||||||
const url = `articles/${id}`;
|
const url = `articles/${id}`;
|
||||||
return await httpGetInterceptor(url);
|
return await httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ export async function getDetail(slug: string) {
|
||||||
return await httpGetInterceptor(`media/public?slug=${slug}&state=mabes`);
|
return await httpGetInterceptor(`media/public?slug=${slug}&state=mabes`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getDetailMetaData(slug: string) {
|
export async function getDetailMetaData(slug: string) {
|
||||||
return await httpGetInterceptorForMetadata(
|
return await httpGetInterceptorForMetadata(
|
||||||
`media/public?slug=${slug}&state=mabes&isSummary=true`
|
`media/public?slug=${slug}&state=mabes&isSummary=true`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue