Merge branch 'main' of https://gitlab.com/hanifsalafi/mediahub_redesign into prod
This commit is contained in:
commit
16cbdcff45
|
|
@ -10,6 +10,7 @@ RUN npm install -g pnpm
|
||||||
# Membuat direktori aplikasi dan mengatur sebagai working directory
|
# Membuat direktori aplikasi dan mengatur sebagai working directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|
||||||
# Menyalin file penting terlebih dahulu untuk caching
|
# Menyalin file penting terlebih dahulu untuk caching
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,743 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
|
||||||
|
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
|
import {
|
||||||
|
getPublicCategoryData,
|
||||||
|
getUserLevelListByParent,
|
||||||
|
listCategory,
|
||||||
|
listData,
|
||||||
|
listDataAll,
|
||||||
|
listDataRegional,
|
||||||
|
} from "@/service/landing/landing";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
} 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 FilterImageComponent from "@/components/landing-page/filter-all/image-filter-card";
|
||||||
|
import FilterVideoComponent from "@/components/landing-page/filter-all/video-filter-card";
|
||||||
|
import FilterDocumentComponent from "@/components/landing-page/filter-all/document-filter-card";
|
||||||
|
import FilterAudioComponent from "@/components/landing-page/filter-all/audio-filter-card";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function FilterPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const asPath = usePathname();
|
||||||
|
const params = useParams();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const locale = params?.locale;
|
||||||
|
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 tag: any = searchParams?.get("tag");
|
||||||
|
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 [contentAll, setContentAll] = useState([]);
|
||||||
|
const t = useTranslations("FilterPage");
|
||||||
|
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||||
|
|
||||||
|
const [categoryPage, setCategoryPage] = useState(1);
|
||||||
|
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||||
|
const poldaName = params?.polda_name;
|
||||||
|
const satkerName = params?.satker_name;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const title = searchParams?.get("title") || "";
|
||||||
|
const category = searchParams?.get("category") || "";
|
||||||
|
const sortBy = searchParams?.get("sortBy") || "latest";
|
||||||
|
|
||||||
|
setSearchTitle(title);
|
||||||
|
setCategoryFilter(category ? category.split("&") : []);
|
||||||
|
setSortByOpt(sortBy === "popular" ? "clickCount" : "createdAt");
|
||||||
|
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(monthYearFilter, "monthFilter");
|
||||||
|
initState();
|
||||||
|
}, [
|
||||||
|
change,
|
||||||
|
monthYearFilter,
|
||||||
|
sortBy,
|
||||||
|
sortByOpt,
|
||||||
|
title,
|
||||||
|
startDateString,
|
||||||
|
endDateString,
|
||||||
|
categorie,
|
||||||
|
formatFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listCategory("1");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getData() {
|
||||||
|
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 listDataAll(
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
filter,
|
||||||
|
"",
|
||||||
|
tag,
|
||||||
|
filterGroup,
|
||||||
|
startDateString,
|
||||||
|
endDateString,
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)
|
||||||
|
?.split("/")[0]
|
||||||
|
?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
setContentAll(response?.data?.data?.content);
|
||||||
|
setTotalPage(response?.data?.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 listDataAll(
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
filter,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
tag,
|
||||||
|
startDateString,
|
||||||
|
endDateString,
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)
|
||||||
|
?.split("/")[0]
|
||||||
|
?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
setContentAll(response?.data?.data?.content);
|
||||||
|
setTotalPage(response?.data?.data?.totalPages);
|
||||||
|
setTotalContent(response?.data?.data?.totalElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||||
|
let filter = [...categoryFilter];
|
||||||
|
|
||||||
|
if (e) {
|
||||||
|
filter = [...categoryFilter, String(id)];
|
||||||
|
} else {
|
||||||
|
filter.splice(categoryFilter.indexOf(id), 1);
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
"",
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full max-w-screen overflow-x-hidden">
|
||||||
|
<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("content", { defaultValue: "Content" })}</p>
|
||||||
|
{">"}
|
||||||
|
<p>
|
||||||
|
<span className="font-bold">
|
||||||
|
{t("allContent", { defaultValue: "All Content" })}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className="font-bold">|</p>
|
||||||
|
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t(
|
||||||
|
"downloadableContent",
|
||||||
|
{ defaultValue: "Downloadable ContentS" }
|
||||||
|
)}`}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Left */}
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Konten Kanan */}
|
||||||
|
<div className="w-full pl-4 pr-4 lg:pr-16 pb-4 overflow-x-hidden">
|
||||||
|
<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>
|
||||||
|
<div className="flex flex-col gap-4 w-full">
|
||||||
|
<FilterImageComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
<FilterVideoComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
<FilterDocumentComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
<FilterAudioComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Footer from "@/components/landing-page/footer";
|
||||||
|
import Navbar from "@/components/landing-page/navbar";
|
||||||
|
|
||||||
|
const layout = async ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default layout;
|
||||||
|
|
@ -134,7 +134,7 @@ const DetailVideo = () => {
|
||||||
const handleDownload = () => {
|
const handleDownload = () => {
|
||||||
if (downloadProgress === 0) {
|
if (downloadProgress === 0) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
router.push("/auth/login");
|
router.push("/auth");
|
||||||
} else {
|
} else {
|
||||||
sendActivityLog(2);
|
sendActivityLog(2);
|
||||||
sendActivityLog(3);
|
sendActivityLog(3);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
|
import { getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
|
||||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
|
getPublicCategoryData,
|
||||||
getUserLevelListByParent,
|
getUserLevelListByParent,
|
||||||
listCategory,
|
listCategory,
|
||||||
listData,
|
listData,
|
||||||
|
|
@ -18,13 +19,7 @@ import {
|
||||||
PaginationState,
|
PaginationState,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
getCoreRowModel,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { Reveal } from "@/components/landing-page/Reveal";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import ReactDatePicker from "react-datepicker";
|
import ReactDatePicker from "react-datepicker";
|
||||||
|
|
@ -34,6 +29,7 @@ import FilterImageComponent from "@/components/landing-page/filter-all/image-fil
|
||||||
import FilterVideoComponent from "@/components/landing-page/filter-all/video-filter-card";
|
import FilterVideoComponent from "@/components/landing-page/filter-all/video-filter-card";
|
||||||
import FilterDocumentComponent from "@/components/landing-page/filter-all/document-filter-card";
|
import FilterDocumentComponent from "@/components/landing-page/filter-all/document-filter-card";
|
||||||
import FilterAudioComponent from "@/components/landing-page/filter-all/audio-filter-card";
|
import FilterAudioComponent from "@/components/landing-page/filter-all/audio-filter-card";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function FilterPage() {
|
export default function FilterPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -86,8 +82,52 @@ export default function FilterPage() {
|
||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
const [userLevels, setUserLevels] = useState([]);
|
const [userLevels, setUserLevels] = useState([]);
|
||||||
const [contentAll, setContentAll] = useState([]);
|
const [contentAll, setContentAll] = useState([]);
|
||||||
|
const t = useTranslations("FilterPage");
|
||||||
|
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||||
|
|
||||||
// const [startDate, endDate] = dateRange;
|
const [categoryPage, setCategoryPage] = useState(1);
|
||||||
|
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||||
|
const poldaName = params?.polda_name;
|
||||||
|
const satkerName = params?.satker_name;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const title = searchParams?.get("title") || "";
|
||||||
|
const category = searchParams?.get("category") || "";
|
||||||
|
const sortBy = searchParams?.get("sortBy") || "latest";
|
||||||
|
|
||||||
|
setSearchTitle(title);
|
||||||
|
setCategoryFilter(category ? category.split("&") : []);
|
||||||
|
setSortByOpt(sortBy === "popular" ? "clickCount" : "createdAt");
|
||||||
|
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const pageFromUrl = searchParams?.get("page");
|
const pageFromUrl = searchParams?.get("page");
|
||||||
|
|
@ -387,244 +427,314 @@ export default function FilterPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col w-full max-w-screen overflow-x-hidden">
|
||||||
{/* 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("content", { defaultValue: "Content" })}</p>
|
||||||
<div className="flex flex-col md:flex-row items-start gap-5 p-10 bg-[#f7f7f7] dark:bg-black">
|
{">"}
|
||||||
<p>
|
<p>
|
||||||
{" "}
|
<span className="font-bold">
|
||||||
Konten {">"} <span className="font-bold">Semua Konten</span>
|
{t("allContent", { defaultValue: "All Content" })}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="font-bold">|</p>
|
<p className="font-bold">|</p>
|
||||||
<p>{`Hasil Pencarian ${title} `}</p>
|
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t(
|
||||||
{`Terdapat ${contentAll?.length} konten yang dapat diunduh`}
|
"downloadableContent",
|
||||||
|
{ defaultValue: "Downloadable ContentS" }
|
||||||
|
)}`}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Left */}
|
{/* Left */}
|
||||||
<div className="flex flex-col lg:flex-row gap-6 p-4">
|
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||||
<div className="lg:w-[25%] w-full bg-[#f7f7f7] dark:bg-black p-4 rounded-lg shadow-md">
|
<div className="lg:hidden flex justify-end mb-2">
|
||||||
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
<button
|
||||||
<Icon icon="stash:filter-light" fontSize={30} />
|
onClick={() => setIsFilterOpen(!isFilterOpen)}
|
||||||
Filter
|
className="text-sm text-white bg-[#bb3523] px-4 py-1 rounded-md shadow"
|
||||||
</h2>
|
>
|
||||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
{isFilterOpen ? "Hide Filter" : "Show Filter"}
|
||||||
<div className="space-y-6">
|
</button>
|
||||||
<div>
|
</div>
|
||||||
<label
|
|
||||||
htmlFor="search"
|
|
||||||
className="block text-sm font-medium text-gray-700 dark:text-white"
|
|
||||||
>
|
|
||||||
Pencarian
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={searchTitle}
|
|
||||||
onChange={(e) => setSearchTitle(e.target.value)}
|
|
||||||
onKeyUp={handleKeyUp}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
type="text"
|
|
||||||
id="search"
|
|
||||||
placeholder="Cari judul..."
|
|
||||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
{isFilterOpen && (
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
<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">
|
||||||
Tahun & Bulan
|
<h2 className="text-lg font-semibold mb-4 flex items-center gap-1">
|
||||||
</label>
|
<Icon icon="stash:filter-light" fontSize={30} />
|
||||||
<ReactDatePicker
|
Filter
|
||||||
selected={monthYearFilter}
|
</h2>
|
||||||
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
<div className="border-t border-black my-4 dark:border-white"></div>
|
||||||
onChange={(date) => setMonthYearFilter(date)}
|
<div className="space-y-6">
|
||||||
dateFormat="MM | yyyy"
|
<div>
|
||||||
placeholderText="Pilih Tahun dan Bulan"
|
<label
|
||||||
showMonthYearPicker
|
htmlFor="search"
|
||||||
/>
|
className="block text-sm font-medium text-gray-700 dark:text-white"
|
||||||
</div>
|
>
|
||||||
|
{t("search", { defaultValue: "Search" })}
|
||||||
<div>
|
</label>
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
<Input
|
||||||
Tanggal
|
value={searchTitle}
|
||||||
</label>
|
onChange={(e) => setSearchTitle(e.target.value)}
|
||||||
<div className="flex flex-row justify justify-between gap-2">
|
onKeyUp={handleKeyUp}
|
||||||
<ReactDatePicker
|
onKeyDown={handleKeyDown}
|
||||||
selectsRange
|
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"
|
className="mt-1 w-full border 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="Pilih Tanggal"
|
|
||||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center">
|
</div>
|
||||||
{handleClose ? (
|
|
||||||
<Icon
|
<div>
|
||||||
icon="carbon:close-filled"
|
<label className="block text-sm font-medium text-gray-700 dark:text-white">
|
||||||
onClick={handleDeleteDate}
|
{t("monthYear", { defaultValue: "Month Year" })}
|
||||||
width="20"
|
</label>
|
||||||
inline
|
<ReactDatePicker
|
||||||
color="#216ba5"
|
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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
||||||
Kategori
|
{t("categories", { defaultValue: "Categories" })}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="mt-2 space-y-2">
|
<ul className="mt-2 space-y-2">
|
||||||
{categories.map((category: any) => (
|
{categories.map((category: any) => (
|
||||||
<li key={category?.id}>
|
<li key={category?.id}>
|
||||||
<label
|
<label
|
||||||
className="inline-flex items-center"
|
className="inline-flex items-center"
|
||||||
htmlFor={`${category.id}`}
|
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
|
<Checkbox
|
||||||
id={`${category.id}`}
|
id="png"
|
||||||
value={category.id}
|
value="png"
|
||||||
checked={categoryFilter.includes(String(category.id))}
|
checked={formatFilter.includes("png")}
|
||||||
onCheckedChange={(e) =>
|
onCheckedChange={(e) =>
|
||||||
handleCategoryFilter(Boolean(e), category.id)
|
handleFormatFilter(Boolean(e), "png")
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span className="ml-2 text-gray-700 dark:text-white">
|
<span className="ml-2 text-gray-700 dark:text-white">
|
||||||
{category?.name}
|
PNG
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
))}
|
<li>
|
||||||
</ul>
|
<label className="inline-flex items-center">
|
||||||
</div>
|
<Checkbox
|
||||||
{/* Garis */}
|
id="jpeg"
|
||||||
<div className="border-t border-black my-4 dark:border-white"></div>
|
value="jpeg"
|
||||||
{/* Garis */}
|
checked={formatFilter.includes("jpeg")}
|
||||||
<div>
|
onCheckedChange={(e) =>
|
||||||
<h3 className="text-sm font-medium text-gray-700 dark:text-white">
|
handleFormatFilter(Boolean(e), "jpeg")
|
||||||
Format Konten
|
}
|
||||||
</h3>
|
/>
|
||||||
<ul className="mt-2 space-y-2">
|
<span className="ml-2 text-gray-700 dark:text-white">
|
||||||
<li>
|
JPEG
|
||||||
<label className="inline-flex items-center">
|
</span>
|
||||||
<Checkbox
|
</label>
|
||||||
id="png"
|
</li>
|
||||||
value="png"
|
<li>
|
||||||
checked={formatFilter.includes("image")}
|
<label className="inline-flex items-center">
|
||||||
onCheckedChange={(e) =>
|
<Checkbox
|
||||||
handleFormatFilter(Boolean(e), "image")
|
id="jpg"
|
||||||
}
|
value="jpg"
|
||||||
/>
|
checked={formatFilter.includes("jpg")}
|
||||||
<span className="ml-2 text-gray-700 dark:text-white">
|
onCheckedChange={(e) =>
|
||||||
Foto
|
handleFormatFilter(Boolean(e), "jpg")
|
||||||
</span>
|
}
|
||||||
</label>
|
/>
|
||||||
</li>
|
<span className="ml-2 text-gray-700 dark:text-white">
|
||||||
<li>
|
JPG
|
||||||
<label className="inline-flex items-center">
|
</span>
|
||||||
<Checkbox
|
</label>
|
||||||
id="jpeg"
|
</li>
|
||||||
value="jpeg"
|
</ul>
|
||||||
checked={formatFilter.includes("video")}
|
</div>
|
||||||
onCheckedChange={(e) =>
|
<div className="border-t border-black dark:border-white my-4"></div>
|
||||||
handleFormatFilter(Boolean(e), "video")
|
<div className="text-center">
|
||||||
}
|
<a
|
||||||
/>
|
onClick={cleanCheckbox}
|
||||||
<span className="ml-2 text-gray-700 dark:text-white">
|
className="text-[#bb3523] cursor-pointer"
|
||||||
Audio Visual
|
>
|
||||||
</span>
|
<b>Reset Filter</b>
|
||||||
</label>
|
</a>
|
||||||
</li>
|
</div>
|
||||||
<li>
|
|
||||||
<label className="inline-flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="jpg"
|
|
||||||
value="jpg"
|
|
||||||
checked={formatFilter.includes("document")}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleFormatFilter(Boolean(e), "document")
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span className="ml-2 text-gray-700 dark:text-white">
|
|
||||||
Teks
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label className="inline-flex items-center">
|
|
||||||
<Checkbox
|
|
||||||
id="jpg"
|
|
||||||
value="jpg"
|
|
||||||
checked={formatFilter.includes("audio")}
|
|
||||||
onCheckedChange={(e) =>
|
|
||||||
handleFormatFilter(Boolean(e), "audio")
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span className="ml-2 text-gray-700 dark:text-white">
|
|
||||||
Audio
|
|
||||||
</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>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* Konten Kanan */}
|
{/* Konten Kanan */}
|
||||||
<div className="flex-1 w-[75%]">
|
<div className="w-full pl-4 pr-4 lg:pr-16 pb-4 overflow-x-hidden">
|
||||||
<div className="flex flex-col items-end mb-4">
|
<div className="w-full">
|
||||||
<h2 className="text-lg font-semibold">Urutkan berdasarkan</h2>
|
<div className="flex flex-col items-end mb-4">
|
||||||
<select
|
<h2 className="text-lg font-semibold">
|
||||||
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
{t("sortBy", { defaultValue: "Sort By" })}
|
||||||
onChange={(e) => handleSorting(e)}
|
</h2>
|
||||||
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
<select
|
||||||
>
|
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
||||||
<option value="terbaru">Terbaru</option>
|
onChange={(e) => handleSorting(e)}
|
||||||
<option value="terpopuler">Terpopuler</option>
|
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||||
</select>
|
>
|
||||||
</div>
|
<option value="latest">
|
||||||
<div className="flex flex-col gap-2 w-full">
|
{t("latest", { defaultValue: "Latest" })}
|
||||||
<FilterImageComponent
|
</option>
|
||||||
categoryFilter={categoryFilter}
|
<option value="popular">
|
||||||
sortByOpt={sortByOpt}
|
{t("mostPopular", { defaultValue: "Most Popular" })}
|
||||||
startDateString={startDateString}
|
</option>
|
||||||
endDateString={endDateString}
|
</select>
|
||||||
monthYearFilter={monthYearFilter}
|
</div>
|
||||||
/>
|
<div className="flex flex-col gap-4 w-full">
|
||||||
<FilterVideoComponent
|
<FilterImageComponent
|
||||||
categoryFilter={categoryFilter}
|
categoryFilter={categoryFilter}
|
||||||
sortByOpt={sortByOpt}
|
sortByOpt={sortByOpt}
|
||||||
startDateString={startDateString}
|
startDateString={startDateString}
|
||||||
endDateString={endDateString}
|
endDateString={endDateString}
|
||||||
monthYearFilter={monthYearFilter}
|
monthYearFilter={monthYearFilter}
|
||||||
/>
|
/>
|
||||||
<FilterDocumentComponent
|
<FilterVideoComponent
|
||||||
categoryFilter={categoryFilter}
|
categoryFilter={categoryFilter}
|
||||||
sortByOpt={sortByOpt}
|
sortByOpt={sortByOpt}
|
||||||
startDateString={startDateString}
|
startDateString={startDateString}
|
||||||
endDateString={endDateString}
|
endDateString={endDateString}
|
||||||
monthYearFilter={monthYearFilter}
|
monthYearFilter={monthYearFilter}
|
||||||
/>
|
/>
|
||||||
<FilterAudioComponent
|
<FilterDocumentComponent
|
||||||
categoryFilter={categoryFilter}
|
categoryFilter={categoryFilter}
|
||||||
sortByOpt={sortByOpt}
|
sortByOpt={sortByOpt}
|
||||||
startDateString={startDateString}
|
startDateString={startDateString}
|
||||||
endDateString={endDateString}
|
endDateString={endDateString}
|
||||||
monthYearFilter={monthYearFilter}
|
monthYearFilter={monthYearFilter}
|
||||||
/>
|
/>
|
||||||
|
<FilterAudioComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -488,10 +488,16 @@ const FilterPage = () => {
|
||||||
<div className="flex flex-col md:flex-row items-start gap-5 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
<div className="flex flex-col md:flex-row items-start gap-5 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||||
<p>
|
<p>
|
||||||
{" "}
|
{" "}
|
||||||
Audio {">"} <span className="font-bold">{t("allAudio", { defaultValue: "All Audio" })}</span>
|
Audio {">"}{" "}
|
||||||
|
<span className="font-bold">
|
||||||
|
{t("allAudio", { defaultValue: "All Audio" })}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="font-bold">|</p>
|
<p className="font-bold">|</p>
|
||||||
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t("downloadableAudio", { defaultValue: "Downloadable Audio" })}`}</p>
|
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t(
|
||||||
|
"downloadableAudio",
|
||||||
|
{ defaultValue: "Downloadable Audio" }
|
||||||
|
)}`}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Left */}
|
{/* Left */}
|
||||||
|
|
@ -531,7 +537,9 @@ const FilterPage = () => {
|
||||||
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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)}
|
onChange={(date) => setMonthYearFilter(date)}
|
||||||
dateFormat="MM | yyyy"
|
dateFormat="MM | yyyy"
|
||||||
placeholderText={t("selectYear", { defaultValue: "Select Year" })}
|
placeholderText={t("selectYear", {
|
||||||
|
defaultValue: "Select Year",
|
||||||
|
})}
|
||||||
showMonthYearPicker
|
showMonthYearPicker
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -549,7 +557,9 @@ const FilterPage = () => {
|
||||||
onChange={(update) => {
|
onChange={(update) => {
|
||||||
setDateRange(update);
|
setDateRange(update);
|
||||||
}}
|
}}
|
||||||
placeholderText={t("searchDate", { defaultValue: "Search Date" })}
|
placeholderText={t("searchDate", {
|
||||||
|
defaultValue: "Search Date",
|
||||||
|
})}
|
||||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -594,7 +604,6 @@ const FilterPage = () => {
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||||
{/* Tombol Prev */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||||
|
|
@ -615,7 +624,6 @@ const FilterPage = () => {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Nomor Halaman */}
|
|
||||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
|
|
@ -628,7 +636,6 @@ const FilterPage = () => {
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Tombol Next */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) =>
|
setCategoryPage((prev) =>
|
||||||
|
|
@ -709,14 +716,20 @@ const FilterPage = () => {
|
||||||
<div className="w-full pr-4 lg:pr-16 pb-4">
|
<div className="w-full pr-4 lg:pr-16 pb-4">
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="flex flex-col items-end mb-4">
|
<div className="flex flex-col items-end mb-4">
|
||||||
<h2 className="text-lg font-semibold">{t("sortBy", { defaultValue: "Sort By" })} </h2>
|
<h2 className="text-lg font-semibold">
|
||||||
|
{t("sortBy", { defaultValue: "Sort By" })}{" "}
|
||||||
|
</h2>
|
||||||
<select
|
<select
|
||||||
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
||||||
onChange={(e) => handleSorting(e)}
|
onChange={(e) => handleSorting(e)}
|
||||||
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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="latest">
|
||||||
<option value="popular">{t("mostPopular", { defaultValue: "Most Popular" })}</option>
|
{t("latest", { defaultValue: "Latest" })}
|
||||||
|
</option>
|
||||||
|
<option value="popular">
|
||||||
|
{t("mostPopular", { defaultValue: "Most Popular" })}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -504,10 +504,16 @@ const FilterPage = () => {
|
||||||
<div className="flex flex-col md:flex-row items-start gap-5 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
<div className="flex flex-col md:flex-row items-start gap-5 py-10 px-4 lg:px-20 bg-[#f7f7f7] dark:bg-black">
|
||||||
<p>
|
<p>
|
||||||
{" "}
|
{" "}
|
||||||
{t("text", { defaultValue: "Text" })} {">"} <span className="font-bold">{t("allText", { defaultValue: "All Text" })}</span>
|
{t("text", { defaultValue: "Text" })} {">"}{" "}
|
||||||
|
<span className="font-bold">
|
||||||
|
{t("allText", { defaultValue: "All Text" })}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="font-bold">|</p>
|
<p className="font-bold">|</p>
|
||||||
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t("downloadableText", { defaultValue: "Downloadable Text" })}`}</p>
|
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t(
|
||||||
|
"downloadableText",
|
||||||
|
{ defaultValue: "Downloadable Text" }
|
||||||
|
)}`}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Left */}
|
{/* Left */}
|
||||||
|
|
@ -547,7 +553,9 @@ const FilterPage = () => {
|
||||||
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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)}
|
onChange={(date) => setMonthYearFilter(date)}
|
||||||
dateFormat="MM | yyyy"
|
dateFormat="MM | yyyy"
|
||||||
placeholderText={t("selectYear", { defaultValue: "Select Year" })}
|
placeholderText={t("selectYear", {
|
||||||
|
defaultValue: "Select Year",
|
||||||
|
})}
|
||||||
showMonthYearPicker
|
showMonthYearPicker
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -565,7 +573,9 @@ const FilterPage = () => {
|
||||||
onChange={(update) => {
|
onChange={(update) => {
|
||||||
setDateRange(update);
|
setDateRange(update);
|
||||||
}}
|
}}
|
||||||
placeholderText={t("selectDate", { defaultValue: "Select Date" })}
|
placeholderText={t("selectDate", {
|
||||||
|
defaultValue: "Select Date",
|
||||||
|
})}
|
||||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -610,7 +620,6 @@ const FilterPage = () => {
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||||
{/* Tombol Prev */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||||
|
|
@ -631,7 +640,6 @@ const FilterPage = () => {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Nomor Halaman */}
|
|
||||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
|
|
@ -644,7 +652,6 @@ const FilterPage = () => {
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Tombol Next */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) =>
|
setCategoryPage((prev) =>
|
||||||
|
|
@ -771,14 +778,20 @@ const FilterPage = () => {
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex flex-col items-end mb-4">
|
<div className="flex flex-col items-end mb-4">
|
||||||
<h2 className="text-lg font-semibold">{t("sortBy", { defaultValue: "Sort By" })}</h2>
|
<h2 className="text-lg font-semibold">
|
||||||
|
{t("sortBy", { defaultValue: "Sort By" })}
|
||||||
|
</h2>
|
||||||
<select
|
<select
|
||||||
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
||||||
onChange={(e) => handleSorting(e)}
|
onChange={(e) => handleSorting(e)}
|
||||||
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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="latest">
|
||||||
<option value="popular">{t("mostPopular", { defaultValue: "Most Popular" })}</option>
|
{t("latest", { defaultValue: "Latest" })}
|
||||||
|
</option>
|
||||||
|
<option value="popular">
|
||||||
|
{t("mostPopular", { defaultValue: "Most Popular" })}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -485,24 +485,24 @@ const FilterPage = () => {
|
||||||
clearTimeout(typingTimer);
|
clearTimeout(typingTimer);
|
||||||
};
|
};
|
||||||
|
|
||||||
const shimmer = (w: number, h: number) => `
|
// 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">
|
// <svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<defs>
|
// <defs>
|
||||||
<linearGradient id="g">
|
// <linearGradient id="g">
|
||||||
<stop stop-color="#bcbcbd" offset="20%" />
|
// <stop stop-color="#bcbcbd" offset="20%" />
|
||||||
<stop stop-color="#f9fafb" offset="50%" />
|
// <stop stop-color="#f9fafb" offset="50%" />
|
||||||
<stop stop-color="#bcbcbd" offset="70%" />
|
// <stop stop-color="#bcbcbd" offset="70%" />
|
||||||
</linearGradient>
|
// </linearGradient>
|
||||||
</defs>
|
// </defs>
|
||||||
<rect width="${w}" height="${h}" fill="#bcbcbd" />
|
// <rect width="${w}" height="${h}" fill="#bcbcbd" />
|
||||||
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
|
// <rect id="r" width="${w}" height="${h}" fill="url(#g)" />
|
||||||
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
// <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
||||||
</svg>`;
|
// </svg>`;
|
||||||
|
|
||||||
const toBase64 = (str: string) =>
|
// const toBase64 = (str: string) =>
|
||||||
typeof window === "undefined"
|
// typeof window === "undefined"
|
||||||
? Buffer.from(str).toString("base64")
|
// ? Buffer.from(str).toString("base64")
|
||||||
: window.btoa(str);
|
// : window.btoa(str);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
|
@ -511,14 +511,19 @@ const FilterPage = () => {
|
||||||
<p> {t("image", { defaultValue: "Image" })}</p>
|
<p> {t("image", { defaultValue: "Image" })}</p>
|
||||||
{">"}
|
{">"}
|
||||||
<p>
|
<p>
|
||||||
<span className="font-bold">{t("allImage", { defaultValue: "All Image" })}</span>
|
<span className="font-bold">
|
||||||
|
{t("allImage", { defaultValue: "All Image" })}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="font-bold">|</p>
|
<p className="font-bold">|</p>
|
||||||
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t("downloadableImage", { defaultValue: "Downloadable Image" })}`}</p>
|
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t(
|
||||||
|
"downloadableImage",
|
||||||
|
{ defaultValue: "Downloadable Image" }
|
||||||
|
)}`}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Left */}
|
|
||||||
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
<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">
|
<div className="lg:hidden flex justify-end mb-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsFilterOpen(!isFilterOpen)}
|
onClick={() => setIsFilterOpen(!isFilterOpen)}
|
||||||
|
|
@ -550,7 +555,9 @@ const FilterPage = () => {
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
type="text"
|
type="text"
|
||||||
id="search"
|
id="search"
|
||||||
placeholder={t("searchTitle", { defaultValue: "Search Title" })}
|
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"
|
className="mt-1 w-full border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -564,7 +571,9 @@ const FilterPage = () => {
|
||||||
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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)}
|
onChange={(date) => setMonthYearFilter(date)}
|
||||||
dateFormat="MM | yyyy"
|
dateFormat="MM | yyyy"
|
||||||
placeholderText={t("selectYear", { defaultValue: "Select Year" })}
|
placeholderText={t("selectYear", {
|
||||||
|
defaultValue: "Select Year",
|
||||||
|
})}
|
||||||
showMonthYearPicker
|
showMonthYearPicker
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -582,7 +591,9 @@ const FilterPage = () => {
|
||||||
onChange={(update) => {
|
onChange={(update) => {
|
||||||
setDateRange(update);
|
setDateRange(update);
|
||||||
}}
|
}}
|
||||||
placeholderText={t("selectDate", { defaultValue: "Select Date" })}
|
placeholderText={t("selectDate", {
|
||||||
|
defaultValue: "Select Date",
|
||||||
|
})}
|
||||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -627,7 +638,6 @@ const FilterPage = () => {
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||||
{/* Tombol Prev */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||||
|
|
@ -648,7 +658,6 @@ const FilterPage = () => {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Nomor Halaman */}
|
|
||||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
|
|
@ -663,7 +672,6 @@ const FilterPage = () => {
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Tombol Next */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) =>
|
setCategoryPage((prev) =>
|
||||||
|
|
@ -761,14 +769,20 @@ const FilterPage = () => {
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex flex-col items-end mb-4">
|
<div className="flex flex-col items-end mb-4">
|
||||||
<h2 className="text-lg font-semibold">{t("sortBy", { defaultValue: "Sort By" })}</h2>
|
<h2 className="text-lg font-semibold">
|
||||||
|
{t("sortBy", { defaultValue: "Sort By" })}
|
||||||
|
</h2>
|
||||||
<select
|
<select
|
||||||
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
||||||
onChange={(e) => handleSorting(e)}
|
onChange={(e) => handleSorting(e)}
|
||||||
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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="latest">
|
||||||
<option value="popular">{t("mostPopular", { defaultValue: "Most Popular" })}</option>
|
{t("latest", { defaultValue: "Latest" })}
|
||||||
|
</option>
|
||||||
|
<option value="popular">
|
||||||
|
{t("mostPopular", { defaultValue: "Most Popular" })}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,731 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
|
import { getOnlyDate, getOnlyMonthAndYear } from "@/utils/globals";
|
||||||
|
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
|
import {
|
||||||
|
getPublicCategoryData,
|
||||||
|
getUserLevelListByParent,
|
||||||
|
listCategory,
|
||||||
|
listData,
|
||||||
|
listDataAll,
|
||||||
|
listDataRegional,
|
||||||
|
} from "@/service/landing/landing";
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
PaginationState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
} 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 FilterImageComponent from "@/components/landing-page/filter-all/image-filter-card";
|
||||||
|
import FilterVideoComponent from "@/components/landing-page/filter-all/video-filter-card";
|
||||||
|
import FilterDocumentComponent from "@/components/landing-page/filter-all/document-filter-card";
|
||||||
|
import FilterAudioComponent from "@/components/landing-page/filter-all/audio-filter-card";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
export default function FilterPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const asPath = usePathname();
|
||||||
|
const params = useParams();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const locale = params?.locale;
|
||||||
|
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 tag: any = searchParams?.get("tag");
|
||||||
|
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 [contentAll, setContentAll] = useState([]);
|
||||||
|
const t = useTranslations("FilterPage");
|
||||||
|
const [isFilterOpen, setIsFilterOpen] = useState(true);
|
||||||
|
const [categoryPage, setCategoryPage] = useState(1);
|
||||||
|
const [categoryTotalPages, setCategoryTotalPages] = useState(1);
|
||||||
|
const poldaName = params?.polda_name;
|
||||||
|
const satkerName = params?.satker_name;
|
||||||
|
|
||||||
|
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
|
||||||
|
); // halaman 1-based
|
||||||
|
|
||||||
|
const content = response?.data?.data?.content || [];
|
||||||
|
const total = response?.data?.data?.totalPages || 1;
|
||||||
|
|
||||||
|
setCategories(content);
|
||||||
|
setCategoryTotalPages(total);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(monthYearFilter, "monthFilter");
|
||||||
|
initState();
|
||||||
|
}, [
|
||||||
|
change,
|
||||||
|
monthYearFilter,
|
||||||
|
sortBy,
|
||||||
|
sortByOpt,
|
||||||
|
title,
|
||||||
|
startDateString,
|
||||||
|
endDateString,
|
||||||
|
categorie,
|
||||||
|
formatFilter,
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function getCategories() {
|
||||||
|
const category = await listCategory("1");
|
||||||
|
const resCategory = category?.data?.data?.content;
|
||||||
|
setCategories(resCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getData() {
|
||||||
|
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 listDataAll(
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
filter,
|
||||||
|
"",
|
||||||
|
tag,
|
||||||
|
filterGroup,
|
||||||
|
startDateString,
|
||||||
|
endDateString,
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)
|
||||||
|
?.split("/")[0]
|
||||||
|
?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
setContentAll(response?.data?.data?.content);
|
||||||
|
setTotalPage(response?.data?.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 listDataAll(
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
filter,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
tag,
|
||||||
|
startDateString,
|
||||||
|
endDateString,
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)
|
||||||
|
?.split("/")[0]
|
||||||
|
?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthYearFilter
|
||||||
|
? getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]
|
||||||
|
: ""
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
setContentAll(response?.data?.data?.content);
|
||||||
|
setTotalPage(response?.data?.data?.totalPages);
|
||||||
|
setTotalContent(response?.data?.data?.totalElements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCategoryFilter = (e: boolean, id: string) => {
|
||||||
|
let filter = [...categoryFilter];
|
||||||
|
|
||||||
|
if (e) {
|
||||||
|
filter = [...categoryFilter, String(id)];
|
||||||
|
} else {
|
||||||
|
filter.splice(categoryFilter.indexOf(id), 1);
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
"",
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = () => {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full max-w-screen overflow-x-hidden">
|
||||||
|
<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("content", { defaultValue: "Content" })}</p>
|
||||||
|
{">"}
|
||||||
|
<p>
|
||||||
|
<span className="font-bold">
|
||||||
|
{t("allContent", { defaultValue: "All Content" })}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className="font-bold">|</p>
|
||||||
|
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t(
|
||||||
|
"downloadableContent",
|
||||||
|
{ defaultValue: "Downloadable ContentS" }
|
||||||
|
)}`}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Left */}
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6 pl-4 lg:pl-20 py-4">
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Konten Kanan */}
|
||||||
|
<div className="w-full pl-4 pr-4 lg:pr-16 pb-4 overflow-x-hidden">
|
||||||
|
<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>
|
||||||
|
<div className="flex flex-col gap-4 w-full">
|
||||||
|
<FilterImageComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
<FilterVideoComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
<FilterDocumentComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
<FilterAudioComponent
|
||||||
|
categoryFilter={categoryFilter}
|
||||||
|
sortByOpt={sortByOpt}
|
||||||
|
startDateString={startDateString}
|
||||||
|
endDateString={endDateString}
|
||||||
|
monthYearFilter={monthYearFilter}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Footer from "@/components/landing-page/footer";
|
||||||
|
import Navbar from "@/components/landing-page/navbar";
|
||||||
|
|
||||||
|
const layout = async ({ children }: { children: React.ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Navbar />
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default layout;
|
||||||
|
|
@ -295,7 +295,6 @@ const Schedule = (props: any) => {
|
||||||
};
|
};
|
||||||
temp.push(now);
|
temp.push(now);
|
||||||
}
|
}
|
||||||
console.log("QQQQQ", temp);
|
|
||||||
setCity(temp);
|
setCity(temp);
|
||||||
}
|
}
|
||||||
fetchPoldaPolres();
|
fetchPoldaPolres();
|
||||||
|
|
@ -341,7 +340,6 @@ const Schedule = (props: any) => {
|
||||||
curr.setDate(curr.getDate() + 1);
|
curr.setDate(curr.getDate() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Change Date :", dateListTemp);
|
|
||||||
setDateList(dateListTemp);
|
setDateList(dateListTemp);
|
||||||
setDateAWeek(dateListTemp);
|
setDateAWeek(dateListTemp);
|
||||||
}
|
}
|
||||||
|
|
@ -431,7 +429,6 @@ const Schedule = (props: any) => {
|
||||||
const response = await detailSchedule(itemFound?.id);
|
const response = await detailSchedule(itemFound?.id);
|
||||||
setDetail(response?.data?.data);
|
setDetail(response?.data?.data);
|
||||||
setContent(response?.data?.data?.files);
|
setContent(response?.data?.data?.files);
|
||||||
console.log("item Found", itemFound);
|
|
||||||
close();
|
close();
|
||||||
setOpenDialog(true);
|
setOpenDialog(true);
|
||||||
};
|
};
|
||||||
|
|
@ -483,7 +480,6 @@ const Schedule = (props: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteFilterhandler = (filterId: any) => {
|
const deleteFilterhandler = (filterId: any) => {
|
||||||
console.log("hapus", filterId);
|
|
||||||
const deletedReg = regionName.filter((list: any) => list.id !== filterId);
|
const deletedReg = regionName.filter((list: any) => list.id !== filterId);
|
||||||
const filtered = regionFilter.filter((list: any) => list !== filterId);
|
const filtered = regionFilter.filter((list: any) => list !== filterId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,7 @@ const FilterPage = () => {
|
||||||
setCategoryFilter(filter);
|
setCategoryFilter(filter);
|
||||||
router.push(`?category=${filter.join("&")}`);
|
router.push(`?category=${filter.join("&")}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormatFilter = (e: boolean, id: string) => {
|
const handleFormatFilter = (e: boolean, id: string) => {
|
||||||
let filter = [...formatFilter];
|
let filter = [...formatFilter];
|
||||||
|
|
||||||
|
|
@ -505,10 +505,16 @@ const FilterPage = () => {
|
||||||
<p>
|
<p>
|
||||||
{" "}
|
{" "}
|
||||||
{t("video", { defaultValue: "Video" })}
|
{t("video", { defaultValue: "Video" })}
|
||||||
{">"} <span className="font-bold">{t("allVideo", { defaultValue: "All Video" })}</span>
|
{">"}{" "}
|
||||||
|
<span className="font-bold">
|
||||||
|
{t("allVideo", { defaultValue: "All Video" })}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="font-bold">|</p>
|
<p className="font-bold">|</p>
|
||||||
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t("downloadableVideo", { defaultValue: "Downloadable Video" })}`}</p>
|
<p>{`${t("thereIs", { defaultValue: "There Is" })} ${totalContent} ${t(
|
||||||
|
"downloadableVideo",
|
||||||
|
{ defaultValue: "Downloadable Video" }
|
||||||
|
)}`}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Left */}
|
{/* Left */}
|
||||||
|
|
@ -548,7 +554,9 @@ const FilterPage = () => {
|
||||||
className="mt-1 w-full text-xs border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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)}
|
onChange={(date) => setMonthYearFilter(date)}
|
||||||
dateFormat="MM | yyyy"
|
dateFormat="MM | yyyy"
|
||||||
placeholderText={t("selectYear", { defaultValue: "Select Year" })}
|
placeholderText={t("selectYear", {
|
||||||
|
defaultValue: "Select Year",
|
||||||
|
})}
|
||||||
showMonthYearPicker
|
showMonthYearPicker
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -566,7 +574,9 @@ const FilterPage = () => {
|
||||||
onChange={(update) => {
|
onChange={(update) => {
|
||||||
setDateRange(update);
|
setDateRange(update);
|
||||||
}}
|
}}
|
||||||
placeholderText={t("selectDate", { defaultValue: "Select Date" })}
|
placeholderText={t("selectDate", {
|
||||||
|
defaultValue: "Select Date",
|
||||||
|
})}
|
||||||
onCalendarClose={() => setCalenderState(!calenderState)}
|
onCalendarClose={() => setCalenderState(!calenderState)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -611,7 +621,6 @@ const FilterPage = () => {
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<div className="mt-4 flex gap-2 justify-center items-center">
|
<div className="mt-4 flex gap-2 justify-center items-center">
|
||||||
{/* Tombol Prev */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
setCategoryPage((prev) => Math.max(prev - 1, 1))
|
||||||
|
|
@ -632,7 +641,6 @@ const FilterPage = () => {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Nomor Halaman */}
|
|
||||||
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
{Array.from({ length: categoryTotalPages }, (_, i) => (
|
||||||
<button
|
<button
|
||||||
key={i}
|
key={i}
|
||||||
|
|
@ -645,7 +653,6 @@ const FilterPage = () => {
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Tombol Next */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCategoryPage((prev) =>
|
setCategoryPage((prev) =>
|
||||||
|
|
@ -772,14 +779,20 @@ const FilterPage = () => {
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex flex-col items-end mb-4">
|
<div className="flex flex-col items-end mb-4">
|
||||||
<h2 className="text-lg font-semibold">{t("sortBy", { defaultValue: "Sort By" })}</h2>
|
<h2 className="text-lg font-semibold">
|
||||||
|
{t("sortBy", { defaultValue: "Sort By" })}
|
||||||
|
</h2>
|
||||||
<select
|
<select
|
||||||
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
defaultValue={sortBy == "popular" ? "terpopuler" : "terbaru"}
|
||||||
onChange={(e) => handleSorting(e)}
|
onChange={(e) => handleSorting(e)}
|
||||||
className="border rounded-md py-2 px-3 focus:ring-red-500 focus:border-red-500"
|
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="latest">
|
||||||
<option value="popular">{t("mostPopular", { defaultValue: "Most Popular" })}</option>
|
{t("latest", { defaultValue: "Latest" })}
|
||||||
|
</option>
|
||||||
|
<option value="popular">
|
||||||
|
{t("mostPopular", { defaultValue: "Most Popular" })}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ type AuthStep = "login" | "email-setup" | "otp";
|
||||||
|
|
||||||
const AuthPage = ({ params: { locale } }: { params: { locale: string } }) => {
|
const AuthPage = ({ params: { locale } }: { params: { locale: string } }) => {
|
||||||
const [currentStep, setCurrentStep] = useState<AuthStep>("login");
|
const [currentStep, setCurrentStep] = useState<AuthStep>("login");
|
||||||
const [loginCredentials, setLoginCredentials] = useState<LoginFormData | null>(null);
|
const [loginCredentials, setLoginCredentials] =
|
||||||
|
useState<LoginFormData | null>(null);
|
||||||
const { validateEmail } = useEmailValidation();
|
const { validateEmail } = useEmailValidation();
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
|
|
||||||
|
|
@ -23,6 +24,9 @@ const AuthPage = ({ params: { locale } }: { params: { locale: string } }) => {
|
||||||
try {
|
try {
|
||||||
const result = await validateEmail(data);
|
const result = await validateEmail(data);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
|
case "skip":
|
||||||
|
handleOTPSuccess();
|
||||||
|
break;
|
||||||
case "setup":
|
case "setup":
|
||||||
setCurrentStep("email-setup");
|
setCurrentStep("email-setup");
|
||||||
break;
|
break;
|
||||||
|
|
@ -112,11 +116,7 @@ const AuthPage = ({ params: { locale } }: { params: { locale: string } }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <AuthLayout>{renderCurrentStep()}</AuthLayout>;
|
||||||
<AuthLayout>
|
|
||||||
{renderCurrentStep()}
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthPage;
|
export default AuthPage;
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,13 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
FileText,
|
FileText,
|
||||||
Image,
|
Image,
|
||||||
Loader2,
|
Loader2,
|
||||||
Save,
|
Save,
|
||||||
Trash2,
|
Trash2,
|
||||||
Edit3,
|
Edit3,
|
||||||
Globe,
|
Globe,
|
||||||
Users,
|
Users,
|
||||||
|
|
@ -42,7 +42,7 @@ import {
|
||||||
Eye,
|
Eye,
|
||||||
Settings,
|
Settings,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
XCircle
|
XCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
// Swiper
|
// Swiper
|
||||||
|
|
@ -62,10 +62,7 @@ import {
|
||||||
getTagsBySubCategoryId,
|
getTagsBySubCategoryId,
|
||||||
listEnableCategory,
|
listEnableCategory,
|
||||||
} from "@/service/content/content";
|
} from "@/service/content/content";
|
||||||
import {
|
import { generateDataRewrite, getDetailArticle } from "@/service/content/ai";
|
||||||
generateDataRewrite,
|
|
||||||
getDetailArticle,
|
|
||||||
} from "@/service/content/ai";
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { getCookiesDecrypt } from "@/lib/utils";
|
import { getCookiesDecrypt } from "@/lib/utils";
|
||||||
|
|
@ -150,14 +147,14 @@ const PLACEMENT_OPTIONS = [
|
||||||
// Dynamic imports
|
// Dynamic imports
|
||||||
const CustomEditor = dynamic(
|
const CustomEditor = dynamic(
|
||||||
() => import("@/components/editor/custom-editor"),
|
() => import("@/components/editor/custom-editor"),
|
||||||
{
|
{
|
||||||
ssr: false,
|
ssr: false,
|
||||||
loading: () => (
|
loading: () => (
|
||||||
<div className="flex items-center justify-center h-32 border rounded-md bg-muted/50">
|
<div className="flex items-center justify-center h-32 border rounded-md bg-muted/50">
|
||||||
<Loader2 className="h-6 w-6 animate-spin" />
|
<Loader2 className="h-6 w-6 animate-spin" />
|
||||||
<span className="ml-2">Loading editor...</span>
|
<span className="ml-2">Loading editor...</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -190,24 +187,31 @@ export default function FormConvertSPIT() {
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [detail, setDetail] = useState<Detail | null>(null);
|
const [detail, setDetail] = useState<Detail | null>(null);
|
||||||
const [categories, setCategories] = useState<Category[]>([]);
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
|
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(
|
||||||
const [selectedFileType, setSelectedFileType] = useState<"original" | "rewrite">("original");
|
null
|
||||||
const [selectedWritingStyle, setSelectedWritingStyle] = useState("professional");
|
);
|
||||||
|
const [selectedFileType, setSelectedFileType] = useState<
|
||||||
|
"original" | "rewrite"
|
||||||
|
>("original");
|
||||||
|
const [selectedWritingStyle, setSelectedWritingStyle] =
|
||||||
|
useState("professional");
|
||||||
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
|
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
|
||||||
const [isGeneratingRewrite, setIsGeneratingRewrite] = useState(false);
|
const [isGeneratingRewrite, setIsGeneratingRewrite] = useState(false);
|
||||||
const [isLoadingRewrite, setIsLoadingRewrite] = useState(false);
|
const [isLoadingRewrite, setIsLoadingRewrite] = useState(false);
|
||||||
|
|
||||||
// Media state
|
// Media state
|
||||||
const [detailThumb, setDetailThumb] = useState<string[]>([]);
|
const [detailThumb, setDetailThumb] = useState<string[]>([]);
|
||||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||||
const [files, setFiles] = useState<FileType[]>([]);
|
const [files, setFiles] = useState<FileType[]>([]);
|
||||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||||
|
|
||||||
// Content rewrite state
|
// Content rewrite state
|
||||||
const [articleIds, setArticleIds] = useState<string[]>([]);
|
const [articleIds, setArticleIds] = useState<string[]>([]);
|
||||||
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(null);
|
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [articleBody, setArticleBody] = useState<string>("");
|
const [articleBody, setArticleBody] = useState<string>("");
|
||||||
|
|
||||||
// Form data state
|
// Form data state
|
||||||
const [tags, setTags] = useState<string[]>([]);
|
const [tags, setTags] = useState<string[]>([]);
|
||||||
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
const [publishedFor, setPublishedFor] = useState<string[]>([]);
|
||||||
|
|
@ -262,7 +266,7 @@ export default function FormConvertSPIT() {
|
||||||
// Auto-select "Pers Rilis" category if schedule type is 3
|
// Auto-select "Pers Rilis" category if schedule type is 3
|
||||||
const scheduleId = Cookies.get("scheduleId");
|
const scheduleId = Cookies.get("scheduleId");
|
||||||
const scheduleType = Cookies.get("scheduleType");
|
const scheduleType = Cookies.get("scheduleType");
|
||||||
|
|
||||||
if (scheduleId && scheduleType === "3") {
|
if (scheduleId && scheduleType === "3") {
|
||||||
const persRilisCategory = categories.find((cat: Category) =>
|
const persRilisCategory = categories.find((cat: Category) =>
|
||||||
cat.name.toLowerCase().includes("pers rilis")
|
cat.name.toLowerCase().includes("pers rilis")
|
||||||
|
|
@ -296,7 +300,7 @@ export default function FormConvertSPIT() {
|
||||||
try {
|
try {
|
||||||
const response = await detailSPIT(id);
|
const response = await detailSPIT(id);
|
||||||
const details = response?.data?.data;
|
const details = response?.data?.data;
|
||||||
|
|
||||||
if (!details) {
|
if (!details) {
|
||||||
throw new Error("Detail not found");
|
throw new Error("Detail not found");
|
||||||
}
|
}
|
||||||
|
|
@ -304,11 +308,11 @@ export default function FormConvertSPIT() {
|
||||||
setDetail(details);
|
setDetail(details);
|
||||||
setFiles(details.contentList || []);
|
setFiles(details.contentList || []);
|
||||||
setDetailThumb(
|
setDetailThumb(
|
||||||
(details.contentList || []).map((file: FileType) =>
|
(details.contentList || []).map(
|
||||||
file.contentFile || "default-image.jpg"
|
(file: FileType) => file.contentFile || "default-image.jpg"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialize file placements
|
// Initialize file placements
|
||||||
const fileCount = details.contentList?.length || 0;
|
const fileCount = details.contentList?.length || 0;
|
||||||
setFilePlacements(Array(fileCount).fill([]));
|
setFilePlacements(Array(fileCount).fill([]));
|
||||||
|
|
@ -317,7 +321,10 @@ export default function FormConvertSPIT() {
|
||||||
setValue("contentTitle", details.contentTitle || "");
|
setValue("contentTitle", details.contentTitle || "");
|
||||||
setValue("contentDescription", details.contentDescription || "");
|
setValue("contentDescription", details.contentDescription || "");
|
||||||
setValue("contentCreator", details.contentCreator || "");
|
setValue("contentCreator", details.contentCreator || "");
|
||||||
setValue("contentRewriteDescription", details.contentRewriteDescription || "");
|
setValue(
|
||||||
|
"contentRewriteDescription",
|
||||||
|
details.contentRewriteDescription || ""
|
||||||
|
);
|
||||||
|
|
||||||
// Set category and load category tags
|
// Set category and load category tags
|
||||||
if (details.categoryId) {
|
if (details.categoryId) {
|
||||||
|
|
@ -368,13 +375,13 @@ export default function FormConvertSPIT() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await generateDataRewrite(request);
|
const response = await generateDataRewrite(request);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response.message);
|
throw new Error(response.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newArticleId = response?.data?.data?.id;
|
const newArticleId = response?.data?.data?.id;
|
||||||
setArticleIds(prev => {
|
setArticleIds((prev) => {
|
||||||
const updated = [...prev];
|
const updated = [...prev];
|
||||||
if (updated.length < 3) {
|
if (updated.length < 3) {
|
||||||
updated.push(newArticleId);
|
updated.push(newArticleId);
|
||||||
|
|
@ -408,23 +415,26 @@ export default function FormConvertSPIT() {
|
||||||
try {
|
try {
|
||||||
setIsLoadingRewrite(true);
|
setIsLoadingRewrite(true);
|
||||||
setSelectedArticleId(articleId);
|
setSelectedArticleId(articleId);
|
||||||
|
|
||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = 20;
|
const maxRetries = 20;
|
||||||
|
|
||||||
while (retryCount < maxRetries) {
|
while (retryCount < maxRetries) {
|
||||||
const response = await getDetailArticle(articleId);
|
const response = await getDetailArticle(articleId);
|
||||||
const articleData = response?.data?.data;
|
const articleData = response?.data?.data;
|
||||||
|
|
||||||
if (articleData?.status === 2) {
|
if (articleData?.status === 2) {
|
||||||
const cleanArticleBody = articleData.articleBody?.replace(/<img[^>]*>/g, "");
|
const cleanArticleBody = articleData.articleBody?.replace(
|
||||||
|
/<img[^>]*>/g,
|
||||||
|
""
|
||||||
|
);
|
||||||
setArticleBody(cleanArticleBody || "");
|
setArticleBody(cleanArticleBody || "");
|
||||||
setValue("contentRewriteDescription", cleanArticleBody || "");
|
setValue("contentRewriteDescription", cleanArticleBody || "");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
retryCount++;
|
retryCount++;
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retryCount >= maxRetries) {
|
if (retryCount >= maxRetries) {
|
||||||
|
|
@ -447,40 +457,46 @@ export default function FormConvertSPIT() {
|
||||||
if (e.key === "Enter" && inputRef.current?.value.trim()) {
|
if (e.key === "Enter" && inputRef.current?.value.trim()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newTag = inputRef.current.value.trim();
|
const newTag = inputRef.current.value.trim();
|
||||||
|
|
||||||
if (!tags.includes(newTag)) {
|
if (!tags.includes(newTag)) {
|
||||||
setTags(prev => [...prev, newTag]);
|
setTags((prev) => [...prev, newTag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputRef.current.value = "";
|
inputRef.current.value = "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveTag = (index: number) => {
|
const handleRemoveTag = (index: number) => {
|
||||||
setTags(prev => prev.filter((_, i) => i !== index));
|
setTags((prev) => prev.filter((_, i) => i !== index));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePublishTargetChange = (optionId: string) => {
|
const handlePublishTargetChange = (optionId: string) => {
|
||||||
if (optionId === "all") {
|
if (optionId === "all") {
|
||||||
setPublishedFor(prev =>
|
setPublishedFor((prev) =>
|
||||||
prev.length === PUBLISH_OPTIONS.filter(opt => opt.id !== "all").length
|
prev.length === PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").length
|
||||||
? []
|
? []
|
||||||
: PUBLISH_OPTIONS.filter(opt => opt.id !== "all").map(opt => opt.id)
|
: PUBLISH_OPTIONS.filter((opt) => opt.id !== "all").map(
|
||||||
|
(opt) => opt.id
|
||||||
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setPublishedFor(prev =>
|
setPublishedFor((prev) =>
|
||||||
prev.includes(optionId)
|
prev.includes(optionId)
|
||||||
? prev.filter(id => id !== optionId && id !== "all")
|
? prev.filter((id) => id !== optionId && id !== "all")
|
||||||
: [...prev.filter(id => id !== "all"), optionId]
|
: [...prev.filter((id) => id !== "all"), optionId]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilePlacementChange = (fileIndex: number, placement: string, checked: boolean) => {
|
const handleFilePlacementChange = (
|
||||||
setFilePlacements(prev => {
|
fileIndex: number,
|
||||||
|
placement: string,
|
||||||
|
checked: boolean
|
||||||
|
) => {
|
||||||
|
setFilePlacements((prev) => {
|
||||||
const updated = [...prev];
|
const updated = [...prev];
|
||||||
const currentPlacements = updated[fileIndex] || [];
|
const currentPlacements = updated[fileIndex] || [];
|
||||||
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
if (placement === "all") {
|
if (placement === "all") {
|
||||||
updated[fileIndex] = ["all", "mabes", "polda", "international"];
|
updated[fileIndex] = ["all", "mabes", "polda", "international"];
|
||||||
|
|
@ -495,25 +511,27 @@ export default function FormConvertSPIT() {
|
||||||
if (placement === "all") {
|
if (placement === "all") {
|
||||||
updated[fileIndex] = [];
|
updated[fileIndex] = [];
|
||||||
} else {
|
} else {
|
||||||
const newPlacements = currentPlacements.filter(p => p !== placement);
|
const newPlacements = currentPlacements.filter(
|
||||||
|
(p) => p !== placement
|
||||||
|
);
|
||||||
if (newPlacements.length === 3 && newPlacements.includes("all")) {
|
if (newPlacements.length === 3 && newPlacements.includes("all")) {
|
||||||
updated[fileIndex] = newPlacements.filter(p => p !== "all");
|
updated[fileIndex] = newPlacements.filter((p) => p !== "all");
|
||||||
} else {
|
} else {
|
||||||
updated[fileIndex] = newPlacements;
|
updated[fileIndex] = newPlacements;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectAllPlacements = (placement: string, checked: boolean) => {
|
const handleSelectAllPlacements = (placement: string, checked: boolean) => {
|
||||||
setFilePlacements(prev =>
|
setFilePlacements((prev) =>
|
||||||
prev.map(filePlacements =>
|
prev.map((filePlacements) =>
|
||||||
checked
|
checked
|
||||||
? Array.from(new Set([...filePlacements, placement]))
|
? Array.from(new Set([...filePlacements, placement]))
|
||||||
: filePlacements.filter(p => p !== placement)
|
: filePlacements.filter((p) => p !== placement)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -523,7 +541,7 @@ export default function FormConvertSPIT() {
|
||||||
console.log("filePlacements : ", filePlacements);
|
console.log("filePlacements : ", filePlacements);
|
||||||
for (let i = 0; i < filePlacements.length; i++) {
|
for (let i = 0; i < filePlacements.length; i++) {
|
||||||
if (filePlacements[i].length > 0) {
|
if (filePlacements[i].length > 0) {
|
||||||
const placements = filePlacements[i];
|
const placements = filePlacements[i];
|
||||||
placementData.push({
|
placementData.push({
|
||||||
mediaFileId: files[i].contentId,
|
mediaFileId: files[i].contentId,
|
||||||
placements: placements.join(","),
|
placements: placements.join(","),
|
||||||
|
|
@ -533,15 +551,38 @@ export default function FormConvertSPIT() {
|
||||||
return placementData;
|
return placementData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkPlacement = (data: any) => {
|
||||||
|
let temp = true;
|
||||||
|
for (const element of data) {
|
||||||
|
if (element.length < 1) {
|
||||||
|
temp = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
};
|
||||||
|
|
||||||
// Form submission
|
// Form submission
|
||||||
const onSubmit = async (data: FormData) => {
|
const onSubmit = async (data: FormData) => {
|
||||||
|
if (!checkPlacement(filePlacements)) {
|
||||||
|
error("Select File Placement");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publishedFor.length < 1) {
|
||||||
|
error("Select Publish Target");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
|
||||||
const description = selectedFileType === "original"
|
const description =
|
||||||
? data.contentDescription
|
selectedFileType === "original"
|
||||||
: data.contentRewriteDescription;
|
? data.contentDescription
|
||||||
|
: data.contentRewriteDescription;
|
||||||
|
|
||||||
const requestData = {
|
const requestData = {
|
||||||
spitId: id,
|
spitId: id,
|
||||||
title: data.contentTitle,
|
title: data.contentTitle,
|
||||||
|
|
@ -555,7 +596,7 @@ export default function FormConvertSPIT() {
|
||||||
};
|
};
|
||||||
|
|
||||||
await convertSPIT(requestData);
|
await convertSPIT(requestData);
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
text: "Data saved successfully",
|
text: "Data saved successfully",
|
||||||
|
|
@ -593,7 +634,7 @@ export default function FormConvertSPIT() {
|
||||||
try {
|
try {
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
await deleteSPIT(id);
|
await deleteSPIT(id);
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
text: "Content deleted successfully",
|
text: "Content deleted successfully",
|
||||||
|
|
@ -633,17 +674,15 @@ export default function FormConvertSPIT() {
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight">SPIT Convert Form</h1>
|
<h1 className="text-3xl font-bold tracking-tight">
|
||||||
|
SPIT Convert Form
|
||||||
|
</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Convert and manage your SPIT content
|
Convert and manage your SPIT content
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button variant="outline" size="sm" onClick={() => router.back()}>
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => router.back()}
|
|
||||||
>
|
|
||||||
<XCircle className="h-4 w-4 mr-2" />
|
<XCircle className="h-4 w-4 mr-2" />
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -737,7 +776,9 @@ export default function FormConvertSPIT() {
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={selectedFileType}
|
value={selectedFileType}
|
||||||
onValueChange={(value: "original" | "rewrite") => setSelectedFileType(value)}
|
onValueChange={(value: "original" | "rewrite") =>
|
||||||
|
setSelectedFileType(value)
|
||||||
|
}
|
||||||
className="grid grid-cols-2 gap-4"
|
className="grid grid-cols-2 gap-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|
@ -792,7 +833,9 @@ export default function FormConvertSPIT() {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleRewriteClick}
|
onClick={handleRewriteClick}
|
||||||
disabled={isGeneratingRewrite || !detail?.contentDescription}
|
disabled={
|
||||||
|
isGeneratingRewrite || !detail?.contentDescription
|
||||||
|
}
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
className="bg-blue-600 hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
{isGeneratingRewrite ? (
|
{isGeneratingRewrite ? (
|
||||||
|
|
@ -812,14 +855,19 @@ export default function FormConvertSPIT() {
|
||||||
<Button
|
<Button
|
||||||
key={articleId}
|
key={articleId}
|
||||||
type="button"
|
type="button"
|
||||||
variant={selectedArticleId === articleId ? "default" : "outline"}
|
variant={
|
||||||
|
selectedArticleId === articleId
|
||||||
|
? "default"
|
||||||
|
: "outline"
|
||||||
|
}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleArticleSelect(articleId)}
|
onClick={() => handleArticleSelect(articleId)}
|
||||||
disabled={isLoadingRewrite}
|
disabled={isLoadingRewrite}
|
||||||
>
|
>
|
||||||
{isLoadingRewrite && selectedArticleId === articleId && (
|
{isLoadingRewrite &&
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
selectedArticleId === articleId && (
|
||||||
)}
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
)}
|
||||||
Narrative {index + 1}
|
Narrative {index + 1}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
|
@ -873,7 +921,7 @@ export default function FormConvertSPIT() {
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
|
|
||||||
<Swiper
|
<Swiper
|
||||||
onSwiper={setThumbsSwiper}
|
onSwiper={setThumbsSwiper}
|
||||||
slidesPerView={8}
|
slidesPerView={8}
|
||||||
|
|
@ -909,14 +957,23 @@ export default function FormConvertSPIT() {
|
||||||
{files.length > 1 && (
|
{files.length > 1 && (
|
||||||
<div className="flex flex-wrap gap-4 p-4 bg-muted/50 rounded-lg">
|
<div className="flex flex-wrap gap-4 p-4 bg-muted/50 rounded-lg">
|
||||||
{PLACEMENT_OPTIONS.map((option) => (
|
{PLACEMENT_OPTIONS.map((option) => (
|
||||||
<div key={option.value} className="flex items-center space-x-2">
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={`select-all-${option.value}`}
|
id={`select-all-${option.value}`}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
handleSelectAllPlacements(option.value, Boolean(checked))
|
handleSelectAllPlacements(
|
||||||
|
option.value,
|
||||||
|
Boolean(checked)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={`select-all-${option.value}`} className="text-sm">
|
<Label
|
||||||
|
htmlFor={`select-all-${option.value}`}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
All {option.label}
|
All {option.label}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -939,12 +996,21 @@ export default function FormConvertSPIT() {
|
||||||
<p className="font-medium text-sm">{file.fileName}</p>
|
<p className="font-medium text-sm">{file.fileName}</p>
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{PLACEMENT_OPTIONS.map((option) => (
|
{PLACEMENT_OPTIONS.map((option) => (
|
||||||
<div key={option.value} className="flex items-center space-x-2">
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id={`${file.contentId}-${option.value}`}
|
id={`${file.contentId}-${option.value}`}
|
||||||
checked={filePlacements[index]?.includes(option.value)}
|
checked={filePlacements[index]?.includes(
|
||||||
|
option.value
|
||||||
|
)}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
handleFilePlacementChange(index, option.value, Boolean(checked))
|
handleFilePlacementChange(
|
||||||
|
index,
|
||||||
|
option.value,
|
||||||
|
Boolean(checked)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
|
|
@ -1070,10 +1136,14 @@ export default function FormConvertSPIT() {
|
||||||
id={option.id}
|
id={option.id}
|
||||||
checked={
|
checked={
|
||||||
option.id === "all"
|
option.id === "all"
|
||||||
? publishedFor.length === PUBLISH_OPTIONS.filter(opt => opt.id !== "all").length
|
? publishedFor.length ===
|
||||||
|
PUBLISH_OPTIONS.filter((opt) => opt.id !== "all")
|
||||||
|
.length
|
||||||
: publishedFor.includes(option.id)
|
: publishedFor.includes(option.id)
|
||||||
}
|
}
|
||||||
onCheckedChange={() => handlePublishTargetChange(option.id)}
|
onCheckedChange={() =>
|
||||||
|
handlePublishTargetChange(option.id)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={option.id} className="text-sm">
|
<Label htmlFor={option.id} className="text-sm">
|
||||||
{option.label}
|
{option.label}
|
||||||
|
|
|
||||||
|
|
@ -606,7 +606,7 @@ export default function FormVideoUpdate() {
|
||||||
|
|
||||||
const fileList = files.map((file: any) => (
|
const fileList = files.map((file: any) => (
|
||||||
<div
|
<div
|
||||||
key={file.id} // Gunakan ID file sebagai key
|
key={file.id}
|
||||||
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
className="flex justify-between border px-3.5 py-3 my-6 rounded-md"
|
||||||
>
|
>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
|
|
@ -995,7 +995,7 @@ export default function FormVideoUpdate() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 px-3 space-y-2">
|
{/* <div className="mt-3 px-3 space-y-2">
|
||||||
<Label>{t("preview", { defaultValue: "Preview" })}</Label>
|
<Label>{t("preview", { defaultValue: "Preview" })}</Label>
|
||||||
<Card className="mt-2">
|
<Card className="mt-2">
|
||||||
<img
|
<img
|
||||||
|
|
@ -1004,7 +1004,39 @@ export default function FormVideoUpdate() {
|
||||||
className="w-full h-auto rounded"
|
className="w-full h-auto rounded"
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
</div> */}
|
||||||
|
<div className="mt-3 px-3 space-y-2">
|
||||||
|
<Label>{t("preview", { defaultValue: "Preview" })}</Label>
|
||||||
|
<Input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={(e) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
setSelectedFiles([file]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="dark:border dark:border-gray-500 dark:rounded-lg"
|
||||||
|
/>
|
||||||
|
{selectedFiles.length > 0 ? (
|
||||||
|
<Card className="mt-2">
|
||||||
|
<img
|
||||||
|
src={URL.createObjectURL(selectedFiles[0])}
|
||||||
|
alt="Thumbnail Baru"
|
||||||
|
className="w-full h-auto rounded"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
) : (
|
||||||
|
<Card className="mt-2">
|
||||||
|
<img
|
||||||
|
src={detail?.thumbnailLink}
|
||||||
|
alt="Thumbnail Lama"
|
||||||
|
className="w-full h-auto rounded"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-3 py-3">
|
<div className="px-3 py-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>{t("tags", { defaultValue: "Tags" })}</Label>
|
<Label>{t("tags", { defaultValue: "Tags" })}</Label>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import Coverage from "./coverage";
|
||||||
import Division from "./division";
|
import Division from "./division";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
import { useRouter } from "@/i18n/routing";
|
||||||
|
import { Icon } from "../ui/icon";
|
||||||
|
|
||||||
const AreaCoverageWorkUnits = () => {
|
const AreaCoverageWorkUnits = () => {
|
||||||
const [openPolda, setOpenPolda] = useState(false);
|
const [openPolda, setOpenPolda] = useState(false);
|
||||||
|
|
@ -22,6 +24,16 @@ const AreaCoverageWorkUnits = () => {
|
||||||
const t = useTranslations("LandingPage");
|
const t = useTranslations("LandingPage");
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const locale = params?.locale;
|
const locale = params?.locale;
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const router = useRouter();
|
||||||
|
const poldaName = params?.polda_name;
|
||||||
|
const satkerName = params?.satker_name;
|
||||||
|
|
||||||
|
let prefixPath = poldaName
|
||||||
|
? `/polda/${poldaName}`
|
||||||
|
: satkerName
|
||||||
|
? `/satker/${satkerName}`
|
||||||
|
: "";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (openPolda || openSatker) {
|
if (openPolda || openSatker) {
|
||||||
|
|
@ -42,9 +54,28 @@ const AreaCoverageWorkUnits = () => {
|
||||||
<h2 className="text-start text-lg md:text-xl font-bold text-[#bb3523] border-b-2 border-[#bb3523] mb-4 uppercase">
|
<h2 className="text-start text-lg md:text-xl font-bold text-[#bb3523] border-b-2 border-[#bb3523] mb-4 uppercase">
|
||||||
{t("areaCoverage", { defaultValue: "Area Coverage" })}
|
{t("areaCoverage", { defaultValue: "Area Coverage" })}
|
||||||
</h2>
|
</h2>
|
||||||
|
{/* search */}
|
||||||
|
<div className="flex flex-row gap-2 mb-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t("searchCoverageHere", {
|
||||||
|
defaultValue: "Search Coverage Here",
|
||||||
|
})}
|
||||||
|
className="w-full py-4 px-2 text-sm text-gray-700 dark:text-gray-100 focus:outline-none rounded-md"
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
router.push(prefixPath + `/regional/filter?title=${search}`)
|
||||||
|
}
|
||||||
|
className="flex justify-center items-center px-6 w-full lg:w-[20%] py-4 bg-[#bb3523] gap-2 text-white rounded-lg hover:bg-red-700 text-[14px]"
|
||||||
|
>
|
||||||
|
{t("searchCoverage", { defaultValue: "Search Coverage" })}
|
||||||
|
<Icon icon="ri:arrow-right-s-line" fontSize={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="flex flex-col justify-center lg:flex-row gap-8 ">
|
<div className="flex flex-col justify-center lg:flex-row gap-8 ">
|
||||||
{/* POLDA */}
|
{/* POLDA */}
|
||||||
|
|
||||||
<Dialog open={openPolda} onOpenChange={setOpenPolda}>
|
<Dialog open={openPolda} onOpenChange={setOpenPolda}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<button
|
<button
|
||||||
|
|
@ -59,7 +90,9 @@ const AreaCoverageWorkUnits = () => {
|
||||||
src="/assets/indo.png"
|
src="/assets/indo.png"
|
||||||
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] "
|
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] "
|
||||||
/>
|
/>
|
||||||
<p className="text-base font-bold">{t("regionalPolice", { defaultValue: "Regional Police" })}</p>
|
<p className="text-base font-bold">
|
||||||
|
{t("regionalPolice", { defaultValue: "Regional Police" })}
|
||||||
|
</p>
|
||||||
</button>
|
</button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
|
|
@ -69,7 +102,9 @@ const AreaCoverageWorkUnits = () => {
|
||||||
>
|
>
|
||||||
<DialogHeader className="flex flex-col justify-center">
|
<DialogHeader className="flex flex-col justify-center">
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<p className="text-center">{t("regionalPolice", { defaultValue: "Regional Police" })}</p>
|
<p className="text-center">
|
||||||
|
{t("regionalPolice", { defaultValue: "Regional Police" })}
|
||||||
|
</p>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<div className="h-1 w-[150px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
<div className="h-1 w-[150px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
||||||
|
|
@ -89,7 +124,6 @@ const AreaCoverageWorkUnits = () => {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* SATKER */}
|
{/* SATKER */}
|
||||||
|
|
||||||
<Dialog open={openSatker} onOpenChange={setOpenSatker}>
|
<Dialog open={openSatker} onOpenChange={setOpenSatker}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<button className="flex flex-col gap-2 justify-center items-center shadow-lg group rounded-xl py-5 w-full border-2 border-transparent hover:border-[#bb3523] transition-all duration-300">
|
<button className="flex flex-col gap-2 justify-center items-center shadow-lg group rounded-xl py-5 w-full border-2 border-transparent hover:border-[#bb3523] transition-all duration-300">
|
||||||
|
|
@ -101,13 +135,17 @@ const AreaCoverageWorkUnits = () => {
|
||||||
src="/assets/logo-polri.png"
|
src="/assets/logo-polri.png"
|
||||||
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] transition-transform duration-300"
|
className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] transition-transform duration-300"
|
||||||
/>
|
/>
|
||||||
<p className="text-base font-bold">{t("policeDivision", { defaultValue: "Police Division" })}</p>
|
<p className="text-base font-bold">
|
||||||
|
{t("policeDivision", { defaultValue: "Police Division" })}
|
||||||
|
</p>
|
||||||
</button>
|
</button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent size="md" data-lenis-prevent>
|
<DialogContent size="md" data-lenis-prevent>
|
||||||
<DialogHeader className="flex flex-col justify-center">
|
<DialogHeader className="flex flex-col justify-center">
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<p className="text-center">{t("policeDivision", { defaultValue: "Police Division" })}</p>
|
<p className="text-center">
|
||||||
|
{t("policeDivision", { defaultValue: "Police Division" })}
|
||||||
|
</p>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<div className="h-1 w-[150px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
<div className="h-1 w-[150px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "@/components/ui/carousel";
|
} from "@/components/ui/carousel";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link, usePathname } from "@/i18n/routing";
|
import { Link, usePathname } from "@/i18n/routing";
|
||||||
import { listData } from "@/service/landing/landing";
|
import { listData, listDataRegional } from "@/service/landing/landing";
|
||||||
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { pages } from "next/dist/build/templates/app-page";
|
import { pages } from "next/dist/build/templates/app-page";
|
||||||
|
|
@ -42,6 +42,50 @@ export default function FilterAudioComponent(props: {
|
||||||
const categorie = searchParams?.get("category");
|
const categorie = searchParams?.get("category");
|
||||||
const group = searchParams?.get("group");
|
const group = searchParams?.get("group");
|
||||||
const [totalContent, setTotalContent] = useState();
|
const [totalContent, setTotalContent] = useState();
|
||||||
|
const isRegional = group || asPath.includes("/polda/");
|
||||||
|
const searchType = isRegional ? "regional" : "filter";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchType === "regional") {
|
||||||
|
getDataRegional();
|
||||||
|
} else {
|
||||||
|
getDataAll();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
title,
|
||||||
|
categoryFilter,
|
||||||
|
categorie,
|
||||||
|
group,
|
||||||
|
startDateString,
|
||||||
|
endDateString,
|
||||||
|
monthYearFilter,
|
||||||
|
sortByOpt,
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function getDataRegional() {
|
||||||
|
let startDateFilter = startDateString ? startDateString : "";
|
||||||
|
let endDateFilter = endDateString ? endDateString : "";
|
||||||
|
let monthFilter = monthYearFilter ? monthYearFilter : "";
|
||||||
|
|
||||||
|
const response = await listDataRegional(
|
||||||
|
"4",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
startDateFilter,
|
||||||
|
endDateFilter,
|
||||||
|
monthFilter
|
||||||
|
? getOnlyMonthAndYear(monthFilter)?.split("/")[0]?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthFilter ? getOnlyMonthAndYear(monthFilter)?.split("/")[1] : ""
|
||||||
|
);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const contentData = data?.content;
|
||||||
|
setNewContent(contentData);
|
||||||
|
setTotalData(data?.totalElements);
|
||||||
|
// setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDataAll();
|
getDataAll();
|
||||||
|
|
@ -137,7 +181,7 @@ export default function FilterAudioComponent(props: {
|
||||||
|
|
||||||
return newContent?.length > 0 ? (
|
return newContent?.length > 0 ? (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<p>{`Audio(${totalContent})`}</p>
|
<p>{`Audio (${totalContent})`}</p>
|
||||||
<Carousel className="w-full max-w-7xl mx-auto">
|
<Carousel className="w-full max-w-7xl mx-auto">
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{newContent?.map((audio: any) => (
|
{newContent?.map((audio: any) => (
|
||||||
|
|
@ -145,7 +189,7 @@ export default function FilterAudioComponent(props: {
|
||||||
<div className="flex flex-row gap-6">
|
<div className="flex flex-row gap-6">
|
||||||
<Link
|
<Link
|
||||||
href={`/audio/detail/${audio?.slug}`}
|
href={`/audio/detail/${audio?.slug}`}
|
||||||
className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow-md rounded-lg p-4 gap-4 w-full"
|
className="flex flex-col sm:flex-row items-center bg-white dark:bg-gray-800 cursor-pointer shadow- rounded-lg p-2 gap-4 w-full"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16">
|
<div className="flex items-center justify-center bg-red-500 text-white rounded-lg w-16 h-8 lg:h-16">
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -163,11 +207,11 @@ export default function FilterAudioComponent(props: {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm">
|
<div className="text-gray-500 dark:text-gray-400 flex flex-row text-sm items-center">
|
||||||
{formatDateToIndonesian(new Date(audio?.createdAt))}{" "}
|
{formatDateToIndonesian(new Date(audio?.createdAt))}{" "}
|
||||||
{audio?.timezone ? audio?.timezone : "WIB"} |{" "}
|
{audio?.timezone ? audio?.timezone : "WIB"} |
|
||||||
<Icon icon="formkit:eye" width="15" height="15" />{" "}
|
<Icon icon="formkit:eye" width="15" height="15" />
|
||||||
{audio?.clickCount}{" "}
|
{audio?.clickCount}{" "}
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
|
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">
|
||||||
{audio?.title}
|
{audio?.title}
|
||||||
|
|
@ -178,9 +222,27 @@ export default function FilterAudioComponent(props: {
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
<CarouselPrevious />
|
<CarouselPrevious className="-ml-0" />
|
||||||
<CarouselNext />
|
<CarouselNext className="-mr-0" />
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
<div className="flex justify-center mt-1 mb-6">
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
`${
|
||||||
|
asPath.includes("/polda/")
|
||||||
|
? `/${asPath.split("/")[1]}/${asPath.split("/")[2]}`
|
||||||
|
: ""
|
||||||
|
}/audio/` +
|
||||||
|
`${group ? "regional?" : "filter?"}` +
|
||||||
|
`sortBy=${sortBy === "popular" ? "popular" : "latest"}` +
|
||||||
|
`${title ? `&title=${title.toLowerCase()}` : ""}` +
|
||||||
|
`${categorie ? `&category=${categorie}` : ""}`
|
||||||
|
}
|
||||||
|
className="border border-red-500 text-red-500 hover:bg-red-500 hover:text-white transition-colors duration-200 px-4 py-2 rounded-md text-sm"
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "@/components/ui/carousel";
|
} from "@/components/ui/carousel";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link, usePathname } from "@/i18n/routing";
|
import { Link, usePathname } from "@/i18n/routing";
|
||||||
import { listData } from "@/service/landing/landing";
|
import { listData, listDataRegional } from "@/service/landing/landing";
|
||||||
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { pages } from "next/dist/build/templates/app-page";
|
import { pages } from "next/dist/build/templates/app-page";
|
||||||
|
|
@ -43,8 +43,15 @@ export default function FilterDocumentComponent(props: {
|
||||||
const group = searchParams?.get("group");
|
const group = searchParams?.get("group");
|
||||||
const [totalContent, setTotalContent] = useState();
|
const [totalContent, setTotalContent] = useState();
|
||||||
|
|
||||||
|
const isRegional = group || asPath.includes("/polda/");
|
||||||
|
const searchType = isRegional ? "regional" : "filter";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDataAll();
|
if (searchType === "regional") {
|
||||||
|
getDataRegional();
|
||||||
|
} else {
|
||||||
|
getDataAll();
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
title,
|
title,
|
||||||
categoryFilter,
|
categoryFilter,
|
||||||
|
|
@ -56,6 +63,31 @@ export default function FilterDocumentComponent(props: {
|
||||||
sortByOpt,
|
sortByOpt,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
async function getDataRegional() {
|
||||||
|
let startDateFilter = startDateString ? startDateString : "";
|
||||||
|
let endDateFilter = endDateString ? endDateString : "";
|
||||||
|
let monthFilter = monthYearFilter ? monthYearFilter : "";
|
||||||
|
|
||||||
|
const response = await listDataRegional(
|
||||||
|
"3",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
startDateFilter,
|
||||||
|
endDateFilter,
|
||||||
|
monthFilter
|
||||||
|
? getOnlyMonthAndYear(monthFilter)?.split("/")[0]?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthFilter ? getOnlyMonthAndYear(monthFilter)?.split("/")[1] : ""
|
||||||
|
);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const contentData = data?.content;
|
||||||
|
setNewContent(contentData);
|
||||||
|
setTotalData(data?.totalElements);
|
||||||
|
// setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
async function getDataAll() {
|
async function getDataAll() {
|
||||||
if (asPath?.includes("/polda/") == true) {
|
if (asPath?.includes("/polda/") == true) {
|
||||||
if (asPath?.split("/")[2] !== "[polda_name]") {
|
if (asPath?.split("/")[2] !== "[polda_name]") {
|
||||||
|
|
@ -137,7 +169,7 @@ export default function FilterDocumentComponent(props: {
|
||||||
|
|
||||||
return newContent?.length > 0 ? (
|
return newContent?.length > 0 ? (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<p>{`Document(${totalContent})`}</p>
|
<p>{`Document (${totalContent})`}</p>
|
||||||
<Carousel className="w-full max-w-7xl mx-auto">
|
<Carousel className="w-full max-w-7xl mx-auto">
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{newContent?.map((text: any) => (
|
{newContent?.map((text: any) => (
|
||||||
|
|
@ -164,12 +196,12 @@ export default function FilterDocumentComponent(props: {
|
||||||
|
|
||||||
<div className="flex w-full pr-10 flex-col flex-1">
|
<div className="flex w-full pr-10 flex-col flex-1">
|
||||||
<div className="text-gray-500 dark:text-gray-400 flex flex-row items-center text-xs gap-0 lg:gap-1 mt-1 lg:text-sm">
|
<div className="text-gray-500 dark:text-gray-400 flex flex-row items-center text-xs gap-0 lg:gap-1 mt-1 lg:text-sm">
|
||||||
{formatDateToIndonesian(new Date(text?.createdAt))}
|
{formatDateToIndonesian(new Date(text?.createdAt))}
|
||||||
{text?.timezone ? text?.timezone : "WIB"}|
|
{text?.timezone ? text?.timezone : "WIB"} |
|
||||||
<Icon icon="formkit:eye" width="15" height="15" />
|
<Icon icon="formkit:eye" width="15" height="15" />
|
||||||
{text?.clickCount}
|
{text?.clickCount}
|
||||||
</div>
|
</div>
|
||||||
<div className="font-semibold text-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible ">
|
<div className="font-semibold tet-gray-900 dark:text-white mt-1 text-sm h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible ">
|
||||||
{text?.title}
|
{text?.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
|
<div className="flex gap-2 items-center text-sm text-red-500 dark:text-red-500">
|
||||||
|
|
@ -192,9 +224,25 @@ export default function FilterDocumentComponent(props: {
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
<CarouselPrevious />
|
<CarouselPrevious className="-ml-0" />
|
||||||
<CarouselNext />
|
<CarouselNext className="-mr-0" />
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
<div className="flex justify-center mt-1 mb-6">
|
||||||
|
<Link
|
||||||
|
href={`${
|
||||||
|
asPath.includes("/polda/")
|
||||||
|
? `/${asPath.split("/")[1]}/${asPath.split("/")[2]}`
|
||||||
|
: ""
|
||||||
|
}/document/${group ? "filter" : "regional"}?sortBy=${
|
||||||
|
sortBy || "latest"
|
||||||
|
}${title ? `&title=${title.toLowerCase()}` : ""}${
|
||||||
|
categorie ? `&category=${categorie}` : ""
|
||||||
|
}${group ? `&group=${group}` : ""}`}
|
||||||
|
className="border border-red-500 text-red-500 hover:bg-red-500 hover:text-white transition-colors duration-200 px-4 py-2 rounded-md text-sm"
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "@/components/ui/carousel";
|
} from "@/components/ui/carousel";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link, usePathname } from "@/i18n/routing";
|
import { Link, usePathname } from "@/i18n/routing";
|
||||||
import { listData } from "@/service/landing/landing";
|
import { listData, listDataRegional } from "@/service/landing/landing";
|
||||||
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { pages } from "next/dist/build/templates/app-page";
|
import { pages } from "next/dist/build/templates/app-page";
|
||||||
|
|
@ -42,8 +42,15 @@ export default function FilterImageComponent(props: {
|
||||||
const group = searchParams?.get("group");
|
const group = searchParams?.get("group");
|
||||||
const [totalContent, setTotalContent] = useState();
|
const [totalContent, setTotalContent] = useState();
|
||||||
|
|
||||||
|
const isRegional = group || asPath.includes("/polda/");
|
||||||
|
const searchType = isRegional ? "regional" : "filter";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDataAll();
|
if (searchType === "regional") {
|
||||||
|
getDataRegional();
|
||||||
|
} else {
|
||||||
|
getDataAll();
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
title,
|
title,
|
||||||
categoryFilter,
|
categoryFilter,
|
||||||
|
|
@ -134,10 +141,48 @@ export default function FilterImageComponent(props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getDataRegional() {
|
||||||
|
let startDateFilter = startDateString ? startDateString : "";
|
||||||
|
let endDateFilter = endDateString ? endDateString : "";
|
||||||
|
let monthFilter = monthYearFilter ? monthYearFilter : "";
|
||||||
|
|
||||||
|
const response = await listDataRegional(
|
||||||
|
"1",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
startDateFilter,
|
||||||
|
endDateFilter,
|
||||||
|
monthFilter
|
||||||
|
? getOnlyMonthAndYear(monthFilter)?.split("/")[0]?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthFilter ? getOnlyMonthAndYear(monthFilter)?.split("/")[1] : ""
|
||||||
|
);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const contentData = data?.content;
|
||||||
|
setNewContent(contentData);
|
||||||
|
setTotalData(data?.totalElements);
|
||||||
|
// setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePath = asPath.includes("/polda/")
|
||||||
|
? `/${asPath.split("/")[1]}/${asPath.split("/")[2]}`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const type = group || asPath.includes("/polda/") ? "regional?" : "filter?";
|
||||||
|
const sort = sortByOpt === "popular" ? "sortBy=popular" : "sortBy=latest";
|
||||||
|
const titleParam = title ? `&title=${title?.toLowerCase()}` : "";
|
||||||
|
const categoryParam = categorie ? `&category=${categorie}` : "";
|
||||||
|
const fullQuery = `${type}${sort}${titleParam}${categoryParam}${
|
||||||
|
startDateString ? `&startDate=${startDateString}` : ""
|
||||||
|
}${endDateString ? `&endDate=${endDateString}` : ""}`;
|
||||||
|
const href = `${basePath}/image/${fullQuery}`;
|
||||||
|
|
||||||
return newContent?.length > 0 ? (
|
return newContent?.length > 0 ? (
|
||||||
<div className="flex flex-col gap-3 w-full">
|
<div className="flex flex-col gap-3 w-full">
|
||||||
<p>{`Foto(${totalContent})`}</p>
|
<p>{`Foto (${totalContent})`}</p>
|
||||||
<Carousel className="w-full max-w-7xl mx-auto">
|
<Carousel className="w-full max-w-7xl mx-auto mt-3">
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{newContent?.map((image: any) => (
|
{newContent?.map((image: any) => (
|
||||||
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
|
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
|
||||||
|
|
@ -164,9 +209,17 @@ export default function FilterImageComponent(props: {
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
<CarouselPrevious />
|
<CarouselPrevious className="-ml-0" />
|
||||||
<CarouselNext />
|
<CarouselNext className="-mr-0" />
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
<div className="flex justify-center mt-1 mb-6">
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
className="border border-red-500 text-red-500 hover:bg-red-500 text-sm hover:text-white px-4 py-2 rounded transition duration-200"
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
} from "@/components/ui/carousel";
|
} from "@/components/ui/carousel";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
import { Link, usePathname } from "@/i18n/routing";
|
import { Link, usePathname } from "@/i18n/routing";
|
||||||
import { listData } from "@/service/landing/landing";
|
import { listData, listDataRegional } from "@/service/landing/landing";
|
||||||
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
import { formatDateToIndonesian, getOnlyMonthAndYear } from "@/utils/globals";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import { pages } from "next/dist/build/templates/app-page";
|
import { pages } from "next/dist/build/templates/app-page";
|
||||||
|
|
@ -43,8 +43,15 @@ export default function FilterVideoComponent(props: {
|
||||||
const group = searchParams?.get("group");
|
const group = searchParams?.get("group");
|
||||||
const [totalContent, setTotalContent] = useState();
|
const [totalContent, setTotalContent] = useState();
|
||||||
|
|
||||||
|
const isRegional = group || asPath.includes("/polda/");
|
||||||
|
const searchType = isRegional ? "regional" : "filter";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDataAll();
|
if (searchType === "regional") {
|
||||||
|
getDataRegional();
|
||||||
|
} else {
|
||||||
|
getDataAll();
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
title,
|
title,
|
||||||
categoryFilter,
|
categoryFilter,
|
||||||
|
|
@ -56,6 +63,31 @@ export default function FilterVideoComponent(props: {
|
||||||
sortByOpt,
|
sortByOpt,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
async function getDataRegional() {
|
||||||
|
let startDateFilter = startDateString ? startDateString : "";
|
||||||
|
let endDateFilter = endDateString ? endDateString : "";
|
||||||
|
let monthFilter = monthYearFilter ? monthYearFilter : "";
|
||||||
|
|
||||||
|
const response = await listDataRegional(
|
||||||
|
"2",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
startDateFilter,
|
||||||
|
endDateFilter,
|
||||||
|
monthFilter
|
||||||
|
? getOnlyMonthAndYear(monthFilter)?.split("/")[0]?.replace("0", "")
|
||||||
|
: "",
|
||||||
|
monthFilter ? getOnlyMonthAndYear(monthFilter)?.split("/")[1] : ""
|
||||||
|
);
|
||||||
|
const data = response?.data?.data;
|
||||||
|
const contentData = data?.content;
|
||||||
|
setNewContent(contentData);
|
||||||
|
setTotalData(data?.totalElements);
|
||||||
|
// setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
async function getDataAll() {
|
async function getDataAll() {
|
||||||
if (asPath?.includes("/polda/") == true) {
|
if (asPath?.includes("/polda/") == true) {
|
||||||
if (asPath?.split("/")[2] !== "[polda_name]") {
|
if (asPath?.split("/")[2] !== "[polda_name]") {
|
||||||
|
|
@ -137,7 +169,7 @@ export default function FilterVideoComponent(props: {
|
||||||
|
|
||||||
return newContent?.length > 0 ? (
|
return newContent?.length > 0 ? (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<p>{`Audio Visual(${totalContent})`}</p>
|
<p>{`Audio Visual (${totalContent})`}</p>
|
||||||
<Carousel className="w-full max-w-7xl mx-auto">
|
<Carousel className="w-full max-w-7xl mx-auto">
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{newContent?.map((video: any) => (
|
{newContent?.map((video: any) => (
|
||||||
|
|
@ -165,9 +197,35 @@ export default function FilterVideoComponent(props: {
|
||||||
</CarouselItem>
|
</CarouselItem>
|
||||||
))}
|
))}
|
||||||
</CarouselContent>
|
</CarouselContent>
|
||||||
<CarouselPrevious />
|
<CarouselPrevious className="-ml-0" />
|
||||||
<CarouselNext />
|
<CarouselNext className="-mr-0" />
|
||||||
</Carousel>
|
</Carousel>
|
||||||
|
<div className="flex justify-center mt-1 mb-6">
|
||||||
|
<Link
|
||||||
|
href={`${
|
||||||
|
asPath.includes("/polda/")
|
||||||
|
? `/${asPath.split("/")[1]}/${asPath.split("/")[2]}`
|
||||||
|
: ""
|
||||||
|
}/video/${
|
||||||
|
group || asPath.includes("/polda/") ? "regional?" : "filter?"
|
||||||
|
}${sortByOpt === "popular" ? "sortBy=popular" : "sortBy=latest"}${
|
||||||
|
title ? `&title=${title.toLowerCase()}` : ""
|
||||||
|
}${categorie ? `&category=${categorie}` : ""}${
|
||||||
|
startDateString ? `&startDate=${startDateString}` : ""
|
||||||
|
}${endDateString ? `&endDate=${endDateString}` : ""}${
|
||||||
|
monthYearFilter
|
||||||
|
? `&month=${getOnlyMonthAndYear(monthYearFilter)?.split("/")[0]}`
|
||||||
|
: ""
|
||||||
|
}${
|
||||||
|
monthYearFilter
|
||||||
|
? `&year=${getOnlyMonthAndYear(monthYearFilter)?.split("/")[1]}`
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
className="border border-red-500 text-red-500 hover:bg-red-500 hover:text-white px-4 py-2 text-sm rounded transition duration-200"
|
||||||
|
>
|
||||||
|
Lihat Semua
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import router from "next/router";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -13,7 +12,6 @@ import {
|
||||||
} from "../ui/select";
|
} from "../ui/select";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { getHeroData } from "@/service/landing/landing";
|
import { getHeroData } from "@/service/landing/landing";
|
||||||
import { title } from "process";
|
|
||||||
import { htmlToString } from "@/utils/globals";
|
import { htmlToString } from "@/utils/globals";
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
|
|
@ -88,7 +86,7 @@ const ScrollableContent = () => {
|
||||||
<span className="text-[#c03724] dark:text-white">
|
<span className="text-[#c03724] dark:text-white">
|
||||||
{t("download", { defaultValue: "Download" })}
|
{t("download", { defaultValue: "Download" })}
|
||||||
{t("coverage", { defaultValue: "Coverage" })}
|
{t("coverage", { defaultValue: "Coverage" })}
|
||||||
</span>{" "}
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div className="w-[10%] h-1 bg-[#bb3523] mt-2"></div>
|
<div className="w-[10%] h-1 bg-[#bb3523] mt-2"></div>
|
||||||
<div className="w-full h-1 bg-[#bb3523] mx-auto"></div>
|
<div className="w-full h-1 bg-[#bb3523] mx-auto"></div>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ const SearchSection = () => {
|
||||||
const t = useTranslations("LandingPage");
|
const t = useTranslations("LandingPage");
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
// Determine background image based on theme
|
|
||||||
const getBackgroundImage = () => {
|
const getBackgroundImage = () => {
|
||||||
if (theme === "dark") {
|
if (theme === "dark") {
|
||||||
return "url('/assets/background-dark.png')";
|
return "url('/assets/background-dark.png')";
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,7 @@ const DetailAudio = () => {
|
||||||
|
|
||||||
async function shareToEmail() {
|
async function shareToEmail() {
|
||||||
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
if (Number(userRoleId) < 1 || userRoleId == undefined) {
|
||||||
router.push("/auth/login");
|
router.push("/auth");
|
||||||
} else {
|
} else {
|
||||||
const data = {
|
const data = {
|
||||||
mediaUploadId: id?.split("-")?.[0],
|
mediaUploadId: id?.split("-")?.[0],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useLocale } from "next-intl";
|
import { useLocale } from "next-intl";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams, useSearchParams } from "next/navigation";
|
||||||
import { locales } from "@/config";
|
import { locales } from "@/config";
|
||||||
import { usePathname, useRouter } from "@/i18n/routing";
|
import { usePathname, useRouter } from "@/i18n/routing";
|
||||||
|
|
||||||
|
|
@ -33,13 +33,22 @@ export default function LocalSwitcher() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const localActive = useLocale() || "in";
|
const localActive = useLocale() || "in";
|
||||||
const [selectedLang, setSelectedLang] = useState<string>("");
|
const [selectedLang, setSelectedLang] = useState<string>("");
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedLang = getLanguage();
|
const storedLang = getLanguage();
|
||||||
|
let joinParam = "";
|
||||||
|
if (searchParams) {
|
||||||
|
joinParam = Array.from(searchParams.entries())
|
||||||
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
|
.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
if (pathname.includes("polda")){
|
if (pathname.includes("polda")) {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.replace(pathname, { locale: "in" });
|
router.replace(pathname + joinParam === "" ? "" : `?${joinParam}`, {
|
||||||
|
locale: "in",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!storedLang) {
|
if (!storedLang) {
|
||||||
|
|
@ -47,16 +56,20 @@ export default function LocalSwitcher() {
|
||||||
setSelectedLang("in");
|
setSelectedLang("in");
|
||||||
|
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.replace(pathname, { locale: "in" });
|
router.replace(pathname + joinParam === "" ? "" : `?${joinParam}`, {
|
||||||
|
locale: "in",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setSelectedLang(storedLang);
|
setSelectedLang(storedLang);
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.replace(pathname, { locale: storedLang });
|
router.replace(pathname + joinParam === "" ? "" : `?${joinParam}`, {
|
||||||
|
locale: storedLang,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, [searchParams]);
|
||||||
|
|
||||||
const onSelectChange = (nextLocale: string) => {
|
const onSelectChange = (nextLocale: string) => {
|
||||||
setLanguage(nextLocale);
|
setLanguage(nextLocale);
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,33 @@
|
||||||
import { useState, useCallback, useEffect } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { useRouter } from "@/components/navigation";
|
import { useRouter } from "@/components/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
LoginFormData,
|
LoginFormData,
|
||||||
ProfileData,
|
ProfileData,
|
||||||
AuthState,
|
AuthState,
|
||||||
AuthContextType,
|
AuthContextType,
|
||||||
EmailValidationData,
|
EmailValidationData,
|
||||||
OTPData
|
OTPData,
|
||||||
} from "@/types/auth";
|
} from "@/types/auth";
|
||||||
import {
|
import {
|
||||||
login,
|
login,
|
||||||
getProfile,
|
getProfile,
|
||||||
postEmailValidation,
|
postEmailValidation,
|
||||||
postSetupEmail,
|
postSetupEmail,
|
||||||
verifyOTPByUsername,
|
verifyOTPByUsername,
|
||||||
doLogin
|
doLogin,
|
||||||
} from "@/service/auth";
|
} from "@/service/auth";
|
||||||
import {
|
import {
|
||||||
setAuthCookies,
|
setAuthCookies,
|
||||||
setProfileCookies,
|
setProfileCookies,
|
||||||
clearAllCookies,
|
clearAllCookies,
|
||||||
isUserEligible,
|
isUserEligible,
|
||||||
isProtectedRole,
|
isProtectedRole,
|
||||||
getNavigationPath,
|
getNavigationPath,
|
||||||
showAuthError,
|
showAuthError,
|
||||||
showAuthSuccess,
|
showAuthSuccess,
|
||||||
loginRateLimiter,
|
loginRateLimiter,
|
||||||
AUTH_CONSTANTS
|
AUTH_CONSTANTS,
|
||||||
} from "@/lib/auth-utils";
|
} from "@/lib/auth-utils";
|
||||||
import { warning } from "@/lib/swal";
|
import { warning } from "@/lib/swal";
|
||||||
|
|
||||||
|
|
@ -46,108 +46,114 @@ export const useAuth = (): AuthContextType => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
try {
|
try {
|
||||||
setState(prev => ({ ...prev, loading: true }));
|
setState((prev) => ({ ...prev, loading: true }));
|
||||||
// Add logic to check if user is authenticated
|
// Add logic to check if user is authenticated
|
||||||
// This could check for valid tokens, etc.
|
// This could check for valid tokens, etc.
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setState(prev => ({
|
setState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
user: null,
|
user: null,
|
||||||
error: "Authentication check failed"
|
error: "Authentication check failed",
|
||||||
}));
|
}));
|
||||||
} finally {
|
} finally {
|
||||||
setState(prev => ({ ...prev, loading: false }));
|
setState((prev) => ({ ...prev, loading: false }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
checkAuth();
|
checkAuth();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = useCallback(async (credentials: LoginFormData): Promise<void> => {
|
const login = useCallback(
|
||||||
try {
|
async (credentials: LoginFormData): Promise<void> => {
|
||||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
try {
|
||||||
|
setState((prev) => ({ ...prev, loading: true, error: null }));
|
||||||
|
|
||||||
// Check rate limiting
|
// Check rate limiting
|
||||||
if (!loginRateLimiter.canAttempt(credentials.username)) {
|
if (!loginRateLimiter.canAttempt(credentials.username)) {
|
||||||
const remainingTime = loginRateLimiter.getRemainingTime(credentials.username);
|
const remainingTime = loginRateLimiter.getRemainingTime(
|
||||||
const minutes = Math.ceil(remainingTime / (60 * 1000));
|
credentials.username
|
||||||
throw new Error(`Too many login attempts. Please try again in ${minutes} minutes.`);
|
);
|
||||||
|
const minutes = Math.ceil(remainingTime / (60 * 1000));
|
||||||
|
throw new Error(
|
||||||
|
`Too many login attempts. Please try again in ${minutes} minutes.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt login
|
||||||
|
const response = await doLogin({
|
||||||
|
...credentials,
|
||||||
|
grantType: AUTH_CONSTANTS.GRANT_TYPE,
|
||||||
|
clientId: AUTH_CONSTANTS.CLIENT_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
loginRateLimiter.recordAttempt(credentials.username);
|
||||||
|
throw new Error("Invalid username or password");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { access_token, refresh_token } = response?.data || {};
|
||||||
|
|
||||||
|
if (!access_token || !refresh_token) {
|
||||||
|
throw new Error("Invalid response from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set auth cookies
|
||||||
|
setAuthCookies(access_token, refresh_token);
|
||||||
|
|
||||||
|
// Get user profile
|
||||||
|
const profileResponse = await getProfile(access_token);
|
||||||
|
const profile: ProfileData = profileResponse?.data?.data;
|
||||||
|
|
||||||
|
if (!profile) {
|
||||||
|
throw new Error("Failed to fetch user profile");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate user eligibility
|
||||||
|
// if (!isUserEligible(profile)) {
|
||||||
|
// clearAllCookies();
|
||||||
|
// warning(
|
||||||
|
// "Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri",
|
||||||
|
// "/auth"
|
||||||
|
// );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Set profile cookies
|
||||||
|
setProfileCookies(profile);
|
||||||
|
|
||||||
|
// Reset rate limiter on successful login
|
||||||
|
loginRateLimiter.resetAttempts(credentials.username);
|
||||||
|
|
||||||
|
// Navigate based on user role
|
||||||
|
const navigationPath = getNavigationPath(
|
||||||
|
profile.roleId,
|
||||||
|
profile.userLevel?.id,
|
||||||
|
profile.userLevel?.parentLevelId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
setState({
|
||||||
|
isAuthenticated: true,
|
||||||
|
user: profile,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigate to appropriate dashboard
|
||||||
|
window.location.href = navigationPath;
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = error?.message || "Login failed";
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
loading: false,
|
||||||
|
error: errorMessage,
|
||||||
|
}));
|
||||||
|
showAuthError(error, "Login failed");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
// Attempt login
|
[router]
|
||||||
const response = await doLogin({
|
);
|
||||||
...credentials,
|
|
||||||
grantType: AUTH_CONSTANTS.GRANT_TYPE,
|
|
||||||
clientId: AUTH_CONSTANTS.CLIENT_ID,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response?.error) {
|
|
||||||
loginRateLimiter.recordAttempt(credentials.username);
|
|
||||||
throw new Error("Invalid username or password");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { access_token, refresh_token } = response?.data || {};
|
|
||||||
|
|
||||||
if (!access_token || !refresh_token) {
|
|
||||||
throw new Error("Invalid response from server");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set auth cookies
|
|
||||||
setAuthCookies(access_token, refresh_token);
|
|
||||||
|
|
||||||
// Get user profile
|
|
||||||
const profileResponse = await getProfile(access_token);
|
|
||||||
const profile: ProfileData = profileResponse?.data?.data;
|
|
||||||
|
|
||||||
if (!profile) {
|
|
||||||
throw new Error("Failed to fetch user profile");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate user eligibility
|
|
||||||
// if (!isUserEligible(profile)) {
|
|
||||||
// clearAllCookies();
|
|
||||||
// warning(
|
|
||||||
// "Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri",
|
|
||||||
// "/auth"
|
|
||||||
// );
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Set profile cookies
|
|
||||||
setProfileCookies(profile);
|
|
||||||
|
|
||||||
// Reset rate limiter on successful login
|
|
||||||
loginRateLimiter.resetAttempts(credentials.username);
|
|
||||||
|
|
||||||
// Navigate based on user role
|
|
||||||
const navigationPath = getNavigationPath(
|
|
||||||
profile.roleId,
|
|
||||||
profile.userLevel?.id,
|
|
||||||
profile.userLevel?.parentLevelId
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
setState({
|
|
||||||
isAuthenticated: true,
|
|
||||||
user: profile,
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navigate to appropriate dashboard
|
|
||||||
window.location.href = navigationPath;
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
const errorMessage = error?.message || "Login failed";
|
|
||||||
setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
loading: false,
|
|
||||||
error: errorMessage
|
|
||||||
}));
|
|
||||||
showAuthError(error, "Login failed");
|
|
||||||
}
|
|
||||||
}, [router]);
|
|
||||||
|
|
||||||
const logout = useCallback((): void => {
|
const logout = useCallback((): void => {
|
||||||
clearAllCookies();
|
clearAllCookies();
|
||||||
|
|
@ -162,13 +168,13 @@ export const useAuth = (): AuthContextType => {
|
||||||
|
|
||||||
const refreshToken = useCallback(async (): Promise<void> => {
|
const refreshToken = useCallback(async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setState(prev => ({ ...prev, loading: true }));
|
setState((prev) => ({ ...prev, loading: true }));
|
||||||
// Add token refresh logic here
|
// Add token refresh logic here
|
||||||
// This would typically call an API to refresh the access token
|
// This would typically call an API to refresh the access token
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logout();
|
logout();
|
||||||
} finally {
|
} finally {
|
||||||
setState(prev => ({ ...prev, loading: false }));
|
setState((prev) => ({ ...prev, loading: false }));
|
||||||
}
|
}
|
||||||
}, [logout]);
|
}, [logout]);
|
||||||
|
|
||||||
|
|
@ -185,38 +191,43 @@ export const useEmailValidation = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const validateEmail = useCallback(async (credentials: LoginFormData): Promise<string> => {
|
const validateEmail = useCallback(
|
||||||
try {
|
async (credentials: LoginFormData): Promise<string> => {
|
||||||
setLoading(true);
|
try {
|
||||||
setError(null);
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
const response = await postEmailValidation(credentials);
|
const response = await postEmailValidation(credentials);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response?.message || "Email validation failed");
|
throw new Error(response?.message || "Email validation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = response?.data?.message;
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
|
case "Continue to setup email":
|
||||||
|
return "setup";
|
||||||
|
case "Email is valid and OTP has been sent":
|
||||||
|
return "otp";
|
||||||
|
case "Username & password valid":
|
||||||
|
return "success";
|
||||||
|
case "Skip to login":
|
||||||
|
return "skip";
|
||||||
|
default:
|
||||||
|
return "login";
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = error?.message || "Email validation failed";
|
||||||
|
setError(errorMessage);
|
||||||
|
showAuthError(error, "Email validation failed");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const message = response?.data?.message;
|
[]
|
||||||
|
);
|
||||||
switch (message) {
|
|
||||||
case "Continue to setup email":
|
|
||||||
return "setup";
|
|
||||||
case "Email is valid and OTP has been sent":
|
|
||||||
return "otp";
|
|
||||||
case "Username & password valid":
|
|
||||||
return "success";
|
|
||||||
default:
|
|
||||||
return "login";
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
const errorMessage = error?.message || "Email validation failed";
|
|
||||||
setError(errorMessage);
|
|
||||||
showAuthError(error, "Email validation failed");
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
validateEmail,
|
validateEmail,
|
||||||
|
|
@ -230,46 +241,49 @@ export const useEmailSetup = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const setupEmail = useCallback(async (
|
const setupEmail = useCallback(
|
||||||
credentials: LoginFormData,
|
async (
|
||||||
emailData: EmailValidationData
|
credentials: LoginFormData,
|
||||||
): Promise<string> => {
|
emailData: EmailValidationData
|
||||||
try {
|
): Promise<string> => {
|
||||||
setLoading(true);
|
try {
|
||||||
setError(null);
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
username: credentials.username,
|
username: credentials.username,
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
oldEmail: emailData.oldEmail,
|
oldEmail: emailData.oldEmail,
|
||||||
newEmail: emailData.newEmail,
|
newEmail: emailData.newEmail,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await postSetupEmail(data);
|
const response = await postSetupEmail(data);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response.message || "Email setup failed");
|
throw new Error(response.message || "Email setup failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = response?.data?.message;
|
||||||
|
|
||||||
|
switch (message) {
|
||||||
|
case "Email is valid and OTP has been sent":
|
||||||
|
return "otp";
|
||||||
|
case "The old email is not same":
|
||||||
|
throw new Error("Email is invalid");
|
||||||
|
default:
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = error?.message || "Email setup failed";
|
||||||
|
setError(errorMessage);
|
||||||
|
showAuthError(error, "Email setup failed");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const message = response?.data?.message;
|
[]
|
||||||
|
);
|
||||||
switch (message) {
|
|
||||||
case "Email is valid and OTP has been sent":
|
|
||||||
return "otp";
|
|
||||||
case "The old email is not same":
|
|
||||||
throw new Error("Email is invalid");
|
|
||||||
default:
|
|
||||||
return "success";
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
const errorMessage = error?.message || "Email setup failed";
|
|
||||||
setError(errorMessage);
|
|
||||||
showAuthError(error, "Email setup failed");
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setupEmail,
|
setupEmail,
|
||||||
|
|
@ -283,38 +297,38 @@ export const useOTPVerification = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const verifyOTP = useCallback(async (
|
const verifyOTP = useCallback(
|
||||||
username: string,
|
async (username: string, otp: string): Promise<boolean> => {
|
||||||
otp: string
|
try {
|
||||||
): Promise<boolean> => {
|
setLoading(true);
|
||||||
try {
|
setError(null);
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
if (otp.length !== 6) {
|
if (otp.length !== 6) {
|
||||||
throw new Error("OTP must be exactly 6 digits");
|
throw new Error("OTP must be exactly 6 digits");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await verifyOTPByUsername(username, otp);
|
||||||
|
|
||||||
|
if (response?.error) {
|
||||||
|
throw new Error(response.message || "OTP verification failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response?.message === "success";
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = error?.message || "OTP verification failed";
|
||||||
|
setError(errorMessage);
|
||||||
|
showAuthError(error, "OTP verification failed");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const response = await verifyOTPByUsername(username, otp);
|
[]
|
||||||
|
);
|
||||||
if (response?.error) {
|
|
||||||
throw new Error(response.message || "OTP verification failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return response?.message === "success";
|
|
||||||
} catch (error: any) {
|
|
||||||
const errorMessage = error?.message || "OTP verification failed";
|
|
||||||
setError(errorMessage);
|
|
||||||
showAuthError(error, "OTP verification failed");
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
verifyOTP,
|
verifyOTP,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -641,6 +641,9 @@
|
||||||
"timeTable1": "TIMETABLE /"
|
"timeTable1": "TIMETABLE /"
|
||||||
},
|
},
|
||||||
"FilterPage": {
|
"FilterPage": {
|
||||||
|
"downloadableContent": "Downloadable Content",
|
||||||
|
"content": "Content",
|
||||||
|
"allContent":"All Content",
|
||||||
"image": "Image",
|
"image": "Image",
|
||||||
"video": "Video",
|
"video": "Video",
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
|
|
@ -653,10 +656,10 @@
|
||||||
"searchTitle": "Find Title...",
|
"searchTitle": "Find Title...",
|
||||||
"monthYear": "Month and Year",
|
"monthYear": "Month and Year",
|
||||||
"thereIs": "There is",
|
"thereIs": "There is",
|
||||||
"downloadableImage": "downloadable image",
|
"downloadableImage": "Downloadable image",
|
||||||
"downloadableVideo": "downloadable video",
|
"downloadableVideo": "Downloadable video",
|
||||||
"downloadableText": "downloadable text",
|
"downloadableText": "Downloadable text",
|
||||||
"downloadableAudio": "downloadable audio",
|
"downloadableAudio": "Downloadable audio",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"selectYear": "Select Month and Year",
|
"selectYear": "Select Month and Year",
|
||||||
"selectDate": "Select Date",
|
"selectDate": "Select Date",
|
||||||
|
|
|
||||||
|
|
@ -642,6 +642,9 @@
|
||||||
"timeTable1": "JADWAL /"
|
"timeTable1": "JADWAL /"
|
||||||
},
|
},
|
||||||
"FilterPage": {
|
"FilterPage": {
|
||||||
|
"content": "Konten",
|
||||||
|
"downloadableContent": "artikel berisi konten yang dapat di unduh",
|
||||||
|
"allContent": "Semua Konten",
|
||||||
"image": "Foto",
|
"image": "Foto",
|
||||||
"video": "Audio Visual",
|
"video": "Audio Visual",
|
||||||
"text": "Teks",
|
"text": "Teks",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue