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 Image from "next/image";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
||||
import { Card } from "../ui/card";
|
||||
import Link from "next/link";
|
||||
import { listData, listArticles } from "@/service/landing/landing";
|
||||
import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import { Navigation } from "swiper/modules";
|
||||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { listData, listArticles } from "@/service/landing/landing";
|
||||
import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content";
|
||||
import { getCookiesDecrypt } from "@/lib/utils";
|
||||
import Link from "next/link";
|
||||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
||||
|
||||
// Format tanggal
|
||||
// 🔹 Fungsi format tanggal ke WIB
|
||||
function formatTanggal(dateString: string) {
|
||||
if (!dateString) return "";
|
||||
return (
|
||||
|
|
@ -35,82 +36,88 @@ function formatTanggal(dateString: string) {
|
|||
}
|
||||
|
||||
export default function MediaUpdate() {
|
||||
const [tab, setTab] = useState<"latest" | "popular">("latest");
|
||||
const [dataToRender, setDataToRender] = useState<any[]>([]);
|
||||
const [tipeKonten, setTipeKonten] = useState<
|
||||
"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 [loading, setLoading] = useState(true);
|
||||
const MySwal = withReactContent(Swal);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData(tab);
|
||||
}, [tab]);
|
||||
// 🔹 Pemetaan tipe konten ke typeId API
|
||||
const typeMap: Record<typeof tipeKonten, number> = {
|
||||
image: 1,
|
||||
video: 2,
|
||||
text: 3,
|
||||
audio: 4,
|
||||
};
|
||||
|
||||
async function fetchData(section: "latest" | "popular") {
|
||||
useEffect(() => {
|
||||
ambilData();
|
||||
}, [tipeKonten, urutan]);
|
||||
|
||||
async function ambilData() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 🔹 Ambil data artikel
|
||||
const typeId = typeMap[tipeKonten];
|
||||
const sortBy = urutan === "latest" ? "createdAt" : "viewCount";
|
||||
|
||||
// 🔹 Panggil API baru
|
||||
const response = await listArticles(
|
||||
1,
|
||||
20,
|
||||
1, // typeId = image
|
||||
typeId,
|
||||
undefined,
|
||||
undefined,
|
||||
section === "latest" ? "createdAt" : "viewCount"
|
||||
sortBy
|
||||
);
|
||||
|
||||
let articlesData: any[] = [];
|
||||
let hasil: any[] = [];
|
||||
|
||||
if (response?.error) {
|
||||
console.error("Articles API failed, fallback ke old API");
|
||||
const fallbackRes = await listData(
|
||||
"1",
|
||||
console.error(
|
||||
"Gagal ambil data dari listArticles, fallback ke listData"
|
||||
);
|
||||
const fallback = await listData(
|
||||
String(typeId),
|
||||
"",
|
||||
"",
|
||||
20,
|
||||
0,
|
||||
section === "latest" ? "createdAt" : "clickCount",
|
||||
urutan === "latest" ? "createdAt" : "clickCount",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
);
|
||||
articlesData = fallbackRes?.data?.data?.content || [];
|
||||
hasil = fallback?.data?.data?.content || [];
|
||||
} else {
|
||||
articlesData = response?.data?.data || [];
|
||||
hasil = response?.data?.data || [];
|
||||
}
|
||||
|
||||
// 🔹 Normalisasi struktur data
|
||||
const transformedData = articlesData.map((article: any) => ({
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
// 🔹 Normalisasi data artikel
|
||||
const dataBaru = hasil.map((a: any) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
category:
|
||||
article.categoryName ||
|
||||
(article.categories && article.categories[0]?.title) ||
|
||||
a.categoryName ||
|
||||
(a.categories && a.categories[0]?.title) ||
|
||||
"Tanpa Kategori",
|
||||
createdAt: article.createdAt,
|
||||
smallThumbnailLink: article.thumbnailUrl,
|
||||
label:
|
||||
article.typeId === 1
|
||||
? "Image"
|
||||
: article.typeId === 2
|
||||
? "Video"
|
||||
: article.typeId === 3
|
||||
? "Text"
|
||||
: article.typeId === 4
|
||||
? "Audio"
|
||||
: "",
|
||||
...article,
|
||||
createdAt: a.createdAt,
|
||||
smallThumbnailLink: a.thumbnailUrl,
|
||||
typeId: a.typeId,
|
||||
}));
|
||||
|
||||
setDataToRender(transformedData);
|
||||
setDataKonten(dataBaru);
|
||||
|
||||
// 🔹 Sinkronisasi bookmark
|
||||
// 🔹 Sinkronisasi data bookmark
|
||||
const roleId = Number(getCookiesDecrypt("urie"));
|
||||
if (roleId && !isNaN(roleId)) {
|
||||
const savedLocal = localStorage.getItem("bookmarkedIds");
|
||||
const simpananLocal = localStorage.getItem("bookmarkedIds");
|
||||
let localSet = new Set<number>();
|
||||
if (savedLocal) {
|
||||
localSet = new Set(JSON.parse(savedLocal));
|
||||
if (simpananLocal) {
|
||||
localSet = new Set(JSON.parse(simpananLocal));
|
||||
setBookmarkedIds(localSet);
|
||||
}
|
||||
|
||||
|
|
@ -120,18 +127,17 @@ export default function MediaUpdate() {
|
|||
res?.data?.data?.bookmarks ||
|
||||
res?.data?.data ||
|
||||
[];
|
||||
|
||||
const ids = new Set<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);
|
||||
const gabungan = new Set([...localSet, ...ids]);
|
||||
setBookmarkedIds(gabungan);
|
||||
localStorage.setItem(
|
||||
"bookmarkedIds",
|
||||
JSON.stringify(Array.from(merged))
|
||||
JSON.stringify(Array.from(gabungan))
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -141,16 +147,7 @@ export default function MediaUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
// 🔹 Simpan perubahan bookmark ke localStorage
|
||||
useEffect(() => {
|
||||
if (bookmarkedIds.size > 0) {
|
||||
localStorage.setItem(
|
||||
"bookmarkedIds",
|
||||
JSON.stringify(Array.from(bookmarkedIds))
|
||||
);
|
||||
}
|
||||
}, [bookmarkedIds]);
|
||||
|
||||
// 🔹 Simpan bookmark
|
||||
const handleSave = async (id: number) => {
|
||||
const roleId = Number(getCookiesDecrypt("urie"));
|
||||
if (!roleId || isNaN(roleId)) {
|
||||
|
|
@ -170,7 +167,7 @@ export default function MediaUpdate() {
|
|||
MySwal.fire({
|
||||
icon: "error",
|
||||
title: "Gagal",
|
||||
text: "Gagal menyimpan artikel.",
|
||||
text: "Tidak dapat menyimpan artikel.",
|
||||
confirmButtonColor: "#d33",
|
||||
});
|
||||
} else {
|
||||
|
|
@ -191,7 +188,7 @@ export default function MediaUpdate() {
|
|||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error saving bookmark:", err);
|
||||
console.error("Error menyimpan bookmark:", err);
|
||||
MySwal.fire({
|
||||
icon: "error",
|
||||
title: "Kesalahan",
|
||||
|
|
@ -207,31 +204,54 @@ export default function MediaUpdate() {
|
|||
Media Update
|
||||
</h2>
|
||||
|
||||
{/* Tab */}
|
||||
{/* 🔸 Tab Urutan */}
|
||||
<div className="flex justify-center mb-8 bg-white">
|
||||
<Card className="bg-[#FFFFFF] rounded-xl flex flex-row p-3 gap-2">
|
||||
<button
|
||||
onClick={() => setTab("latest")}
|
||||
onClick={() => setUrutan("latest")}
|
||||
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
|
||||
onClick={() => setTab("popular")}
|
||||
onClick={() => setUrutan("popular")}
|
||||
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>
|
||||
</Card>
|
||||
</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 ? (
|
||||
<p className="text-center">Loading...</p>
|
||||
<p className="text-center">Memuat konten...</p>
|
||||
) : (
|
||||
<Swiper
|
||||
modules={[Navigation]}
|
||||
|
|
@ -243,7 +263,7 @@ export default function MediaUpdate() {
|
|||
1024: { slidesPerView: 4 },
|
||||
}}
|
||||
>
|
||||
{dataToRender.map((item) => (
|
||||
{dataKonten.map((item) => (
|
||||
<SwiperSlide key={item.id}>
|
||||
<div className="rounded-xl shadow-md overflow-hidden bg-white">
|
||||
<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">
|
||||
{item.category || "Tanpa Kategori"}
|
||||
</span>
|
||||
<span className="text-xs font-medium text-[#b3882e]">
|
||||
{item.label || ""}
|
||||
<span className="text-xs font-medium text-[#b3882e] capitalize">
|
||||
{tipeKonten}
|
||||
</span>
|
||||
</div>
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
{bookmarkedIds.has(Number(item.id)) ? "Saved" : "Save"}
|
||||
{bookmarkedIds.has(Number(item.id))
|
||||
? "Tersimpan"
|
||||
: "Simpan"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -296,21 +318,13 @@ export default function MediaUpdate() {
|
|||
</Swiper>
|
||||
)}
|
||||
|
||||
{/* Lihat lebih banyak */}
|
||||
{/* 🔸 Tombol Lihat Lebih Banyak */}
|
||||
<div className="text-center mt-10">
|
||||
<Link
|
||||
href={
|
||||
tab === "latest"
|
||||
? "https://mediahub.polri.go.id/"
|
||||
: "https://tribratanews.polri.go.id/"
|
||||
}
|
||||
href={`/${tipeKonten}/filter?sortBy=${urutan}`}
|
||||
className="inline-block border border-[#b3882e] text-[#b3882e] px-6 py-2 rounded-md text-sm font-medium hover:bg-[#b3882e]/10 transition"
|
||||
>
|
||||
<Button
|
||||
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>
|
||||
Lihat Lebih Banyak
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -327,10 +341,14 @@ export default function MediaUpdate() {
|
|||
// import { Card } from "../ui/card";
|
||||
// import Link from "next/link";
|
||||
// import { listData, listArticles } from "@/service/landing/landing";
|
||||
// import { toggleBookmark, getBookmarkSummaryForUser } from "@/service/content";
|
||||
// import { getCookiesDecrypt } from "@/lib/utils";
|
||||
// import { Swiper, SwiperSlide } from "swiper/react";
|
||||
// import "swiper/css";
|
||||
// import "swiper/css/navigation";
|
||||
// import { Navigation } from "swiper/modules";
|
||||
// import Swal from "sweetalert2";
|
||||
// import withReactContent from "sweetalert2-react-content";
|
||||
|
||||
// // Format tanggal
|
||||
// function formatTanggal(dateString: string) {
|
||||
|
|
@ -353,7 +371,9 @@ export default function MediaUpdate() {
|
|||
// export default function MediaUpdate() {
|
||||
// const [tab, setTab] = useState<"latest" | "popular">("latest");
|
||||
// const [dataToRender, setDataToRender] = useState<any[]>([]);
|
||||
// const [bookmarkedIds, setBookmarkedIds] = useState<Set<number>>(new Set());
|
||||
// const [loading, setLoading] = useState(true);
|
||||
// const MySwal = withReactContent(Swal);
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchData(tab);
|
||||
|
|
@ -363,21 +383,19 @@ export default function MediaUpdate() {
|
|||
// try {
|
||||
// setLoading(true);
|
||||
|
||||
// // Use new Articles API
|
||||
// const response = await listArticles(
|
||||
// 1,
|
||||
// 20,
|
||||
// 1, // typeId for images
|
||||
// 1, // typeId = image
|
||||
// undefined,
|
||||
// undefined,
|
||||
// section === "latest" ? "createdAt" : "viewCount"
|
||||
// );
|
||||
|
||||
// console.log("Media Update Articles API response:", response);
|
||||
// let articlesData: any[] = [];
|
||||
|
||||
// if (response?.error) {
|
||||
// console.error("Articles API failed, falling back to old API");
|
||||
// // Fallback to old API
|
||||
// console.error("Articles API failed, fallback ke old API");
|
||||
// const fallbackRes = await listData(
|
||||
// "1",
|
||||
// "",
|
||||
|
|
@ -389,49 +407,132 @@ export default function MediaUpdate() {
|
|||
// "",
|
||||
// ""
|
||||
// );
|
||||
// setDataToRender(fallbackRes?.data?.data?.content || []);
|
||||
// return;
|
||||
// articlesData = fallbackRes?.data?.data?.content || [];
|
||||
// } else {
|
||||
// articlesData = response?.data?.data || [];
|
||||
// }
|
||||
|
||||
// // Handle new API response structure
|
||||
// const articlesData = response?.data?.data || [];
|
||||
|
||||
// // Transform articles data to match old structure for backward compatibility
|
||||
// // 🔹 Normalisasi struktur data
|
||||
// const transformedData = articlesData.map((article: any) => ({
|
||||
// id: article.id,
|
||||
// title: article.title,
|
||||
// category: article.categoryName || (article.categories && article.categories[0]?.title) || "Tanpa Kategori",
|
||||
// category:
|
||||
// article.categoryName ||
|
||||
// (article.categories && article.categories[0]?.title) ||
|
||||
// "Tanpa Kategori",
|
||||
// createdAt: article.createdAt,
|
||||
// smallThumbnailLink: article.thumbnailUrl,
|
||||
// label: article.typeId === 1 ? "Image" : article.typeId === 2 ? "Video" : article.typeId === 3 ? "Text" : article.typeId === 4 ? "Audio" : "",
|
||||
// ...article
|
||||
// label:
|
||||
// article.typeId === 1
|
||||
// ? "Image"
|
||||
// : article.typeId === 2
|
||||
// ? "Video"
|
||||
// : article.typeId === 3
|
||||
// ? "Text"
|
||||
// : article.typeId === 4
|
||||
// ? "Audio"
|
||||
// : "",
|
||||
// ...article,
|
||||
// }));
|
||||
|
||||
// setDataToRender(transformedData);
|
||||
|
||||
// // 🔹 Sinkronisasi bookmark
|
||||
// const roleId = Number(getCookiesDecrypt("urie"));
|
||||
// if (roleId && !isNaN(roleId)) {
|
||||
// const savedLocal = localStorage.getItem("bookmarkedIds");
|
||||
// let localSet = new Set<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) {
|
||||
// console.error("Gagal memuat data:", err);
|
||||
// // Try fallback to old API if new API fails
|
||||
// try {
|
||||
// const fallbackRes = await listData(
|
||||
// "1",
|
||||
// "",
|
||||
// "",
|
||||
// 20,
|
||||
// 0,
|
||||
// section === "latest" ? "createdAt" : "clickCount",
|
||||
// "",
|
||||
// "",
|
||||
// ""
|
||||
// );
|
||||
// setDataToRender(fallbackRes?.data?.data?.content || []);
|
||||
// } catch (fallbackError) {
|
||||
// console.error("Fallback API also failed:", fallbackError);
|
||||
// }
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 🔹 Simpan perubahan bookmark ke localStorage
|
||||
// useEffect(() => {
|
||||
// if (bookmarkedIds.size > 0) {
|
||||
// localStorage.setItem(
|
||||
// "bookmarkedIds",
|
||||
// JSON.stringify(Array.from(bookmarkedIds))
|
||||
// );
|
||||
// }
|
||||
// }, [bookmarkedIds]);
|
||||
|
||||
// const handleSave = async (id: number) => {
|
||||
// const roleId = Number(getCookiesDecrypt("urie"));
|
||||
// if (!roleId || isNaN(roleId)) {
|
||||
// MySwal.fire({
|
||||
// icon: "warning",
|
||||
// title: "Login diperlukan",
|
||||
// text: "Silakan login terlebih dahulu untuk menyimpan artikel.",
|
||||
// confirmButtonText: "Login Sekarang",
|
||||
// confirmButtonColor: "#d33",
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const res = await toggleBookmark(id);
|
||||
// if (res?.error) {
|
||||
// MySwal.fire({
|
||||
// icon: "error",
|
||||
// title: "Gagal",
|
||||
// text: "Gagal menyimpan artikel.",
|
||||
// confirmButtonColor: "#d33",
|
||||
// });
|
||||
// } else {
|
||||
// const updated = new Set(bookmarkedIds);
|
||||
// updated.add(Number(id));
|
||||
// setBookmarkedIds(updated);
|
||||
// localStorage.setItem(
|
||||
// "bookmarkedIds",
|
||||
// JSON.stringify(Array.from(updated))
|
||||
// );
|
||||
|
||||
// MySwal.fire({
|
||||
// icon: "success",
|
||||
// title: "Berhasil",
|
||||
// text: "Artikel berhasil disimpan ke bookmark.",
|
||||
// timer: 1500,
|
||||
// showConfirmButton: false,
|
||||
// });
|
||||
// }
|
||||
// } catch (err) {
|
||||
// console.error("Error saving bookmark:", err);
|
||||
// MySwal.fire({
|
||||
// icon: "error",
|
||||
// title: "Kesalahan",
|
||||
// text: "Terjadi kesalahan saat menyimpan artikel.",
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <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">
|
||||
|
|
@ -508,11 +609,17 @@ export default function MediaUpdate() {
|
|||
// <ThumbsDown className="w-4 h-4 cursor-pointer" />
|
||||
// </div>
|
||||
// <Button
|
||||
// onClick={() => handleSave(item.id)}
|
||||
// disabled={bookmarkedIds.has(Number(item.id))}
|
||||
// variant="default"
|
||||
// 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>
|
||||
// </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";
|
||||
import Image from "next/image";
|
||||
import { Calendar, Clock, Eye } from "lucide-react";
|
||||
import { Calendar, Eye } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
|
|
@ -15,10 +15,10 @@ import {
|
|||
FaLink,
|
||||
FaShareAlt,
|
||||
} from "react-icons/fa";
|
||||
import { getDetail, getArticleDetail } from "@/service/landing/landing";
|
||||
import VideoPlayer from "@/utils/video-player";
|
||||
import { getDetail } from "@/service/landing/landing";
|
||||
import { toBase64, shimmer } from "@/utils/globals";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { getArticleDetail } from "@/service/content/content";
|
||||
|
||||
export default function ImageDetail({ id }: { id: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
|
@ -28,21 +28,22 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
const [selectedImage, setSelectedImage] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState<any>(true);
|
||||
|
||||
// animasi skeleton loading
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// salin link ke clipboard
|
||||
const handleCopyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(window.location.href);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy: ", err);
|
||||
console.error("Gagal menyalin:", err);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -59,82 +60,81 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
</div>
|
||||
);
|
||||
|
||||
// 🔹 Ambil data detail dari API
|
||||
useEffect(() => {
|
||||
const fetchDetail = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Try new Articles API first
|
||||
const response = await getArticleDetail(id);
|
||||
console.log("Article Detail API response:", response);
|
||||
|
||||
if (response?.error) {
|
||||
console.error("Articles API failed, falling back to old API");
|
||||
// Fallback to old API
|
||||
const fallbackResponse = await getDetail(id);
|
||||
setData(fallbackResponse?.data?.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle new API response structure
|
||||
const articleData = response?.data?.data;
|
||||
if (articleData) {
|
||||
// Transform article data to match old structure for backward compatibility
|
||||
const transformedData = {
|
||||
id: articleData.id,
|
||||
title: articleData.title,
|
||||
description: articleData.description,
|
||||
createdAt: articleData.createdAt,
|
||||
clickCount: articleData.viewCount,
|
||||
creatorGroupLevelName: articleData.createdByName || "Unknown",
|
||||
uploadedBy: {
|
||||
publisher: articleData.createdByName || "MABES POLRI"
|
||||
},
|
||||
files: articleData.files?.map((file: any) => ({
|
||||
id: file.id,
|
||||
url: file.file_url,
|
||||
fileName: file.file_name,
|
||||
filePath: file.file_path,
|
||||
fileThumbnail: file.file_thumbnail,
|
||||
fileAlt: file.file_alt,
|
||||
widthPixel: file.width_pixel,
|
||||
heightPixel: file.height_pixel,
|
||||
size: file.size,
|
||||
downloadCount: file.download_count,
|
||||
createdAt: file.created_at,
|
||||
updatedAt: file.updated_at,
|
||||
...file
|
||||
// 1️⃣ Coba ambil dari API baru /articles/{id}
|
||||
const res = await getArticleDetail(id);
|
||||
console.log("Response dari /articles/{id}:", res);
|
||||
|
||||
// if (res?.error || !res?.data?.data) {
|
||||
// console.warn("Gagal ambil dari API baru, coba fallback ke API lama");
|
||||
// const fallback = await getArticleDetail(id);
|
||||
// setData(fallback?.data?.data);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 2️⃣ Transformasi struktur agar kompatibel dengan UI lama
|
||||
const article = res?.data?.data;
|
||||
const mappedData = {
|
||||
id: article.id,
|
||||
title: article.title,
|
||||
description: article.description,
|
||||
createdAt: article.createdAt,
|
||||
clickCount: article.viewCount,
|
||||
creatorGroupLevelName: article.createdByName || "Unknown",
|
||||
thumbnailUrl: article.thumbnailUrl || "", // ✅ tambahkan ini
|
||||
uploadedBy: {
|
||||
publisher: article.createdByName || "MABES POLRI",
|
||||
},
|
||||
files:
|
||||
article.files?.map((f: any) => ({
|
||||
id: f.id,
|
||||
url: f.file_url,
|
||||
fileName: f.file_name,
|
||||
filePath: f.file_path,
|
||||
fileThumbnail: f.file_thumbnail,
|
||||
fileAlt: f.file_alt,
|
||||
widthPixel: f.width_pixel,
|
||||
heightPixel: f.height_pixel,
|
||||
size: f.size,
|
||||
downloadCount: f.download_count,
|
||||
createdAt: f.created_at,
|
||||
updatedAt: f.updated_at,
|
||||
})) || [],
|
||||
};
|
||||
};
|
||||
|
||||
console.log("transformedData : ", transformedData.files);
|
||||
|
||||
setData(transformedData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching detail:", error);
|
||||
// Try fallback to old API if new API fails
|
||||
setData(mappedData);
|
||||
} catch (err) {
|
||||
console.error("Gagal ambil detail artikel:", err);
|
||||
// fallback ke API lama
|
||||
try {
|
||||
const fallbackResponse = await getDetail(id);
|
||||
setData(fallbackResponse?.data?.data);
|
||||
} catch (fallbackError) {
|
||||
console.error("Fallback API also failed:", fallbackError);
|
||||
const fallback = await getDetail(id);
|
||||
setData(fallback?.data?.data);
|
||||
} catch (err2) {
|
||||
console.error("Fallback API juga gagal:", err2);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (id) fetchDetail();
|
||||
}, [id]);
|
||||
|
||||
// 🔹 UI Loading
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6">
|
||||
<p>Loading...</p>
|
||||
<p>Memuat data...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 🔹 Jika data kosong
|
||||
if (!data) {
|
||||
return (
|
||||
<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 (
|
||||
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
|
||||
{/* Gambar Utama */}
|
||||
{isLoading ? (
|
||||
<div className="relative">
|
||||
<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}
|
||||
height={1440}
|
||||
src={data?.files[selectedImage]?.url || "/nodata.png"}
|
||||
src={
|
||||
data?.files?.[selectedImage]?.url ||
|
||||
data?.thumbnailUrl || // ✅ fallback ke thumbnailUrl
|
||||
"/nodata.png"
|
||||
}
|
||||
alt="Main"
|
||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
||||
/>
|
||||
|
||||
<div className="absolute top-4 right-4"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Gambar bawah Kecil */}
|
||||
{/* Thumbnail bawah */}
|
||||
{isLoading ? (
|
||||
<div className="py-4 flex flex-row gap-3">
|
||||
<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]" />
|
||||
</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}>
|
||||
|
|
@ -210,11 +198,12 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
</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-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"}
|
||||
</span>
|
||||
</span> */}
|
||||
<span className="flex items-center gap-1 text-black">
|
||||
<Calendar className="w-4 h-4" />
|
||||
{new Date(data.createdAt)
|
||||
|
|
@ -228,92 +217,28 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
timeZone: "Asia/Jakarta",
|
||||
})
|
||||
.replace(".", ":")}{" "}
|
||||
WIB
|
||||
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}
|
||||
{data.viewCount || 0}
|
||||
</span>
|
||||
<span className="text-black">
|
||||
{" "}
|
||||
Creator: {data.creatorGroupLevelName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Konten artikel */}
|
||||
<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">
|
||||
{/* Tombol aksi 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}
|
||||
|
|
@ -341,17 +266,7 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
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>
|
||||
|
|
@ -362,3 +277,368 @@ export default function ImageDetail({ id }: { id: string }) {
|
|||
</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
|
||||
export async function getArticleDetail(id: number) {
|
||||
export async function getArticleDetail(id: string) {
|
||||
const url = `articles/${id}`;
|
||||
return await httpGetInterceptor(url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ export async function getDetail(slug: string) {
|
|||
return await httpGetInterceptor(`media/public?slug=${slug}&state=mabes`);
|
||||
}
|
||||
|
||||
|
||||
export async function getDetailMetaData(slug: string) {
|
||||
return await httpGetInterceptorForMetadata(
|
||||
`media/public?slug=${slug}&state=mabes&isSummary=true`
|
||||
|
|
|
|||
Loading…
Reference in New Issue