Compare commits

...

10 Commits

Author SHA1 Message Date
Rama Priyanto 84b84c0fec fix:detail news-> get thumbnail to content 2026-02-24 14:09:04 +07:00
Rama Priyanto 19f71eb972 fix:user edit 2026-02-20 17:17:22 +07:00
Rama Priyanto 6202af2149 fix:detail article 2026-02-06 17:29:59 +07:00
Rama Priyanto 2889a9acc2 fix:dashboard bearer 2026-02-06 15:13:35 +07:00
Rama Priyanto e235b86c12 fix:delete artocle categories 2026-02-06 14:23:11 +07:00
Rama Priyanto adcae589f0 feat:bearer for get users 2026-02-05 09:25:24 +07:00
Rama Priyanto 076c16be28 fix:loading fe 2026-02-04 12:24:19 +07:00
Rama Priyanto 6e071f170f remove time stamp 2026-02-03 22:34:40 +07:00
Rama Priyanto 4c807a7fee fix:filter use query urt 2026-01-29 19:00:18 +07:00
Rama Priyanto 1df9197c5b fix:detail article suggest article 2026-01-22 11:05:51 +07:00
19 changed files with 746 additions and 588 deletions

View File

@ -231,8 +231,9 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
setThumbnail(data?.thumbnailUrl); setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId); setDiseId(data?.aiArticleId);
setDetailFiles(data?.files); setDetailFiles(data?.files);
if ( if (
data?.files && data?.files.length > 0 &&
(data.files[0].file_name.split(".")[1].includes("doc") || (data.files[0].file_name.split(".")[1].includes("doc") ||
data.files[0].file_name.split(".")[1].includes("pdf")) data.files[0].file_name.split(".")[1].includes("pdf"))
) { ) {
@ -240,12 +241,14 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
} else { } else {
setSelectedFileType("image"); setSelectedFileType("image");
} }
setupInitCategory(data?.categories); setupInitCategory(data?.categories);
close(); close();
} }
const setupInitCategory = (data: any) => { const setupInitCategory = (data: any) => {
const temp: CategoryType[] = []; const temp: CategoryType[] = [];
console.log("datass");
for (let i = 0; i < data?.length; i++) { for (let i = 0; i < data?.length; i++) {
const datas = listCategory.filter((a) => a.id == data[i].id); const datas = listCategory.filter((a) => a.id == data[i].id);
if (datas[0]) { if (datas[0]) {

View File

@ -202,86 +202,96 @@ export default function FormMasterUserEdit() {
className="w-full lg:w-1/2 lg:ml-4" className="w-full lg:w-1/2 lg:ml-4"
> >
<Card className="rounded-md p-5 flex flex-col gap-3"> <Card className="rounded-md p-5 flex flex-col gap-3">
<Controller <div className="flex flex-col gap-1">
control={control} <p className="text-sm">Nama Lengkap</p>
name="fullname" <Controller
render={({ field: { onChange, value } }) => ( control={control}
<Input name="fullname"
type="text" render={({ field: { onChange, value } }) => (
id="title" <Input
placeholder="Nama Lengkap..." type="text"
label="Nama Lengkap" id="title"
value={value} placeholder="Nama Lengkap..."
onChange={onChange} label=""
labelPlacement="outside" value={value}
className="w-full" onChange={onChange}
classNames={{ labelPlacement="outside"
inputWrapper: [ className="w-full"
"border-1 rounded-lg", classNames={{
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400", input: "outline-none",
], inputWrapper: [
}} "border-1 rounded-lg",
variant="bordered" "dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
/> ],
}}
variant="bordered"
/>
)}
/>
{errors.fullname?.message && (
<p className="text-red-400 text-sm">{errors.fullname?.message}</p>
)} )}
/> </div>
{errors.fullname?.message && ( <div className="flex flex-col gap-1">
<p className="text-red-400 text-sm">{errors.fullname?.message}</p> <p className="text-sm">Username</p>
)}
<Controller
control={control}
name="username"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="username"
placeholder="Username..."
label="Username"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors.username?.message && (
<p className="text-red-400 text-sm">{errors.username?.message}</p>
)}
<Controller <Controller
control={control} control={control}
name="email" name="username"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<Input <Input
type="email" type="text"
id="email" id="username"
placeholder="Email..." placeholder="Username..."
label="Email" label=""
value={value} value={value}
onChange={onChange} onChange={onChange}
labelPlacement="outside" labelPlacement="outside"
className="w-full" className="w-full"
classNames={{ classNames={{
inputWrapper: [ inputWrapper: [
"border-1 rounded-lg", "border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400", "dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
], ],
}} }}
variant="bordered" variant="bordered"
/> />
)}
/>
{errors.username?.message && (
<p className="text-red-400 text-sm">{errors.username?.message}</p>
)} )}
/> </div>
{errors.email?.message && ( <div className="flex flex-col gap-1">
<p className="text-red-400 text-sm">{errors.email?.message}</p> <p className="text-sm">Email</p>
)}
<Controller
control={control}
name="email"
render={({ field: { onChange, value } }) => (
<Input
type="email"
id="email"
placeholder="Email..."
label=""
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors.email?.message && (
<p className="text-red-400 text-sm">{errors.email?.message}</p>
)}
</div>
{/* <Controller {/* <Controller
control={control} control={control}
name="identityType" name="identityType"
@ -306,165 +316,183 @@ export default function FormMasterUserEdit() {
{errors.identityType?.message} {errors.identityType?.message}
</p> </p>
)} */} )} */}
<Controller <div className="flex flex-col gap-1">
control={control} <p className="text-sm">NRP</p>
name="identityNumber"
render={({ field: { onChange, value } }) => (
<Input
type="number"
id="identityNumber"
placeholder="NRP..."
label="NRP"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
variant="bordered"
/>
)}
/>
{errors.identityNumber?.message && (
<p className="text-red-400 text-sm">
{errors.identityNumber?.message}
</p>
)}
<Controller <Controller
control={control} control={control}
name="address" name="identityNumber"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<Textarea <Input
label="Alamat" type="number"
labelPlacement="outside" id="identityNumber"
placeholder="Alamat..." placeholder="NRP..."
variant="bordered" label=""
value={value}
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
onValueChange={onChange}
/>
)}
/>
{errors.address?.message && (
<p className="text-red-400 text-sm">{errors.address?.message}</p>
)}
<Controller
control={control}
name="genderType"
render={({ field: { onChange, value } }) => (
<RadioGroup
orientation="horizontal"
label="Gender"
value={value}
onValueChange={onChange}
>
<Radio value="Male">Laki-laki</Radio>
<Radio value="Female">Perempuan</Radio>
</RadioGroup>
)}
/>
{errors.genderType?.message && (
<p className="text-red-400 text-sm">{errors.genderType?.message}</p>
)}
<Controller
control={control}
name="userLevelType"
render={({ field: { onChange, value } }) => (
<>
<p className="text-sm mt-3">Level Pengguna</p>
<ReactSelect
className="basic-single text-black z-50"
classNames={{
control: (state: any) =>
"!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500",
}}
classNamePrefix="select"
value={value} value={value}
onChange={onChange} onChange={onChange}
closeMenuOnSelect={false} labelPlacement="outside"
components={animatedComponents} className="w-full"
isClearable={true}
isSearchable={true}
isMulti={false}
placeholder=""
name="sub-module"
options={parentList}
/>
</>
)}
/>
{errors.userLevelType?.message && (
<p className="text-red-400 text-sm">
{errors.userLevelType?.message}
</p>
)}
<Controller
control={control}
name="userRoleType"
render={({ field: { onChange, value } }) => (
<>
<p className="text-sm mt-3">Peran Pengguna</p>
<ReactSelect
className="basic-single text-black z-49"
classNames={{ classNames={{
control: (state: any) => inputWrapper: [
"!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500", "border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}} }}
classNamePrefix="select" variant="bordered"
/>
)}
/>
{errors.identityNumber?.message && (
<p className="text-red-400 text-sm">
{errors.identityNumber?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Alamat</p>
<Controller
control={control}
name="address"
render={({ field: { onChange, value } }) => (
<Textarea
label=""
labelPlacement="outside"
placeholder="Alamat..."
variant="bordered"
value={value}
classNames={{
inputWrapper: [
"border-1 rounded-lg",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
onValueChange={onChange}
/>
)}
/>
{errors.address?.message && (
<p className="text-red-400 text-sm">{errors.address?.message}</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Gender</p>
<Controller
control={control}
name="genderType"
render={({ field: { onChange, value } }) => (
<RadioGroup
orientation="horizontal"
label=""
value={value}
onValueChange={onChange}
>
<Radio value="Male">Laki-laki</Radio>
<Radio value="Female">Perempuan</Radio>
</RadioGroup>
)}
/>
{errors.genderType?.message && (
<p className="text-red-400 text-sm">
{errors.genderType?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<Controller
control={control}
name="userLevelType"
render={({ field: { onChange, value } }) => (
<>
<p className="text-sm mt-3">Level Pengguna</p>
<ReactSelect
className="basic-single text-black z-50"
classNames={{
control: (state: any) =>
"!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500",
}}
classNamePrefix="select"
value={value}
onChange={onChange}
closeMenuOnSelect={false}
components={animatedComponents}
isClearable={true}
isSearchable={true}
isMulti={false}
placeholder=""
name="sub-module"
options={parentList}
/>
</>
)}
/>
{errors.userLevelType?.message && (
<p className="text-red-400 text-sm">
{errors.userLevelType?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<Controller
control={control}
name="userRoleType"
render={({ field: { onChange, value } }) => (
<>
<p className="text-sm mt-3">Peran Pengguna</p>
<ReactSelect
className="basic-single text-black z-49"
classNames={{
control: (state: any) =>
"!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500",
}}
classNamePrefix="select"
value={value}
onChange={onChange}
closeMenuOnSelect={false}
components={animatedComponents}
isClearable={true}
isSearchable={true}
isMulti={false}
placeholder=""
name="sub-module"
options={listRole}
/>
</>
)}
/>
{errors.userRoleType?.message && (
<p className="text-red-400 text-sm">
{errors.userRoleType?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">No. Handphone</p>
<Controller
control={control}
name="phoneNumber"
render={({ field: { onChange, value } }) => (
<Input
type="number"
id="identityNumber"
placeholder="08*********"
label=""
value={value} value={value}
onChange={onChange} onChange={onChange}
closeMenuOnSelect={false} labelPlacement="outside"
components={animatedComponents} className="w-full z-0"
isClearable={true} variant="bordered"
isSearchable={true}
isMulti={false}
placeholder=""
name="sub-module"
options={listRole}
/> />
</> )}
/>
{errors.phoneNumber?.message && (
<p className="text-red-400 text-sm">
{errors.phoneNumber?.message}
</p>
)} )}
/> </div>
{errors.userRoleType?.message && (
<p className="text-red-400 text-sm">
{errors.userRoleType?.message}
</p>
)}
<Controller
control={control}
name="phoneNumber"
render={({ field: { onChange, value } }) => (
<Input
type="number"
id="identityNumber"
placeholder="08*********"
label="No. Handphone"
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full z-0"
variant="bordered"
/>
)}
/>
{errors.phoneNumber?.message && (
<p className="text-red-400 text-sm">
{errors.phoneNumber?.message}
</p>
)}
<div className="flex justify-end gap-3"> <div className="flex justify-end gap-3">
<Link href={`/admin/master-user`}> <Link href={`/admin/master-user`}>
<Button color="danger" variant="ghost"> <Button color="danger" variant="ghost">

View File

@ -110,7 +110,7 @@ const sidebarLevel3 = [
name: "Artikel", name: "Artikel",
moduleId: 652, moduleId: 652,
moduleName: "Dashboard", moduleName: "Dashboard",
modulePathUrl: `/admin/article?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/article `,
parentId: -1, parentId: -1,
icon: <ArticleIcon size={24} />, icon: <ArticleIcon size={24} />,
position: 1, position: 1,
@ -124,7 +124,7 @@ const sidebarLevel3 = [
name: "Kategori", name: "Kategori",
moduleId: 654, moduleId: 654,
moduleName: "Master", moduleName: "Master",
modulePathUrl: `/admin/master-category?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/master-category `,
parentId: -1, parentId: -1,
icon: <MasterCategoryIcon size={22} />, icon: <MasterCategoryIcon size={22} />,
position: 1, position: 1,
@ -185,7 +185,7 @@ const sidebarLevel2 = [
name: "Artikel", name: "Artikel",
moduleId: 652, moduleId: 652,
moduleName: "Dashboard", moduleName: "Dashboard",
modulePathUrl: `/admin/article?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/article `,
parentId: -1, parentId: -1,
icon: <ArticleIcon size={24} />, icon: <ArticleIcon size={24} />,
position: 1, position: 1,
@ -199,7 +199,7 @@ const sidebarLevel2 = [
name: "Kategori", name: "Kategori",
moduleId: 654, moduleId: 654,
moduleName: "Master", moduleName: "Master",
modulePathUrl: `/admin/master-category?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/master-category `,
parentId: -1, parentId: -1,
icon: <MasterCategoryIcon size={22} />, icon: <MasterCategoryIcon size={22} />,
position: 1, position: 1,
@ -702,7 +702,7 @@ const SidebarMobile: React.FC<SidebarProps> = ({ updateSidebarData }) => {
title={item?.name} title={item?.name}
isActive={pathname.includes(item.modulePathUrl)} isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes( isParentActive={pathname.includes(
list.modulePathUrl list.modulePathUrl,
)} )}
path={item.modulePathUrl} path={item.modulePathUrl}
icon={item.icon} icon={item.icon}
@ -710,143 +710,143 @@ const SidebarMobile: React.FC<SidebarProps> = ({ updateSidebarData }) => {
)), )),
]} ]}
/> />
) ),
) )
: rolesId === "2" : rolesId === "2"
? sidebarLevel2?.map((list: any, index: number) => ? sidebarLevel2?.map((list: any, index: number) =>
list.isGroup ? ( list.isGroup ? (
<p <p
key={list.id} key={list.id}
className={`font-bold mr-4 text-white ${ className={`font-bold mr-4 text-white ${
!isOpen ? "text-center" : "" !isOpen ? "text-center" : ""
}`} }`}
>
{isOpen ? list.name : "..."}
</p>
) : list.childMenu?.length < 1 ? (
isOpen ? (
<Link key={list.id} href={list.modulePathUrl}>
<div
className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl)
? "bg-white text-black font-bold"
: "text-white hover:bg-gray-200 hover:text-black"
}`}
>
{list.icon} {isOpen && list.name}
</div>
</Link>
) : (
<Tooltip
content={list.name}
placement="right"
delay={0}
closeDelay={0}
key={list.name}
> >
{isOpen ? list.name : "..."}
</p>
) : list.childMenu?.length < 1 ? (
isOpen ? (
<Link key={list.id} href={list.modulePathUrl}> <Link key={list.id} href={list.modulePathUrl}>
<div <div
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${ className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl) pathname.includes(list.modulePathUrl)
? "bg-zinc-300 text-zinc-500 font-bold hover:text-black" ? "bg-white text-black font-bold"
: "text-zinc-400 hover:text-black" : "text-white hover:bg-gray-200 hover:text-black"
}`} }`}
> >
{list.icon} {isOpen && list.name} {list.icon} {isOpen && list.name}
</div> </div>
</Link> </Link>
</Tooltip> ) : (
) <Tooltip
) : ( content={list.name}
<SidebarCollapseItems placement="right"
key={list.id} delay={0}
title={list.name} closeDelay={0}
isActive={pathname.includes(list.modulePathUrl)} key={list.name}
icon={list.icon}
items={[
list?.childMenu?.map((item: any) => (
<SidebarCollapseSubItems
key={item.id}
title={item?.name}
isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes(
list.modulePathUrl
)}
path={item.modulePathUrl}
icon={item.icon}
/>
)),
]}
/>
)
)
: sidebarLevel3?.map((list: any, index: number) =>
list.isGroup ? (
<p
key={list.id}
className={`font-bold mr-4 text-white ${
!isOpen ? "text-center" : ""
}`}
>
{isOpen ? list.name : "..."}
</p>
) : list.childMenu?.length < 1 ? (
isOpen ? (
<Link key={list.id} href={list.modulePathUrl}>
<div
className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl)
? "bg-white text-black font-bold"
: "text-white hover:bg-gray-200 hover:text-black"
}`}
> >
{list.icon} {isOpen && list.name} <Link key={list.id} href={list.modulePathUrl}>
</div> <div
</Link> className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
pathname.includes(list.modulePathUrl)
? "bg-zinc-300 text-zinc-500 font-bold hover:text-black"
: "text-zinc-400 hover:text-black"
}`}
>
{list.icon} {isOpen && list.name}
</div>
</Link>
</Tooltip>
)
) : ( ) : (
<Tooltip <SidebarCollapseItems
content={list.name} key={list.id}
placement="right" title={list.name}
delay={0} isActive={pathname.includes(list.modulePathUrl)}
closeDelay={0} icon={list.icon}
key={list.name} items={[
list?.childMenu?.map((item: any) => (
<SidebarCollapseSubItems
key={item.id}
title={item?.name}
isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes(
list.modulePathUrl,
)}
path={item.modulePathUrl}
icon={item.icon}
/>
)),
]}
/>
),
)
: sidebarLevel3?.map((list: any, index: number) =>
list.isGroup ? (
<p
key={list.id}
className={`font-bold mr-4 text-white ${
!isOpen ? "text-center" : ""
}`}
> >
{isOpen ? list.name : "..."}
</p>
) : list.childMenu?.length < 1 ? (
isOpen ? (
<Link key={list.id} href={list.modulePathUrl}> <Link key={list.id} href={list.modulePathUrl}>
<div <div
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${ className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl) pathname.includes(list.modulePathUrl)
? "bg-zinc-300 text-zinc-500 font-bold hover:text-black" ? "bg-white text-black font-bold"
: "text-zinc-400 hover:text-black" : "text-white hover:bg-gray-200 hover:text-black"
}`} }`}
> >
{list.icon} {isOpen && list.name} {list.icon} {isOpen && list.name}
</div> </div>
</Link> </Link>
</Tooltip> ) : (
) <Tooltip
) : ( content={list.name}
<SidebarCollapseItems placement="right"
key={list.id} delay={0}
title={list.name} closeDelay={0}
isActive={pathname.includes(list.modulePathUrl)} key={list.name}
icon={list.icon} >
items={[ <Link key={list.id} href={list.modulePathUrl}>
list?.childMenu?.map((item: any) => ( <div
<SidebarCollapseSubItems className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
key={item.id} pathname.includes(list.modulePathUrl)
title={item?.name} ? "bg-zinc-300 text-zinc-500 font-bold hover:text-black"
isActive={pathname.includes(item.modulePathUrl)} : "text-zinc-400 hover:text-black"
isParentActive={pathname.includes( }`}
list.modulePathUrl >
)} {list.icon} {isOpen && list.name}
path={item.modulePathUrl} </div>
icon={item.icon} </Link>
/> </Tooltip>
)), )
]} ) : (
/> <SidebarCollapseItems
) key={list.id}
)} title={list.name}
isActive={pathname.includes(list.modulePathUrl)}
icon={list.icon}
items={[
list?.childMenu?.map((item: any) => (
<SidebarCollapseSubItems
key={item.id}
title={item?.name}
isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes(
list.modulePathUrl,
)}
path={item.modulePathUrl}
icon={item.icon}
/>
)),
]}
/>
),
)}
</SidebarMenu> </SidebarMenu>
</div> </div>
<div <div

View File

@ -37,7 +37,11 @@ import { SidebarMenu } from "./sidebar-menu";
import Image from "next/image"; import Image from "next/image";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { SettingsIcon, UserProfileIcon } from "@/components/icons/globals"; import { SettingsIcon, UserProfileIcon } from "@/components/icons/globals";
import { getCookiesDecrypt, getUnixTimestamp, textEllipsis } from "@/utils/global"; import {
getCookiesDecrypt,
getUnixTimestamp,
textEllipsis,
} from "@/utils/global";
interface SubMenuItems { interface SubMenuItems {
id: number; id: number;
@ -110,7 +114,7 @@ const sidebarLevel3 = [
name: "Artikel", name: "Artikel",
moduleId: 652, moduleId: 652,
moduleName: "Dashboard", moduleName: "Dashboard",
modulePathUrl: `/admin/article?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/article `,
parentId: -1, parentId: -1,
icon: <ArticleIcon size={24} />, icon: <ArticleIcon size={24} />,
position: 1, position: 1,
@ -124,7 +128,7 @@ const sidebarLevel3 = [
name: "Kategori", name: "Kategori",
moduleId: 654, moduleId: 654,
moduleName: "Master", moduleName: "Master",
modulePathUrl: `/admin/master-category?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/master-category `,
parentId: -1, parentId: -1,
icon: <MasterCategoryIcon size={22} />, icon: <MasterCategoryIcon size={22} />,
position: 1, position: 1,
@ -375,7 +379,7 @@ const sidebarLevel1 = [
name: "Artikel", name: "Artikel",
moduleId: 652, moduleId: 652,
moduleName: "Dashboard", moduleName: "Dashboard",
modulePathUrl: `/admin/article?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/article `,
parentId: -1, parentId: -1,
icon: <ArticleIcon size={24} />, icon: <ArticleIcon size={24} />,
position: 1, position: 1,
@ -389,7 +393,7 @@ const sidebarLevel1 = [
name: "Kategori", name: "Kategori",
moduleId: 654, moduleId: 654,
moduleName: "Master", moduleName: "Master",
modulePathUrl: `/admin/master-category?timestamp=${getUnixTimestamp()}`, modulePathUrl: `/admin/master-category `,
parentId: -1, parentId: -1,
icon: <MasterCategoryIcon size={22} />, icon: <MasterCategoryIcon size={22} />,
position: 1, position: 1,
@ -709,7 +713,7 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
title={item?.name} title={item?.name}
isActive={pathname.includes(item.modulePathUrl)} isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes( isParentActive={pathname.includes(
list.modulePathUrl list.modulePathUrl,
)} )}
path={item.modulePathUrl} path={item.modulePathUrl}
icon={item.icon} icon={item.icon}
@ -717,157 +721,157 @@ const Sidebar: React.FC<SidebarProps> = ({ updateSidebarData }) => {
)), )),
]} ]}
/> />
) ),
) )
: rolesId === "2" : rolesId === "2"
? sidebarLevel2?.map((list: any, index: number) => ? sidebarLevel2?.map((list: any, index: number) =>
list.isGroup ? ( list.isGroup ? (
<p <p
key={list.id} key={list.id}
className={`font-bold mr-4 text-white ${ className={`font-bold mr-4 text-white ${
!isOpen ? "text-center" : "" !isOpen ? "text-center" : ""
}`} }`}
> >
{isOpen ? list.name : "..."} {isOpen ? list.name : "..."}
</p> </p>
) : list.childMenu?.length < 1 ? ( ) : list.childMenu?.length < 1 ? (
isOpen ? ( isOpen ? (
<Link key={list.id} href={list.modulePathUrl}> <Link key={list.id} href={list.modulePathUrl}>
{/* <div {/* <div
className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${ className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl) pathname.includes(list.modulePathUrl)
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold" ? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
: "text-zinc-600 dark:text-zinc-400" : "text-zinc-600 dark:text-zinc-400"
}`} }`}
> */} > */}
<div
className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl)
? "bg-white text-black font-bold"
: "text-white hover:bg-gray-200 hover:text-black"
}`}
>
{list.icon} {isOpen && list.name}
</div>
</Link>
) : (
<Tooltip
content={list.name}
placement="right"
delay={0}
closeDelay={0}
key={list.name}
>
<Link key={list.id} href={list.modulePathUrl}>
<div <div
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${ className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl) pathname.includes(list.modulePathUrl)
? "bg-zinc-300 text-zinc-500 font-bold hover:text-black" ? "bg-white text-black font-bold"
: "text-zinc-400 hover:text-black" : "text-white hover:bg-gray-200 hover:text-black"
}`} }`}
> >
{list.icon} {isOpen && list.name} {list.icon} {isOpen && list.name}
</div> </div>
</Link> </Link>
</Tooltip> ) : (
) <Tooltip
) : ( content={list.name}
<SidebarCollapseItems placement="right"
key={list.id} delay={0}
title={list.name} closeDelay={0}
isActive={pathname.includes(list.modulePathUrl)} key={list.name}
icon={list.icon} >
items={[ <Link key={list.id} href={list.modulePathUrl}>
list?.childMenu?.map((item: any, index: number) => ( <div
<SidebarCollapseSubItems className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
key={item.id + index} pathname.includes(list.modulePathUrl)
title={item?.name} ? "bg-zinc-300 text-zinc-500 font-bold hover:text-black"
isActive={pathname.includes(item.modulePathUrl)} : "text-zinc-400 hover:text-black"
isParentActive={pathname.includes( }`}
list.modulePathUrl >
)} {list.icon} {isOpen && list.name}
path={item.modulePathUrl} </div>
icon={item.icon} </Link>
/> </Tooltip>
)), )
]} ) : (
/> <SidebarCollapseItems
key={list.id}
title={list.name}
isActive={pathname.includes(list.modulePathUrl)}
icon={list.icon}
items={[
list?.childMenu?.map((item: any, index: number) => (
<SidebarCollapseSubItems
key={item.id + index}
title={item?.name}
isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes(
list.modulePathUrl,
)}
path={item.modulePathUrl}
icon={item.icon}
/>
)),
]}
/>
),
) )
) : sidebarLevel3?.map((list: any, index: number) =>
: sidebarLevel3?.map((list: any, index: number) => list.isGroup ? (
list.isGroup ? ( <p
<p key={list.id}
key={list.id} className={`font-bold mr-4 text-white ${
className={`font-bold mr-4 text-white ${ !isOpen ? "text-center" : ""
!isOpen ? "text-center" : "" }`}
}`} >
> {isOpen ? list.name : "..."}
{isOpen ? list.name : "..."} </p>
</p> ) : list.childMenu?.length < 1 ? (
) : list.childMenu?.length < 1 ? ( isOpen ? (
isOpen ? ( <Link key={list.id} href={list.modulePathUrl}>
<Link key={list.id} href={list.modulePathUrl}> {/* <div
{/* <div
className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${ className={`px-3.5 py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl) pathname.includes(list.modulePathUrl)
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold" ? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
: "text-zinc-600 dark:text-zinc-400" : "text-zinc-600 dark:text-zinc-400"
}`} }`}
> */} > */}
<div
className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl)
? "bg-white text-black font-bold"
: "text-white hover:bg-gray-200 hover:text-black"
}`}
>
{list.icon} {isOpen && list.name}
</div>
</Link>
) : (
<Tooltip
content={list.name}
placement="right"
delay={0}
closeDelay={0}
key={list.id}
>
<Link key={list.id} href={list.modulePathUrl}>
<div <div
className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${ className={`px-3.5 py-2 mr-4 rounded-lg flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl) pathname.includes(list.modulePathUrl)
? "bg-zinc-300 text-zinc-500 font-bold hover:text-black" ? "bg-white text-black font-bold"
: "text-zinc-400 hover:text-black" : "text-white hover:bg-gray-200 hover:text-black"
}`} }`}
> >
{list.icon} {isOpen && list.name} {list.icon} {isOpen && list.name}
</div> </div>
</Link> </Link>
</Tooltip> ) : (
) <Tooltip
) : ( content={list.name}
<SidebarCollapseItems placement="right"
key={list.id} delay={0}
title={list.name} closeDelay={0}
isActive={pathname.includes(list.modulePathUrl)} key={list.id}
icon={list.icon} >
items={[ <Link key={list.id} href={list.modulePathUrl}>
list?.childMenu?.map((item: any, index: number) => ( <div
<SidebarCollapseSubItems className={`py-2 mr-4 rounded-lg hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row justify-center gap-1 ${
key={item.id + index} pathname.includes(list.modulePathUrl)
title={item?.name} ? "bg-zinc-300 text-zinc-500 font-bold hover:text-black"
isActive={pathname.includes(item.modulePathUrl)} : "text-zinc-400 hover:text-black"
isParentActive={pathname.includes( }`}
list.modulePathUrl >
)} {list.icon} {isOpen && list.name}
path={item.modulePathUrl} </div>
icon={item.icon} </Link>
/> </Tooltip>
)), )
]} ) : (
/> <SidebarCollapseItems
) key={list.id}
)} title={list.name}
isActive={pathname.includes(list.modulePathUrl)}
icon={list.icon}
items={[
list?.childMenu?.map((item: any, index: number) => (
<SidebarCollapseSubItems
key={item.id + index}
title={item?.name}
isActive={pathname.includes(item.modulePathUrl)}
isParentActive={pathname.includes(
list.modulePathUrl,
)}
path={item.modulePathUrl}
icon={item.icon}
/>
)),
]}
/>
),
)}
</SidebarMenu> </SidebarMenu>
</div> </div>
<div <div

View File

@ -99,6 +99,8 @@ export default function ListNews() {
null, null,
); );
const requestIdRef = useRef(0);
const handleMonthClick = (monthIndex: number) => { const handleMonthClick = (monthIndex: number) => {
setSelectedMonth(monthIndex); setSelectedMonth(monthIndex);
setSelectedDate(new Date(year, monthIndex, 1)); setSelectedDate(new Date(year, monthIndex, 1));
@ -110,18 +112,15 @@ export default function ListNews() {
const getPoldaCategId = async () => { const getPoldaCategId = async () => {
const poldaNow = category as string; const poldaNow = category as string;
console.log("poldanow", poldaNow.split("-").join(" "));
const dataNow = await getCategory(poldaNow.split("-").join(" ")); const dataNow = await getCategory(poldaNow.split("-").join(" "));
console.log("datanow", dataNow);
if (dataNow?.length > 0) { if (dataNow?.length > 0) {
const poldaObj = dataNow[0]; const poldaObj = dataNow[0];
setPoldaCategId(poldaObj.id); setPoldaCategId(poldaObj.id);
const option = setupCategory([poldaObj])[0]; // <-- wajib array const option = setupCategory([poldaObj])[0];
setPoldaCategoryOption(option); setPoldaCategoryOption(option);
// pastikan masuk ke selectedCategoryId (kalau belum ada)
setSelectedCategoryId((prev: any[]) => { setSelectedCategoryId((prev: any[]) => {
const already = prev.some((x) => x.id === option.id); const already = prev.some((x) => x.id === option.id);
if (already) return prev; if (already) return prev;
@ -137,7 +136,6 @@ export default function ListNews() {
const yearQ = searchParams.get("year"); const yearQ = searchParams.get("year");
const pageQ = searchParams.get("page"); const pageQ = searchParams.get("page");
// sync UI state dari URL
setSearchValue(search); setSearchValue(search);
if (pageQ) setPage(Number(pageQ)); if (pageQ) setPage(Number(pageQ));
@ -185,7 +183,7 @@ export default function ListNews() {
const yearQ = searchParams.get("year"); const yearQ = searchParams.get("year");
const pageQ = searchParams.get("page"); const pageQ = searchParams.get("page");
getArticle({ await getArticle({
title: search, title: search,
category: setup, category: setup,
month: month ? Number(month) : null, month: month ? Number(month) : null,
@ -205,25 +203,24 @@ export default function ListNews() {
year?: number | null; year?: number | null;
page?: number; page?: number;
}) { }) {
requestIdRef.current += 1;
const currentRequestId = requestIdRef.current;
loading(); loading();
const usedPage = props?.page || page; const usedPage = props?.page || page;
const usedSearch = props?.title ?? searchValue ?? ""; const usedSearch = props?.title ?? searchValue ?? "";
// 1) ambil kategori dari props atau state
const baseCategories = const baseCategories =
props?.category && props.category.length > 0 props?.category && props.category.length > 0
? props.category ? props.category
: selectedCategoryId; : selectedCategoryId;
// 2) kalau halaman polda, pastikan poldaCategId selalu ikut
let finalCategories = baseCategories; let finalCategories = baseCategories;
if (isPoldaPage && poldaCategId) { if (isPoldaPage && poldaCategId) {
const hasPolda = baseCategories.some((x: any) => x.id === poldaCategId); const hasPolda = baseCategories.some((x: any) => x.id === poldaCategId);
if (!hasPolda) { if (!hasPolda) {
// polda wajib selalu ada
finalCategories = [ finalCategories = [
...(poldaCategoryOption ? [poldaCategoryOption] : []), ...(poldaCategoryOption ? [poldaCategoryOption] : []),
...baseCategories, ...baseCategories,
@ -259,12 +256,22 @@ export default function ListNews() {
usedMonth && usedYear usedMonth && usedYear
? convertDateFormatNoTimeV2(new Date(usedYear, usedMonth, 0)) ? convertDateFormatNoTimeV2(new Date(usedYear, usedMonth, 0))
: "", : "",
timeStamp: getUnixTimestamp(),
}; };
const response = await getListArticle(req); try {
setArticle(response?.data?.data); const response = await getListArticle(req);
setTotalPage(response?.data?.meta?.totalPage);
close(); // ❗ GUARD: hanya request terakhir yang boleh set state
if (currentRequestId !== requestIdRef.current) return;
setArticle(response?.data?.data);
setTotalPage(response?.data?.meta?.totalPage);
} finally {
if (currentRequestId === requestIdRef.current) {
close();
}
}
} }
const debounceTimeout = useRef<NodeJS.Timeout | null>(null); const debounceTimeout = useRef<NodeJS.Timeout | null>(null);
@ -379,20 +386,17 @@ export default function ListNews() {
onChange={(val: any) => { onChange={(val: any) => {
const nextValue = val || []; const nextValue = val || [];
// kalau bukan halaman polda, normal
if (!pathname.includes("/news/polda/")) { if (!pathname.includes("/news/polda/")) {
setSelectedCategoryId(nextValue); setSelectedCategoryId(nextValue);
return; return;
} }
// halaman polda: kategori polda harus tetap ada
if (poldaCategoryOption) { if (poldaCategoryOption) {
const hasPolda = nextValue.some( const hasPolda = nextValue.some(
(x: any) => x.id === poldaCategoryOption.id, (x: any) => x.id === poldaCategoryOption.id,
); );
if (!hasPolda) { if (!hasPolda) {
// user mencoba hapus kategori polda -> balikin lagi
setSelectedCategoryId([poldaCategoryOption, ...nextValue]); setSelectedCategoryId([poldaCategoryOption, ...nextValue]);
return; return;
} }

View File

@ -67,22 +67,22 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
switch (platform) { switch (platform) {
case "facebook": case "facebook":
shareLink = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent( shareLink = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
urls urls,
)}`; )}`;
break; break;
case "x": case "x":
shareLink = `https://x.com/intent/tweet?url=${encodeURIComponent( shareLink = `https://x.com/intent/tweet?url=${encodeURIComponent(
urls urls,
)}&text=${encodeURIComponent(shareText)}`; )}&text=${encodeURIComponent(shareText)}`;
break; break;
case "linkedin": case "linkedin":
shareLink = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent( shareLink = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(
urls urls,
)}`; )}`;
break; break;
case "whatsapp": case "whatsapp":
shareLink = `https://wa.me/?text=${encodeURIComponent( shareLink = `https://wa.me/?text=${encodeURIComponent(
shareText + " " + urls shareText + " " + urls,
)}`; )}`;
break; break;
default: default:
@ -97,14 +97,14 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
window.open( window.open(
shareLink, shareLink,
"_blank", "_blank",
`width=${popupWidth},height=${popupHeight},top=${top},left=${left},resizable=no,scrollbars=no,toolbar=no,menubar=no,status=no` `width=${popupWidth},height=${popupHeight},top=${top},left=${left},resizable=no,scrollbars=no,toolbar=no,menubar=no,status=no`,
); );
}; };
useEffect(() => { useEffect(() => {
if (listArticle) { if (listArticle) {
const index = listArticle?.findIndex( const index = listArticle?.findIndex(
(item: any) => item?.id === data?.id (item: any) => item?.id === data?.id,
); );
if (index - 1 == -1) { if (index - 1 == -1) {
setPrevArticle(""); setPrevArticle("");
@ -165,6 +165,18 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
{data?.viewCount === null ? 0 : data?.viewCount} {data?.viewCount === null ? 0 : data?.viewCount}
</p> </p>
</div> </div>
{data?.files?.length < 1 && data?.thumbnailUrl !== "" && (
<Image
classNames={{
wrapper: "!w-full !max-w-full",
img: "!w-full",
}}
alt="Main Image"
src={data.thumbnailUrl}
className="object-cover w-[100%] rounded-md"
/>
)}{" "}
<div className="flex justify-center my-2 lg:my-5"></div>
<div className="flex justify-center my-2 lg:my-5"> <div className="flex justify-center my-2 lg:my-5">
{data?.files?.length > 0 ? ( {data?.files?.length > 0 ? (
data.files[0]?.file_name.split(".")[1].includes("doc") || data.files[0]?.file_name.split(".")[1].includes("doc") ||
@ -273,11 +285,10 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
))} ))}
<div <div
dangerouslySetInnerHTML={removeImgTags( dangerouslySetInnerHTML={removeImgTags(
formatTextToHtmlTag(data?.htmlDescription) formatTextToHtmlTag(data?.htmlDescription),
)} )}
className="text-sm lg:text-xl lg:leading-8 text-justify space-y-4" className="text-sm lg:text-xl lg:leading-8 text-justify space-y-4"
/> />
<div className="bg-gray-50 dark:bg-black text-black dark:text-white rounded-lg justify-center items-center p-4 flex flex-col gap-3"> <div className="bg-gray-50 dark:bg-black text-black dark:text-white rounded-lg justify-center items-center p-4 flex flex-col gap-3">
<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">
@ -306,7 +317,7 @@ export default function DetailNews(props: { data: any; listArticle: any }) {
{tag} {tag}
</Button> </Button>
</Link> </Link>
) ),
)} )}
</div> </div>
</div> </div>

View File

@ -9,7 +9,11 @@ import "swiper/css/pagination";
import Link from "next/link"; import Link from "next/link";
import { getListArticle } from "@/services/article"; import { getListArticle } from "@/services/article";
import { Card, CardFooter, Skeleton } from "@heroui/react"; import { Card, CardFooter, Skeleton } from "@heroui/react";
import { convertDateFormat, textEllipsis } from "@/utils/global"; import {
convertDateFormat,
getUnixTimestamp,
textEllipsis,
} from "@/utils/global";
import Image from "next/image"; import Image from "next/image";
export default function SidebarDetail() { export default function SidebarDetail() {
@ -27,7 +31,9 @@ export default function SidebarDetail() {
search: "", search: "",
limit: "10", limit: "10",
isPublish: true, isPublish: true,
category: "781,802", sort: "desc",
categoryIds: "791,802",
timeStamp: getUnixTimestamp(),
}; };
const response = await getListArticle(req); const response = await getListArticle(req);
@ -35,12 +41,14 @@ export default function SidebarDetail() {
} }
async function getArticlePolda() { async function getArticlePolda() {
console.log("jalan");
const req = { const req = {
page: 1, page: 1,
search: "", search: "",
limit: "10", limit: "10",
sort: "desc",
isPublish: true, isPublish: true,
isPolda: true, timeStamp: getUnixTimestamp(),
}; };
const response = await getListArticle(req); const response = await getListArticle(req);

View File

@ -61,6 +61,7 @@ import { parseDate } from "@internationalized/date";
import { listMasterUsers } from "@/services/master-user"; import { listMasterUsers } from "@/services/master-user";
import ReactSelect from "react-select"; import ReactSelect from "react-select";
import makeAnimated from "react-select/animated"; import makeAnimated from "react-select/animated";
import { useRouter, useSearchParams } from "next/navigation";
const columns = [ const columns = [
{ name: "No", uid: "no" }, { name: "No", uid: "no" },
@ -100,11 +101,14 @@ export default function ArticleTable() {
const animatedComponents = makeAnimated(); const animatedComponents = makeAnimated();
const roleId = getCookiesDecrypt("urie"); const roleId = getCookiesDecrypt("urie");
const [page, setPage] = useState(1); const router = useRouter();
const searchParams = useSearchParams();
const [page, setPage] = useState(Number(searchParams.get("page")) || 1);
const [totalPage, setTotalPage] = useState(1); const [totalPage, setTotalPage] = useState(1);
const [article, setArticle] = useState<any[]>([]); const [article, setArticle] = useState<any[]>([]);
const [showData, setShowData] = useState("10"); const [showData, setShowData] = useState(searchParams.get("limit") || "10");
const [search, setSearch] = useState(""); const [search, setSearch] = useState(searchParams.get("search") || "");
const [categories, setCategories] = useState<any>([]); const [categories, setCategories] = useState<any>([]);
const [users, setUsers] = useState<any>([]); const [users, setUsers] = useState<any>([]);
const [selectedCategories, setSelectedCategories] = useState<any>([]); const [selectedCategories, setSelectedCategories] = useState<any>([]);
@ -116,36 +120,90 @@ export default function ArticleTable() {
const [selectedArticles, setSelectedArticles] = useState<any>(new Set([])); const [selectedArticles, setSelectedArticles] = useState<any>(new Set([]));
// const [articleDate, setArticleDate] = useState<any>({
// startDate: parseDate(
// convertDateFormatNoTimeV2(
// new Date(new Date().setDate(new Date().getDate() - 7)),
// ),
// ),
// endDate: parseDate(convertDateFormatNoTimeV2(new Date())),
// });
const [articleDate, setArticleDate] = useState<any>({ const [articleDate, setArticleDate] = useState<any>({
startDate: parseDate( startDate: searchParams.get("startDate")
convertDateFormatNoTimeV2( ? parseDate(searchParams.get("startDate")!)
new Date(new Date().setDate(new Date().getDate() - 7)) : null,
) endDate: searchParams.get("endDate")
), ? parseDate(searchParams.get("endDate")!)
endDate: parseDate(convertDateFormatNoTimeV2(new Date())), : null,
}); });
useEffect(() => {
initState();
}, [page, showData]);
useEffect(() => {
if (page == 1) {
initState();
} else {
setPage(1);
}
}, [selectedCategories, selectedUsers, articleDate, startDateValue]);
useEffect(() => { useEffect(() => {
getCategories(); getCategories();
getUsers(); getUsers();
}, []); }, []);
useEffect(() => {
initState();
}, [searchParams]);
const updateQuery = (params: Record<string, any>) => {
const current = new URLSearchParams(searchParams.toString());
Object.entries(params).forEach(([key, value]) => {
if (
value === undefined ||
value === null ||
value === "" ||
(Array.isArray(value) && value.length === 0)
) {
current.delete(key);
} else {
current.set(key, String(value));
}
});
router.push(`?${current.toString()}`);
};
const setupList = (data: any, type: string) => { const setupList = (data: any, type: string) => {
const temp = []; const temp = [];
const categoryIds = searchParams.get("categoryIds") || "";
const createdByIds = searchParams.get("createdByIds") || "";
let tempCateg: number[] = [];
let tempCreated: number[] = [];
const selectedCat: any[] = [];
const selectedUsr: any[] = [];
if (categoryIds && categoryIds !== "") {
tempCateg = categoryIds
.split(",")
.map((id) => Number(id))
.filter((id) => !isNaN(id));
}
if (createdByIds && createdByIds !== "") {
tempCreated = createdByIds
.split(",")
.map((id) => Number(id))
.filter((id) => !isNaN(id));
}
if (data) { if (data) {
for (const element of data) { for (const element of data) {
if (type === "category" && tempCateg.includes(element.id)) {
selectedCat.push({
id: element.id,
label: element.title,
value: element.id,
});
}
if (type === "users" && tempCreated.includes(element.id)) {
selectedUsr.push({
id: element.id,
label: element.fullname,
value: element.id,
});
}
temp.push({ temp.push({
id: element.id, id: element.id,
label: element.title || element.fullname, label: element.title || element.fullname,
@ -154,10 +212,12 @@ export default function ArticleTable() {
} }
if (type === "users") { if (type === "users") {
setUsers(temp); setUsers(temp);
setSelectedUsers(selectedUsr);
} }
if (type === "category") { if (type === "category") {
setCategories(temp); setCategories(temp);
setSelectedCategories(selectedCat);
} }
} }
}; };
@ -208,16 +268,15 @@ export default function ArticleTable() {
async function initState() { async function initState() {
loading(); loading();
const req = { const req = {
limit: showData, limit: searchParams.get("limit") || showData,
page: page, page: Number(searchParams.get("page")) || "",
search: search, search: searchParams.get("search") || "",
startDate: getDate(articleDate.startDate), startDate: searchParams.get("startDate") || "",
endDate: getDate(articleDate.endDate), endDate: searchParams.get("endDate") || "",
categoryIds: getIds(selectedCategories), categoryIds: searchParams.get("categoryIds") || "",
createdByIds: getIds(selectedUsers), createdByIds: searchParams.get("createdByIds") || "",
sort: "desc", sort: "desc",
sortBy: "created_at", sortBy: "created_at",
timeStamp: getUnixTimestamp(),
}; };
const res = await getListArticleAdminPage(req); const res = await getListArticleAdminPage(req);
await getTableNumber(parseInt(showData), res.data?.data); await getTableNumber(parseInt(showData), res.data?.data);
@ -467,7 +526,7 @@ export default function ArticleTable() {
return cellValue; return cellValue;
} }
}, },
[article, page, selectedArticles] [article, page, selectedArticles],
); );
let typingTimer: NodeJS.Timeout; let typingTimer: NodeJS.Timeout;
@ -487,6 +546,19 @@ export default function ArticleTable() {
initState(); initState();
} }
const handleApplyFilter = () => {
setPage(1);
updateQuery({
page: 1,
limit: showData,
search,
categoryIds: getIds(selectedCategories),
createdByIds: getIds(selectedUsers),
startDate: getDate(articleDate.startDate),
endDate: getDate(articleDate.endDate),
});
};
const [hasMounted, setHasMounted] = useState(false); const [hasMounted, setHasMounted] = useState(false);
useEffect(() => { useEffect(() => {
@ -507,16 +579,17 @@ export default function ArticleTable() {
aria-label="Search" aria-label="Search"
classNames={{ classNames={{
inputWrapper: "bg-default-100", inputWrapper: "bg-default-100",
input: "text-sm", input: "text-sm outline-none",
}} }}
labelPlacement="outside" labelPlacement="outside"
startContent={ startContent={
<SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" /> <SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" />
} }
type="text" type="text"
value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
onKeyUp={handleKeyUp} // onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown} // onKeyDown={handleKeyDown}
/> />
</div> </div>
<div className="flex flex-col gap-1 w-full lg:w-[72px]"> <div className="flex flex-col gap-1 w-full lg:w-[72px]">
@ -554,6 +627,7 @@ export default function ArticleTable() {
isClearable={true} isClearable={true}
isSearchable={true} isSearchable={true}
isMulti={true} isMulti={true}
value={selectedCategories}
placeholder="Kategori..." placeholder="Kategori..."
name="sub-module" name="sub-module"
options={categories} options={categories}
@ -564,7 +638,7 @@ export default function ArticleTable() {
<p className="font-semibold text-sm">Author</p> <p className="font-semibold text-sm">Author</p>
<ReactSelect <ReactSelect
className="basic-single text-black z-40" className="basic-single text-black z-30"
classNames={{ classNames={{
control: (state: any) => control: (state: any) =>
"!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500", "!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500",
@ -572,6 +646,7 @@ export default function ArticleTable() {
classNamePrefix="select" classNamePrefix="select"
onChange={setSelectedUsers} onChange={setSelectedUsers}
closeMenuOnSelect={false} closeMenuOnSelect={false}
value={selectedUsers}
components={animatedComponents} components={animatedComponents}
isClearable={true} isClearable={true}
isSearchable={true} isSearchable={true}
@ -666,6 +741,42 @@ export default function ArticleTable() {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</div> </div>
<div className="flex flex-col gap-1 ">
<p className="font-semibold text-sm">Filter</p>
<div className="flex flex-row gap-1">
<Button
color="primary"
className="h-[42px] w-full"
onPress={handleApplyFilter}
>
Cari
</Button>
<Button
variant="bordered"
className="w-full"
color="warning"
onPress={() => {
setSearch("");
setSelectedCategories([]);
setSelectedUsers([]);
setArticleDate({ startDate: null, endDate: null });
setPage(1);
setShowData("10");
updateQuery({
page: 1,
limit: "10",
search: "",
categoryIds: [],
createdByIds: [],
startDate: "",
endDate: "",
});
}}
>
Reset
</Button>
</div>
</div>
</div> </div>
<Table <Table
aria-label="micro issue table" aria-label="micro issue table"
@ -717,7 +828,10 @@ export default function ArticleTable() {
}} }}
page={page} page={page}
total={totalPage} total={totalPage}
onChange={(page) => setPage(page)} onChange={(p) => {
setPage(p);
updateQuery({ page: p });
}}
/> />
</div> </div>
</div> </div>

View File

@ -1,5 +1,8 @@
import { PaginationRequest } from "@/types/globals"; import { PaginationRequest } from "@/types/globals";
import { httpGet, httpPost, httpPut } from "./http-config/axios-base-service"; import { httpGet, httpPost, httpPut } from "./http-config/axios-base-service";
import Cookies from "js-cookie";
const token = Cookies.get("access_token");
export async function saveActivity(data: any, token?: string) { export async function saveActivity(data: any, token?: string) {
const headers = token const headers = token
@ -22,7 +25,10 @@ export async function getActivity() {
export async function getVisitorLog(data: any) { export async function getVisitorLog(data: any) {
const { page, limit, startDate, endDate } = data; const { page, limit, startDate, endDate } = data;
const headers = { "content-type": "application/json" }; const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
const pathUrl = `/activity-logs?purpose=visitor-summary&page=${ const pathUrl = `/activity-logs?purpose=visitor-summary&page=${
page || 0 page || 0
}&limit=${limit || 10}&startDate=${startDate || ""}&endDate=${endDate || ""}`; }&limit=${limit || 10}&startDate=${startDate || ""}&endDate=${endDate || ""}`;

View File

@ -30,9 +30,7 @@ export async function getAdvertise(data: any) {
}; };
const pathUrl = `/advertisement?page=${data?.page || 1}&limit=${ const pathUrl = `/advertisement?page=${data?.page || 1}&limit=${
data?.limit || "" data?.limit || ""
}&placement=${data?.placement || ""}&isPublish=${ }&placement=${data?.placement || ""}&isPublish=${data.isPublish || ""}`;
data.isPublish || ""
}&timeStamp=${data.timeStamp || ""}`;
return await httpGet(pathUrl, headers); return await httpGet(pathUrl, headers);
} }
@ -42,9 +40,7 @@ export async function getAdvertiseAdmin(data: any) {
}; };
const pathUrl = `/advertisement?page=${data?.page || 1}&limit=${ const pathUrl = `/advertisement?page=${data?.page || 1}&limit=${
data?.limit || "" data?.limit || ""
}&placement=${data?.placement || ""}&isPublish=${ }&placement=${data?.placement || ""}&isPublish=${data.isPublish || ""}`;
data.isPublish || ""
}&timeStamp=${data.timeStamp || ""}`;
return await httpGetInterceptor(pathUrl); return await httpGetInterceptor(pathUrl);
} }

View File

@ -24,6 +24,7 @@ export async function getListArticle(props: PaginationRequest) {
categoryIds, categoryIds,
createdByIds, createdByIds,
isPolda, isPolda,
timeStamp,
} = props; } = props;
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
@ -42,7 +43,7 @@ export async function getListArticle(props: PaginationRequest) {
); );
} }
export async function getListArticleAdminPage(props: PaginationRequest) { export async function getListArticleAdminPage(props: any) {
const { const {
page, page,
limit, limit,
@ -68,7 +69,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 || ""}`,
); );
} }
@ -88,9 +89,7 @@ export async function getTopArticles(props: PaginationRequest) {
isPublish === undefined ? "" : isPublish isPublish === undefined ? "" : isPublish
}&title=${search}&startDate=${startDate || ""}&endDate=${ }&title=${search}&startDate=${startDate || ""}&endDate=${
endDate || "" endDate || ""
}&category=${category || ""}&sortBy=view_count&sort=desc&timeStamp=${ }&category=${category || ""}&sortBy=view_count&sort=desc`,
timeStamp || ""
}`,
); );
} }
@ -156,9 +155,7 @@ export async function getArticleByCategory(timeStamp: number) {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGetInterceptor( return await httpGetInterceptor(`/article-categories?limit=1000`);
`/article-categories?limit=1000&timeStamp=${timeStamp}`,
);
} }
export async function getCategoryPagination(data: any) { export async function getCategoryPagination(data: any) {
const headers = { const headers = {
@ -167,7 +164,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}`,
); );
} }
@ -216,14 +213,14 @@ 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}`, }`,
); );
} }
export async function getStatisticForMaps(startDate: string, endDate: string) { export async function getStatisticForMaps(startDate: string, endDate: string) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
// Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
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}`,
@ -237,7 +234,7 @@ export async function getStatisticVisitorsBrowser(
) { ) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
// Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
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}`,
@ -249,9 +246,7 @@ export async function getStatisticMonthly(year: string, timeStamp: number) {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGetInterceptor( return await httpGetInterceptor(`/articles/statistic/monthly?year=${year}`);
`/articles/statistic/monthly?year=${year}&timeStamp=${timeStamp}`,
);
} }
export async function getStatisticVisitorsMonthly(year: string) { export async function getStatisticVisitorsMonthly(year: string) {
const headers = { const headers = {
@ -281,19 +276,14 @@ export async function getStatisticMonthlyFeedback(
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGet( return await httpGet(`/feedbacks/statistic/monthly?year=${year}`, headers);
`/feedbacks/statistic/monthly?year=${year}&timeStamp=${timeStamp}`,
headers,
);
} }
export async function getStatisticSummary(timeStamp: number) { export async function getStatisticSummary(timeStamp: number) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpGetInterceptor( return await httpGetInterceptor(`/articles/statistic/summary`);
`/articles/statistic/summary?timeStamp=${timeStamp}`,
);
} }
export async function submitApproval(data: { export async function submitApproval(data: {
@ -326,7 +316,7 @@ export async function getArticleByCategoryLanding(props: {
"content-type": "application/json", "content-type": "application/json",
}; };
return await httpGet( return await httpGet(
`/article-categories?limit=${props.limit}&title=${props.title}&timeStamp=${props?.timeStamp}`, `/article-categories?limit=${props.limit}&title=${props.title}`,
headers, headers,
); );
} }

View File

@ -15,9 +15,7 @@ export async function getComments(data: any) {
}; };
const pathUrl = `/article-comments?page=${data?.page || 1}&limit=${ const pathUrl = `/article-comments?page=${data?.page || 1}&limit=${
data?.limit || "" data?.limit || ""
}&message=${data?.search || ""}&parentId=0&timeStamp=${ }&message=${data?.search || ""}&parentId=0`;
data?.timeStamp || ""
}`;
return await httpGetInterceptor(pathUrl); return await httpGetInterceptor(pathUrl);
} }

View File

@ -26,7 +26,7 @@ export async function getFeedbacks(data: any) {
data?.limit || "" data?.limit || ""
}&message=${data?.search || ""}&startDate=${data.startDate || ""}&endDate=${ }&message=${data?.search || ""}&startDate=${data.startDate || ""}&endDate=${
data.endDate || "" data.endDate || ""
}&timeStamp=${data.timeStamp}`; }`;
return await httpGetInterceptor(pathUrl); return await httpGetInterceptor(pathUrl);
} }

View File

@ -22,8 +22,8 @@ export async function getListMagazine(props: PaginationRequest) {
return await httpGet( return await httpGet(
`/magazines?limit=${limit}&page=${page}&title=${search}&startDate=${ `/magazines?limit=${limit}&page=${page}&title=${search}&startDate=${
startDate || "" startDate || ""
}&endDate=${endDate || ""}&timeStamp=${timeStamp || ""}`, }&endDate=${endDate || ""}`,
headers headers,
); );
} }

View File

@ -44,7 +44,7 @@ export async function deleteCategory(id: number) {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}; };
return await httpPut(`article-categories/delete/${id}`, headers); return await httpDeleteInterceptor(`article-categories/${id}`, headers);
} }
export async function uploadCategoryThumbnail(id: string, data: any) { export async function uploadCategoryThumbnail(id: string, data: any) {

View File

@ -14,9 +14,7 @@ export async function listUserRole(data: any) {
"content-type": "application/json", "content-type": "application/json",
}; };
return await httpGetInterceptor( return await httpGetInterceptor(
`/user-roles?limit=${data.limit}&page=${data.page}&timeStamp=${ `/user-roles?limit=${data.limit}&page=${data.page}`,
data.timeStamp || ""
}`
); );
} }

View File

@ -15,14 +15,13 @@ const id = getCookiesDecrypt("uie");
export async function listMasterUsers(data: any) { export async function listMasterUsers(data: any) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`,
}; };
return await httpGet( return await httpGet(
`/users?page=${data.page}&limit=${data.limit}&username=${ `/users?page=${data.page}&limit=${data.limit}&username=${
data.username || "" data.username || ""
}&fullname=${data.fullname || ""}&email=${data.email || ""}&timeStamp=${ }&fullname=${data.fullname || ""}&email=${data.email || ""}`,
data.timeStamp || "" headers,
}`,
headers
); );
} }
@ -52,6 +51,7 @@ export async function setupEmail(data: any) {
export async function getDetailMasterUsers(id: string) { export async function getDetailMasterUsers(id: string) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${token}`,
}; };
return await httpGet(`/users/detail/${id}`, headers); return await httpGet(`/users/detail/${id}`, headers);
} }
@ -174,7 +174,7 @@ export async function getArticleComment(id: string) {
}; };
return await httpGet( return await httpGet(
`/article-comments?isPublic=true&articleId=${id}`, `/article-comments?isPublic=true&articleId=${id}`,
headers headers,
); );
} }

View File

@ -33,7 +33,7 @@ export async function getCustomStaticPage(props: PaginationRequest) {
"content-type": "application/json", "content-type": "application/json",
}; };
return await httpGetInterceptor( return await httpGetInterceptor(
`/custom-static-pages?limit=${limit}&page=${page}&title=${search}&timeStamp=${timeStamp}` `/custom-static-pages?limit=${limit}&page=${page}&title=${search}`,
); );
} }
export async function getCustomStaticDetail(id: string) { export async function getCustomStaticDetail(id: string) {

View File

@ -18,9 +18,7 @@ export async function getAllUserLevels(data?: any) {
return await httpGetInterceptor( return await httpGetInterceptor(
`user-levels?limit=${data?.limit || ""}&levelNumber=${ `user-levels?limit=${data?.limit || ""}&levelNumber=${
data?.levelNumber || "" data?.levelNumber || ""
}&name=${data?.search || ""}&page=${data?.page || "1"}&timeStamp=${ }&name=${data?.search || ""}&page=${data?.page || "1"}`,
data?.timeStamp || ""
}`
); );
} }
export async function getUserLevels(id: string) { export async function getUserLevels(id: string) {