fix:page query, polda query

This commit is contained in:
Rama Priyanto 2026-01-19 13:03:54 +07:00
parent 407566899f
commit 962b9708bf
4 changed files with 238 additions and 56 deletions

View File

@ -320,7 +320,7 @@ export default function CategorySatker(props: {
<ModalBody className="flex flex-row flex-wrap justify-center text-center"> <ModalBody className="flex flex-row flex-wrap justify-center text-center">
{SatkerAll.map((item: any, index: any) => ( {SatkerAll.map((item: any, index: any) => (
<div <div
key={index.id} key={index}
className="w-[140px] h-[115px] flex flex-col items-center justify-center rounded-lg shadow-sm" className="w-[140px] h-[115px] flex flex-col items-center justify-center rounded-lg shadow-sm"
> >
<Link href={`/news/satker/${changeNameToSlug(item.title)}`}> <Link href={`/news/satker/${changeNameToSlug(item.title)}`}>

View File

@ -318,7 +318,7 @@ export default function RegionalNews(props: {
<ModalBody className="flex flex-row flex-wrap justify-center text-center"> <ModalBody className="flex flex-row flex-wrap justify-center text-center">
{listPoldaAll.map((item: any, index: any) => ( {listPoldaAll.map((item: any, index: any) => (
<div <div
key={index.id} key={index}
className="w-[140px] h-[115px] flex flex-col items-center justify-center rounded-lg shadow-sm" className="w-[140px] h-[115px] flex flex-col items-center justify-center rounded-lg shadow-sm"
> >
<Link href={`/news/polda/${item.path}`}> <Link href={`/news/polda/${item.path}`}>

View File

@ -37,6 +37,7 @@ import {
import { import {
convertDateFormatNoTimeV2, convertDateFormatNoTimeV2,
formatMonthString, formatMonthString,
getUnixTimestamp,
htmlToString, htmlToString,
textEllipsis, textEllipsis,
} from "@/utils/global"; } from "@/utils/global";
@ -50,6 +51,7 @@ import { close, loading } from "@/config/swal";
import { format } from "date-fns"; import { format } from "date-fns";
import { getCategoryById } from "@/services/master-categories"; import { getCategoryById } from "@/services/master-categories";
import AsyncSelect from "react-select/async"; import AsyncSelect from "react-select/async";
import { data } from "autoprefixer";
const months = [ const months = [
"Jan", "Jan",
@ -79,7 +81,7 @@ export default function ListNews() {
const categoryIds = searchParams.get("category_id"); const categoryIds = searchParams.get("category_id");
const [categories, setCategories] = useState<any>([]); const [categories, setCategories] = useState<any>([]);
const [searchValue, setSearchValue] = useState( const [searchValue, setSearchValue] = useState(
searchParams.get("search") || "" searchParams.get("search") || "",
); );
const [categorySearch, setCategorySearch] = useState(""); const [categorySearch, setCategorySearch] = useState("");
const [selectedCategoryId, setSelectedCategoryId] = useState<any>([]); const [selectedCategoryId, setSelectedCategoryId] = useState<any>([]);
@ -88,9 +90,14 @@ export default function ListNews() {
const [year, setYear] = useState(today.getFullYear()); const [year, setYear] = useState(today.getFullYear());
const [selectedMonth, setSelectedMonth] = useState<number | null>( const [selectedMonth, setSelectedMonth] = useState<number | null>(
searchParams.get("month") ? Number(searchParams.get("month")) - 1 : null searchParams.get("month") ? Number(searchParams.get("month")) - 1 : null,
); );
const [selectedDate, setSelectedDate] = useState<Date | null>(null); const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const [poldaCategId, setPoldaCategId] = useState(0);
const isPoldaPage = pathname.includes("/news/polda/");
const [poldaCategoryOption, setPoldaCategoryOption] = useState<any | null>(
null,
);
const handleMonthClick = (monthIndex: number) => { const handleMonthClick = (monthIndex: number) => {
setSelectedMonth(monthIndex); setSelectedMonth(monthIndex);
@ -98,17 +105,68 @@ export default function ListNews() {
}; };
useEffect(() => { useEffect(() => {
const search = searchParams.get("search"); if (isPoldaPage) getPoldaCategId();
const category = searchParams.get("category_id"); }, [params, isPoldaPage]);
if (searchParams.get("search")) {
setSearchValue(String(searchParams.get("search"))); const getPoldaCategId = async () => {
getArticle({ title: String(search) }); const poldaNow = category as string;
console.log("poldanow", poldaNow.split("-").join(" "));
const dataNow = await getCategory(poldaNow.split("-").join(" "));
console.log("datanow", dataNow);
if (dataNow?.length > 0) {
const poldaObj = dataNow[0];
setPoldaCategId(poldaObj.id);
const option = setupCategory([poldaObj])[0]; // <-- wajib array
setPoldaCategoryOption(option);
// pastikan masuk ke selectedCategoryId (kalau belum ada)
setSelectedCategoryId((prev: any[]) => {
const already = prev.some((x) => x.id === option.id);
if (already) return prev;
return [option, ...prev];
});
}
};
useEffect(() => {
const search = searchParams.get("search") || "";
const category = searchParams.get("category_id") || "";
const month = searchParams.get("month");
const yearQ = searchParams.get("year");
const pageQ = searchParams.get("page");
// sync UI state dari URL
setSearchValue(search);
if (pageQ) setPage(Number(pageQ));
else setPage(1);
if (yearQ) setYear(Number(yearQ));
if (month && yearQ) {
const m = Number(month) - 1;
setSelectedMonth(m);
setSelectedDate(new Date(Number(yearQ), m, 1));
} else {
setSelectedMonth(null);
setSelectedDate(null);
} }
if (category && category !== "") { if (category && category !== "") {
getCategoryFromQueries(category.split(",")); getCategoryFromQueries(category.split(","));
} else {
setSelectedCategoryId([]);
getArticle({
title: search,
category: [],
month: month ? Number(month) : null,
year: yearQ ? Number(yearQ) : null,
page: pageQ ? Number(pageQ) : 1,
});
} }
}, [searchParams]); }, [searchParams, poldaCategId]);
const getCategoryFromQueries = async (category: string[]) => { const getCategoryFromQueries = async (category: string[]) => {
const temp = []; const temp = [];
@ -121,41 +179,88 @@ export default function ListNews() {
const setup = setupCategory(temp); const setup = setupCategory(temp);
setSelectedCategoryId(setup); setSelectedCategoryId(setup);
getArticle({ category: setup });
const search = searchParams.get("search") || "";
const month = searchParams.get("month");
const yearQ = searchParams.get("year");
const pageQ = searchParams.get("page");
getArticle({
title: search,
category: setup,
month: month ? Number(month) : null,
year: yearQ ? Number(yearQ) : null,
page: pageQ ? Number(pageQ) : 1,
});
}; };
useEffect(() => { // useEffect(() => {
getArticle(); // getArticle();
}, [page, searchParams]); // }, [page, searchParams]);
async function getArticle(props?: { title?: string; category?: any }) { async function getArticle(props?: {
title?: string;
category?: any[];
month?: number | null;
year?: number | null;
page?: number;
}) {
loading(); loading();
// topRef.current?.scrollIntoView({ behavior: "smooth" });
const usedPage = props?.page || page;
const usedSearch = props?.title ?? searchValue ?? "";
// 1) ambil kategori dari props atau state
const baseCategories =
props?.category && props.category.length > 0
? props.category
: selectedCategoryId;
// 2) kalau halaman polda, pastikan poldaCategId selalu ikut
let finalCategories = baseCategories;
if (isPoldaPage && poldaCategId) {
const hasPolda = baseCategories.some((x: any) => x.id === poldaCategId);
if (!hasPolda) {
// polda wajib selalu ada
finalCategories = [
...(poldaCategoryOption ? [poldaCategoryOption] : []),
...baseCategories,
];
}
}
const usedCategoryIds =
finalCategories.length > 0
? finalCategories.map((val: any) => val.id).join(",")
: "";
const usedMonth =
props?.month ?? (selectedDate ? selectedDate.getMonth() + 1 : null);
const usedYear =
props?.year ?? (selectedDate ? selectedDate.getFullYear() : null);
const req = { const req = {
page: page, page: usedPage,
search: props?.title || searchValue || "", search: usedSearch,
limit: "9", limit: "9",
isPublish: true, isPublish: true,
sort: "desc", sort: "desc",
categorySlug: categorySlug: pathname.includes("satker") ? String(category) : "",
pathname.includes("polda") || pathname.includes("satker") categoryIds: usedCategoryIds,
? String(category)
: "",
categoryIds: props?.category
? props.category.map((val: any) => val.id).join(",")
: selectedCategoryId.length > 0
? selectedCategoryId.map((val: any) => val.id).join(",")
: "",
startDate: startDate:
selectedDate && selectedMonth !== null usedMonth && usedYear
? convertDateFormatNoTimeV2(new Date(year, selectedMonth, 1)) ? convertDateFormatNoTimeV2(new Date(usedYear, usedMonth - 1, 1))
: "", : "",
endDate: endDate:
selectedDate && selectedMonth !== null usedMonth && usedYear
? convertDateFormatNoTimeV2(new Date(year, selectedMonth + 1, 0)) ? convertDateFormatNoTimeV2(new Date(usedYear, usedMonth, 0))
: "", : "",
}; };
const response = await getListArticle(req); const response = await getListArticle(req);
setArticle(response?.data?.data); setArticle(response?.data?.data);
setTotalPage(response?.data?.meta?.totalPage); setTotalPage(response?.data?.meta?.totalPage);
@ -170,6 +275,7 @@ export default function ListNews() {
// title: debouncedValue, // title: debouncedValue,
limit: !search || search === "" ? "5" : "", limit: !search || search === "" ? "5" : "",
title: search ? search : "", title: search ? search : "",
timeStamp: getUnixTimestamp(),
}); });
if (res?.data?.data) { if (res?.data?.data) {
setCategories(res?.data?.data); setCategories(res?.data?.data);
@ -205,9 +311,28 @@ export default function ListNews() {
} }
}, 1500); }, 1500);
}, },
[] [],
); );
const updateQuery = (newParams: Record<string, any>) => {
const params = new URLSearchParams(searchParams.toString());
Object.entries(newParams).forEach(([key, value]) => {
if (
value === undefined ||
value === null ||
value === "" ||
(Array.isArray(value) && value.length === 0)
) {
params.delete(key);
} else {
params.set(key, String(value));
}
});
router.push(`${pathname}?${params.toString()}`);
};
return ( return (
<div className="bg-white border-b-1" ref={topRef}> <div className="bg-white border-b-1" ref={topRef}>
<div className="text-black py-5 px-3 lg:w-[75vw] mx-auto bg-white"> <div className="text-black py-5 px-3 lg:w-[75vw] mx-auto bg-white">
@ -225,7 +350,7 @@ export default function ListNews() {
className="w-full lg:w-[300px]" className="w-full lg:w-[300px]"
classNames={{ classNames={{
inputWrapper: "bg-white hover:!bg-gray-100 border-1 rounded-md", inputWrapper: "bg-white hover:!bg-gray-100 border-1 rounded-md",
input: "text-sm !text-black", input: "text-sm !text-black outline-none",
}} }}
// onKeyDown={(event) => { // onKeyDown={(event) => {
// if (event.key === "Enter") { // if (event.key === "Enter") {
@ -251,7 +376,30 @@ export default function ListNews() {
menu: () => "z-50", menu: () => "z-50",
}} }}
value={selectedCategoryId} value={selectedCategoryId}
onChange={setSelectedCategoryId} onChange={(val: any) => {
const nextValue = val || [];
// kalau bukan halaman polda, normal
if (!pathname.includes("/news/polda/")) {
setSelectedCategoryId(nextValue);
return;
}
// halaman polda: kategori polda harus tetap ada
if (poldaCategoryOption) {
const hasPolda = nextValue.some(
(x: any) => x.id === poldaCategoryOption.id,
);
if (!hasPolda) {
// user mencoba hapus kategori polda -> balikin lagi
setSelectedCategoryId([poldaCategoryOption, ...nextValue]);
return;
}
}
setSelectedCategoryId(nextValue);
}}
/> />
<div className="flex flex-row items-center border-1 h-[40px] w-full lg:w-[200px] rounded-md px-2"> <div className="flex flex-row items-center border-1 h-[40px] w-full lg:w-[200px] rounded-md px-2">
<Popover placement="bottom" showArrow={true} className="w-full"> <Popover placement="bottom" showArrow={true} className="w-full">
@ -302,17 +450,38 @@ export default function ListNews() {
{selectedDate && ( {selectedDate && (
<a <a
className="cursor-pointer w-[20px]" className="cursor-pointer w-[20px]"
onClick={() => setSelectedDate(null)} onClick={() => {
setSelectedDate(null);
setSelectedMonth(null);
updateQuery({ month: "", year: "", page: 1 });
}}
> >
<TimesIcon size={20} /> <TimesIcon size={20} />
</a> </a>
)} )}
</div> </div>
<Button {/* <Button
onPress={() => getArticle()} onPress={() => getArticle()}
className="bg-red-600 text-white w-[80px] rounded-md" className="bg-red-600 text-white w-[80px] rounded-md"
> >
Cari Cari
</Button> */}
<Button
onPress={() => {
updateQuery({
search: searchValue,
category_id:
selectedCategoryId.length > 0
? selectedCategoryId.map((v: any) => v.id).join(",")
: "",
month: selectedDate ? selectedDate.getMonth() + 1 : "",
year: selectedDate ? selectedDate.getFullYear() : "",
page: 1, // reset page setiap cari
});
}}
className="bg-red-600 text-white w-[80px] rounded-md"
>
Cari
</Button> </Button>
{/* </Link> */} {/* </Link> */}
</div> </div>
@ -372,7 +541,7 @@ export default function ListNews() {
))} ))}
</section> </section>
<div className="flex justify-center mt-5"> <div className="flex justify-center mt-5">
<Pagination {/* <Pagination
page={page} page={page}
total={totalPage} total={totalPage}
onChange={setPage} onChange={setPage}
@ -380,6 +549,18 @@ export default function ListNews() {
item: "w-fit px-3", item: "w-fit px-3",
cursor: "w-fit px-3", cursor: "w-fit px-3",
}} }}
/> */}
<Pagination
page={page}
total={totalPage}
onChange={(p) => {
setPage(p);
updateQuery({ page: p });
}}
classNames={{
item: "w-fit px-3",
cursor: "w-fit px-3",
}}
/> />
</div> </div>
</> </>

View File

@ -38,7 +38,7 @@ export async function getListArticle(props: PaginationRequest) {
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}&categoryIds=${ }&category=${categorySlug || ""}&isBanner=${isBanner || ""}&categoryIds=${
categoryIds || "" categoryIds || ""
}&createdByIds=${createdByIds || ""}&isPolda=${isPolda || ""}`, }&createdByIds=${createdByIds || ""}&isPolda=${isPolda || ""}`,
headers headers,
); );
} }
@ -68,7 +68,7 @@ export async function getListArticleAdminPage(props: PaginationRequest) {
sort || "asc" sort || "asc"
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}&categoryIds=${ }&category=${categorySlug || ""}&isBanner=${isBanner || ""}&categoryIds=${
categoryIds || "" categoryIds || ""
}&createdByIds=${createdByIds || ""}&timeStamp=${timeStamp || ""}` }&createdByIds=${createdByIds || ""}&timeStamp=${timeStamp || ""}`,
); );
} }
@ -90,7 +90,7 @@ export async function getTopArticles(props: PaginationRequest) {
endDate || "" endDate || ""
}&category=${category || ""}&sortBy=view_count&sort=desc&timeStamp=${ }&category=${category || ""}&sortBy=view_count&sort=desc&timeStamp=${
timeStamp || "" timeStamp || ""
}` }`,
); );
} }
@ -133,7 +133,7 @@ export async function getRecapArticleData(data: any) {
}; };
return await httpGet( return await httpGet(
`/articles?page=${data.page}&userLevelId=${data.id}&startDate=${data.startDate}&endDate=${data.endDate}&startTime=${data.startTime}&endTime=${data.endTime}`, `/articles?page=${data.page}&userLevelId=${data.id}&startDate=${data.startDate}&endDate=${data.endDate}&startTime=${data.startTime}&endTime=${data.endTime}`,
headers headers,
); );
} }
@ -157,7 +157,7 @@ export async function getArticleByCategory(timeStamp: number) {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGetInterceptor( return await httpGetInterceptor(
`/article-categories?limit=1000&timeStamp=${timeStamp}` `/article-categories?limit=1000&timeStamp=${timeStamp}`,
); );
} }
export async function getCategoryPagination(data: any) { export async function getCategoryPagination(data: any) {
@ -167,7 +167,7 @@ export async function getCategoryPagination(data: any) {
}; };
return await httpGetInterceptor( return await httpGetInterceptor(
`/article-categories?limit=${data?.limit}&page=${data?.page}&title=${data?.search}&timeStamp=${data.timeStamp}` `/article-categories?limit=${data?.limit}&page=${data?.page}&title=${data?.search}&timeStamp=${data.timeStamp}`,
); );
} }
@ -207,7 +207,7 @@ export async function getUserLevelDataStat(
startTime: string, startTime: string,
endTime: string, endTime: string,
levelType: string, levelType: string,
levelId?: number levelId?: number,
) { ) {
// const headers = { // const headers = {
// "content-type": "application/json", // "content-type": "application/json",
@ -216,7 +216,7 @@ export async function getUserLevelDataStat(
return await httpGetInterceptor( return await httpGetInterceptor(
`/articles/statistic/user-levels?startDate=${startDate}&endDate=${endDate}&startTime=${startTime}&endTime=${endTime}&levelType=${levelType}&userLevelId=${ `/articles/statistic/user-levels?startDate=${startDate}&endDate=${endDate}&startTime=${startTime}&endTime=${endTime}&levelType=${levelType}&userLevelId=${
levelId || "" levelId || ""
}&timeStamp=${timeStamp}` }&timeStamp=${timeStamp}`,
); );
} }
@ -227,13 +227,13 @@ export async function getStatisticForMaps(startDate: string, endDate: string) {
}; };
return await httpGet( return await httpGet(
`/activity-logs/visitors-by-region-stats?startDate=${startDate}&endDate=${endDate}`, `/activity-logs/visitors-by-region-stats?startDate=${startDate}&endDate=${endDate}`,
headers headers,
); );
} }
export async function getStatisticVisitorsBrowser( export async function getStatisticVisitorsBrowser(
startDate: string, startDate: string,
endDate: string endDate: string,
) { ) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
@ -241,7 +241,7 @@ export async function getStatisticVisitorsBrowser(
}; };
return await httpGet( return await httpGet(
`/activity-logs/visitors-by-browser-stats?startDate=${startDate}&endDate=${endDate}`, `/activity-logs/visitors-by-browser-stats?startDate=${startDate}&endDate=${endDate}`,
headers headers,
); );
} }
export async function getStatisticMonthly(year: string, timeStamp: number) { export async function getStatisticMonthly(year: string, timeStamp: number) {
@ -250,7 +250,7 @@ export async function getStatisticMonthly(year: string, timeStamp: number) {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGetInterceptor( return await httpGetInterceptor(
`/articles/statistic/monthly?year=${year}&timeStamp=${timeStamp}` `/articles/statistic/monthly?year=${year}&timeStamp=${timeStamp}`,
); );
} }
export async function getStatisticVisitorsMonthly(year: string) { export async function getStatisticVisitorsMonthly(year: string) {
@ -260,7 +260,7 @@ export async function getStatisticVisitorsMonthly(year: string) {
}; };
return await httpGet( return await httpGet(
`/activity-logs/visitors-monthly-stats?year=${year}`, `/activity-logs/visitors-monthly-stats?year=${year}`,
headers headers,
); );
} }
export async function getStatisticUsersMonthly(year: string) { export async function getStatisticUsersMonthly(year: string) {
@ -270,12 +270,12 @@ export async function getStatisticUsersMonthly(year: string) {
}; };
return await httpGet( return await httpGet(
`/articles/statistic/monthly-per-user-level?year=${year}`, `/articles/statistic/monthly-per-user-level?year=${year}`,
headers headers,
); );
} }
export async function getStatisticMonthlyFeedback( export async function getStatisticMonthlyFeedback(
year: string, year: string,
timeStamp: number timeStamp: number,
) { ) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
@ -283,7 +283,7 @@ export async function getStatisticMonthlyFeedback(
}; };
return await httpGet( return await httpGet(
`/feedbacks/statistic/monthly?year=${year}&timeStamp=${timeStamp}`, `/feedbacks/statistic/monthly?year=${year}&timeStamp=${timeStamp}`,
headers headers,
); );
} }
export async function getStatisticSummary(timeStamp: number) { export async function getStatisticSummary(timeStamp: number) {
@ -292,7 +292,7 @@ export async function getStatisticSummary(timeStamp: number) {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGetInterceptor( return await httpGetInterceptor(
`/articles/statistic/summary?timeStamp=${timeStamp}` `/articles/statistic/summary?timeStamp=${timeStamp}`,
); );
} }
@ -320,12 +320,13 @@ export async function updateIsBannerArticle(id: number, status: boolean) {
export async function getArticleByCategoryLanding(props: { export async function getArticleByCategoryLanding(props: {
limit: string; limit: string;
title: string; title: string;
timeStamp?: number;
}) { }) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
}; };
return await httpGet( return await httpGet(
`/article-categories?limit=${props.limit}&title=${props.title}`, `/article-categories?limit=${props.limit}&title=${props.title}&timeStamp=${props?.timeStamp}`,
headers headers,
); );
} }