fix:category landing

This commit is contained in:
Rama Priyanto 2025-06-02 12:18:56 +07:00
parent af5b8000cd
commit 70ac77dba4
3 changed files with 191 additions and 167 deletions

View File

@ -29,7 +29,7 @@ import {
UserIcon, UserIcon,
} from "../../icons"; } from "../../icons";
import Link from "next/link"; import Link from "next/link";
import { useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { import {
getArticleByCategoryLanding, getArticleByCategoryLanding,
getListArticle, getListArticle,
@ -49,6 +49,7 @@ import {
import { close, loading } from "@/config/swal"; 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";
const months = [ const months = [
"Jan", "Jan",
@ -81,12 +82,7 @@ export default function ListNews() {
searchParams.get("search") || "" searchParams.get("search") || ""
); );
const [categorySearch, setCategorySearch] = useState(""); const [categorySearch, setCategorySearch] = useState("");
const [debouncedValue, setDebouncedValue] = useState(""); const [selectedCategoryId, setSelectedCategoryId] = useState<any>([]);
const [selectedCategoryId, setSelectedCategoryId] = useState<any>(
categoryIds ? categoryIds : ""
);
const [count, setCount] = useState(0);
const today = new Date(); const today = new Date();
const [year, setYear] = useState(today.getFullYear()); const [year, setYear] = useState(today.getFullYear());
@ -101,42 +97,43 @@ export default function ListNews() {
setSelectedDate(new Date(year, monthIndex, 1)); setSelectedDate(new Date(year, monthIndex, 1));
}; };
const getCategoryId = async () => {
if (categoryIds) {
const res = await getCategoryById(Number(selectedCategoryId));
setCategorySearch(res?.data?.data.title);
setCategories([res?.data?.data]);
setCount(1);
}
};
useEffect(() => { useEffect(() => {
getCategory(); const search = searchParams.get("search");
if (categoryIds && count == 0) { const category = searchParams.get("category_id");
getCategoryId(); if (searchParams.get("search")) {
setSearchValue(String(searchParams.get("search")));
getArticle({ title: String(search) });
} }
}, [debouncedValue]);
if (category && category !== "") {
getCategoryFromQueries(category.split(","));
}
}, [searchParams]);
const getCategoryFromQueries = async (category: string[]) => {
const temp = [];
for (const element of category) {
const res = await getCategoryById(Number(element));
if (res?.data?.data) {
temp.push(res?.data?.data);
}
}
const setup = setupCategory(temp);
setSelectedCategoryId(setup);
getArticle({ category: setup });
};
useEffect(() => { useEffect(() => {
getArticle(); getArticle();
}, [page]); }, [page, searchParams]);
const getCategory = async () => { async function getArticle(props?: { title?: string; category?: any }) {
const res = await getArticleByCategoryLanding({
limit: debouncedValue === "" ? "5" : "",
title: debouncedValue,
});
if (res?.data?.data) {
setCategories(res?.data?.data);
}
};
async function getArticle() {
loading(); loading();
// topRef.current?.scrollIntoView({ behavior: "smooth" }); // topRef.current?.scrollIntoView({ behavior: "smooth" });
const req = { const req = {
page: page, page: page,
search: searchValue || "", search: props?.title || searchValue || "",
limit: "9", limit: "9",
isPublish: true, isPublish: true,
sort: "desc", sort: "desc",
@ -144,9 +141,12 @@ export default function ListNews() {
pathname.includes("polda") || pathname.includes("satker") pathname.includes("polda") || pathname.includes("satker")
? String(category) ? String(category)
: "", : "",
categoryIds: selectedCategoryId, categoryIds: props?.category
// categoryIds: ? props.category.map((val: any) => val.id).join(",")
// selectedCategoryId && categorySearch !== "" ? selectedCategoryId : "", : selectedCategoryId.length > 0
? selectedCategoryId.map((val: any) => val.id).join(",")
: "",
startDate: startDate:
selectedDate && selectedMonth !== null selectedDate && selectedMonth !== null
? convertDateFormatNoTimeV2(new Date(year, selectedMonth, 1)) ? convertDateFormatNoTimeV2(new Date(year, selectedMonth, 1))
@ -162,21 +162,52 @@ export default function ListNews() {
close(); close();
} }
useEffect(() => { const debounceTimeout = useRef<NodeJS.Timeout | null>(null);
const timeout = setTimeout(() => {
setDebouncedValue(categorySearch);
}, 1500);
return () => clearTimeout(timeout); const getCategory = async (search?: string) => {
}, [categorySearch]); const res = await getArticleByCategoryLanding({
// limit: debouncedValue === "" ? "5" : "",
const onInputChange = (value: string) => { // title: debouncedValue,
setCategorySearch(value); limit: !search || search === "" ? "5" : "",
if (value === "") { title: search ? search : "",
setSelectedCategoryId(""); });
if (res?.data?.data) {
setCategories(res?.data?.data);
return res?.data?.data;
} }
return [];
}; };
const setupCategory = (data: any) => {
const temp = [];
for (const element of data) {
temp.push({
id: element.id,
label: element.title,
value: element.id,
});
}
return temp;
};
const loadOptions = useCallback(
(inputValue: string, callback: (options: any) => void) => {
if (debounceTimeout.current) {
clearTimeout(debounceTimeout.current);
}
debounceTimeout.current = setTimeout(async () => {
try {
const data = await getCategory(inputValue);
callback(setupCategory(data));
} catch (error) {
callback([]);
}
}, 1500);
},
[]
);
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">
@ -187,118 +218,104 @@ export default function ListNews() {
<ChevronRightIcon /> <ChevronRightIcon />
<p className="text-black">Berita</p> <p className="text-black">Berita</p>
</div> </div>
<div className="py-5 lg:py-10 lg:px-10 flex flex-col lg:flex-row gap-2 items-end"> <div className="w-full flex justify-center">
<Input <div className="py-5 lg:py-10 lg:mx-auto flex flex-col lg:flex-row gap-2 items-end grow-0 mx-auto">
aria-label="Judul" <Input
className="w-full" aria-label="Judul"
classNames={{ className="w-full lg:w-[300px]"
inputWrapper: "bg-white hover:!bg-gray-100 border-1", classNames={{
input: "text-sm !text-black", inputWrapper: "bg-white hover:!bg-gray-100 border-1 rounded-md",
}} input: "text-sm !text-black",
// onKeyDown={(event) => { }}
// if (event.key === "Enter") { // onKeyDown={(event) => {
// router.push(pathname + `?search=${searchValue}`); // if (event.key === "Enter") {
// getArticle(); // router.push(pathname + `?search=${searchValue}`);
// } // getArticle();
// }} // }
labelPlacement="outside" // }}
placeholder="Judul..."
value={searchValue}
onValueChange={setSearchValue}
type="search"
/>
<div className="flex w-full lg:w-fit gap-4">
<Autocomplete
className="w-full lg:w-[240px] mt-0"
label=""
labelPlacement="outside" labelPlacement="outside"
variant="bordered" placeholder="Judul..."
value={searchValue}
onValueChange={setSearchValue}
type="search"
/>
<AsyncSelect
isMulti
loadOptions={loadOptions}
defaultOptions
placeholder="Kategori" placeholder="Kategori"
inputValue={categorySearch} className="z-50 min-w-[300px] max-w-[600px]"
// selectedKey={selectedCategoryId} classNames={{
onInputChange={onInputChange} control: () =>
onSelectionChange={(e) => setSelectedCategoryId(e)} "border border-gray-300 border-1 rounded-xl min-w-[300px] max-w-[600px]",
inputProps={{ classNames: { inputWrapper: "border-1" } }} menu: () => "z-50",
defaultItems={categories} }}
> value={selectedCategoryId}
{/* {categories.length > 0 && onChange={setSelectedCategoryId}
categories.map((category: any) => ( />
<AutocompleteItem key={`${category.id}`}> <div className="flex flex-row items-center border-1 h-[40px] w-full lg:w-[200px] rounded-md px-2">
{category.title} <Popover placement="bottom" showArrow={true} className="w-full">
</AutocompleteItem> <PopoverTrigger>
))} */} <a className="px-2 py-1 text-sm w-[220px]">
{(item: any) => ( {" "}
<AutocompleteItem key={item.id}>{item.title}</AutocompleteItem> {selectedDate
)} ? format(selectedDate, "MMMM yyyy")
</Autocomplete> : "Pilih Bulan"}
</div> </a>
<div className="flex flex-row items-center border-1 h-[40px] w-full lg:w-[240px] rounded-xl px-2"> </PopoverTrigger>
<Popover placement="bottom" showArrow={true} className="w-full"> <PopoverContent className="p-4 w-[200px]">
<PopoverTrigger> <div className="flex items-center justify-between mb-2 px-1 w-full">
<a className="px-2 py-1 text-sm w-[220px]">
{" "}
{selectedDate
? format(selectedDate, "MMMM yyyy")
: "Pilih Bulan"}
</a>
</PopoverTrigger>
<PopoverContent className="p-4 w-[220px]">
<div className="flex items-center justify-between mb-2 px-1 w-full">
<button
className="text-gray-500 hover:text-black"
onClick={() => setYear((prev) => prev - 1)}
>
<ChevronLeftIcon />
</button>
<span className="font-semibold text-center">{year}</span>
<button
className="text-gray-500 hover:text-black"
onClick={() => setYear((prev) => prev + 1)}
>
<ChevronRightIcon />
</button>
</div>
<div className="grid grid-cols-3 gap-2 w-full">
{months.map((month, idx) => (
<button <button
key={idx} className="text-gray-500 hover:text-black"
onClick={() => handleMonthClick(idx)} onClick={() => setYear((prev) => prev - 1)}
className={`py-1 rounded-md text-sm transition-colors ${
selectedDate &&
selectedDate.getMonth() === idx &&
selectedDate.getFullYear() === year
? "bg-blue-500 text-white"
: "hover:bg-gray-200 text-gray-700"
}`}
> >
{month} <ChevronLeftIcon />
</button> </button>
))} <span className="font-semibold text-center">{year}</span>
</div> <button
</PopoverContent> className="text-gray-500 hover:text-black"
</Popover>{" "} onClick={() => setYear((prev) => prev + 1)}
{selectedDate && ( >
<a <ChevronRightIcon />
className="cursor-pointer w-[20px]" </button>
onClick={() => setSelectedDate(null)} </div>
>
<TimesIcon size={20} /> <div className="grid grid-cols-3 gap-2 w-full">
</a> {months.map((month, idx) => (
)} <button
key={idx}
onClick={() => handleMonthClick(idx)}
className={`py-1 rounded-md text-sm transition-colors ${
selectedDate &&
selectedDate.getMonth() === idx &&
selectedDate.getFullYear() === year
? "bg-blue-500 text-white"
: "hover:bg-gray-200 text-gray-700"
}`}
>
{month}
</button>
))}
</div>
</PopoverContent>
</Popover>{" "}
{selectedDate && (
<a
className="cursor-pointer w-[20px]"
onClick={() => setSelectedDate(null)}
>
<TimesIcon size={20} />
</a>
)}
</div>
<Button
onPress={() => getArticle()}
className="bg-red-600 text-white w-[80px] rounded-md"
>
Cari
</Button>
{/* </Link> */}
</div> </div>
{/* <Link
href={`/news/all?search=${searchValue}&category_id=${
selectedCategoryId || ""
}&month=${selectedMonth && selectedDate ? selectedMonth + 1 : ""}`}
> */}
<Button
onPress={getArticle}
className="bg-red-600 text-white w-[80px]"
>
Cari
</Button>
{/* </Link> */}
</div> </div>
{article?.length < 1 || !article ? ( {article?.length < 1 || !article ? (
<div className="flex justify-center items-center">Tidak ada Data</div> <div className="flex justify-center items-center">Tidak ada Data</div>

View File

@ -260,7 +260,11 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
<p className="text-lg border-b-3 border-red-600 font-semibold">TAGS</p> <p className="text-lg border-b-3 border-red-600 font-semibold">TAGS</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{data?.categories?.map((category: any) => ( {data?.categories?.map((category: any) => (
<Link href={""} key={category?.id} className="text-sm "> <Link
href={`/news/all?category_id=${category?.id}`}
key={category?.id}
className="text-sm "
>
<Button className="bg-[#BE0106] text-white px-2 py-2 rounded-md "> <Button className="bg-[#BE0106] text-white px-2 py-2 rounded-md ">
{category?.title} {category?.title}
</Button> </Button>
@ -268,17 +272,20 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
))} ))}
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{data?.tags?.split(",").map((tag: any) => ( {data?.tags?.split(",").map(
<Link href={""} key={tag} className="text-xs"> (tag: any) =>
<Button tag !== "" && (
className="border-[#BE0106] px-2 py-2 rounded-md " <Link href={``} key={tag} className="text-xs">
variant="bordered" <Button
size="sm" className="border-[#BE0106] px-2 py-2 rounded-md "
> variant="bordered"
{tag} size="sm"
</Button> >
</Link> {tag}
))} </Button>
</Link>
)
)}
</div> </div>
</div> </div>
<div className="grid grid-cols-2 md:flex lg:justify-between gap-2 lg:gap-10 my-8"> <div className="grid grid-cols-2 md:flex lg:justify-between gap-2 lg:gap-10 my-8">

View File

@ -72,8 +72,8 @@ export default function RelatedNews(props: { categories: any }) {
); );
}} }}
> >
{article?.map((newsItem: any) => ( {article?.map((newsItem: any, index: number) => (
<SwiperSlide key={newsItem.id}> <SwiperSlide key={`${newsItem.id}-${index}`}>
<Card isFooterBlurred radius="lg" className="border-none"> <Card isFooterBlurred radius="lg" className="border-none">
<Image <Image
width={480} width={480}