feat:ads page admin, fix scheduling publish, edit created time

This commit is contained in:
Rama Priyanto 2025-02-27 18:09:32 +07:00
parent ada307accc
commit 286a8e6946
21 changed files with 1545 additions and 315 deletions

View File

@ -0,0 +1,290 @@
"use client";
import { AddIcon, CloudUploadIcon, TimesIcon } from "@/components/icons";
import AdvertiseTable from "@/components/table/advertise/advertise-table";
import {
Button,
Card,
Chip,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Switch,
Textarea,
useDisclosure,
} from "@heroui/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Controller, useForm } from "react-hook-form";
import { Fragment, useEffect, useState } from "react";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useDropzone } from "react-dropzone";
import { close, error, loading } from "@/config/swal";
import Image from "next/image";
const createArticleSchema = z.object({
title: z.string().min(2, {
message: "Judul harus diisi",
}),
url: z.string().min(2, {
message: "Link harus diisi",
}),
description: z.string().min(2, {
message: "Deskripsi harus diisi",
}),
});
export default function BasicPage() {
const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
const MySwal = withReactContent(Swal);
const [refresh, setRefresh] = useState(false);
const [isHeader, setIsHeader] = useState(false);
const [files, setFiles] = useState<File[]>([]);
const formOptions = {
resolver: zodResolver(createArticleSchema),
defaultValues: { title: "", description: "", url: "" },
};
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
setFiles(acceptedFiles.map((file) => Object.assign(file)));
},
maxFiles: 1,
accept: {
"image/*": [],
},
});
type UserSettingSchema = z.infer<typeof createArticleSchema>;
const {
control,
handleSubmit,
formState: { errors },
} = useForm<UserSettingSchema>(formOptions);
const onSubmit = async (values: z.infer<typeof createArticleSchema>) => {
loading();
const formData = {
title: values.title,
description: values.description,
isHeader: isHeader,
url: values.url,
};
console.log("dataas", formData);
close();
// setRefresh(!refresh);
// MySwal.fire({
// title: "Sukses",
// icon: "success",
// confirmButtonColor: "#3085d6",
// confirmButtonText: "OK",
// }).then((result) => {
// if (result.isConfirmed) {
// }
// });
};
const handleRemoveFile = (file: File) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
return (
<div className="overflow-x-hidden overflow-y-scroll">
<div className="px-2 md:px-4 md:py-4 w-full">
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl p-3">
<Button
size="md"
className="bg-[#F07C00] text-white w-full lg:w-fit"
onPress={onOpen}
>
Buat Baru
<AddIcon />
</Button>
<AdvertiseTable triggerRefresh={refresh} />
</div>
</div>
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="3xl">
<ModalContent>
{() => (
<>
<ModalHeader className="flex flex-col gap-1">
Advertise
</ModalHeader>
<ModalBody>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3"
>
<div className="flex flex-col gap-1">
<p className="text-sm">Judul</p>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
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?.title && (
<p className="text-red-400 text-sm">
{errors.title?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Deskripsi</p>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<Textarea
type="text"
id="description"
placeholder=""
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?.description && (
<p className="text-red-400 text-sm">
{errors.description?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Link</p>
<Controller
control={control}
name="url"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="url"
placeholder=""
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?.url && (
<p className="text-red-400 text-sm">
{errors.url?.message}
</p>
)}
</div>
<p className="text-sm mt-3">Header</p>
<Switch
size="sm"
isSelected={isHeader}
onValueChange={setIsHeader}
>
<p className="text-sm"> {isHeader ? "Ya" : "Tidak"}</p>
</Switch>
<div className="flex flex-col gap-1">
<p className="text-sm mt-3">Thumbnail</p>
{files.length < 1 && (
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUploadIcon />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau
.png. Ukuran maksimal 100mb.)
</div>
</div>
</div>
</Fragment>
)}
{files.length > 0 && (
<div className="flex flex-row gap-2">
<Image
src={URL.createObjectURL(files[0])}
className="w-[30%]"
alt="thumbnail"
width={480}
height={480}
/>
<Button
className=" border-none rounded-full"
variant="bordered"
onClick={() => handleRemoveFile(files[0])}
>
<TimesIcon />
</Button>
</div>
)}
</div>
<ModalFooter className="self-end grow items-end">
<Button
color="primary"
type="submit"
isDisabled={files.length < 1}
>
Simpan
</Button>
<Button color="danger" variant="light" onPress={onClose}>
Tutup
</Button>
</ModalFooter>
</form>
</ModalBody>
</>
)}
</ModalContent>
</Modal>
</div>
);
}

View File

@ -22,7 +22,7 @@ const AdminMasterUserLevel = () => {
color="primary"
className="bg-[#F07C00] text-white"
>
Pengguna Baru
Level Baru
<AddIcon />
</Button>
</Link>

View File

@ -22,6 +22,7 @@ import Image from "next/image";
import { Switch } from "@heroui/switch";
import {
createArticle,
createArticleSchedule,
getArticleByCategory,
uploadArticleFile,
uploadArticleThumbnail,
@ -294,11 +295,15 @@ export default function CreateArticleForm() {
tags: values.tags.join(","),
description: htmlToString(removeImgTags(values.description)),
htmlDescription: removeImgTags(values.description),
aiArticleId: await saveArticleToDise(values),
// aiArticleId: await saveArticleToDise(values),
isDraft: status === "draft",
isPublish: status === "publish",
};
console.log(
"ssada",
formData,
`${startDateValue.year}-${startDateValue.month}-${startDateValue.day}`
);
const response = await createArticle(formData);
if (response?.error) {
@ -306,6 +311,7 @@ export default function CreateArticleForm() {
return false;
}
const articleId = response?.data?.data?.id;
if (files?.length > 0) {
const formFiles = new FormData();
@ -331,6 +337,14 @@ export default function CreateArticleForm() {
}
}
if (status === "scheduled") {
const request = {
id: articleId,
date: `${startDateValue.year}-${startDateValue.month}-${startDateValue.day}`,
};
const res = await createArticleSchedule(request);
}
close();
successSubmit("/admin/article");
};
@ -834,11 +848,8 @@ export default function CreateArticleForm() {
<Button
color="primary"
type="submit"
isDisabled={
(isScheduled && startDateValue == null) ||
(isScheduled && timeValue == "")
}
onClick={() =>
isDisabled={isScheduled && startDateValue == null}
onPress={() =>
isScheduled ? setStatus("scheduled") : setStatus("publish")
}
>
@ -847,7 +858,7 @@ export default function CreateArticleForm() {
<Button
color="success"
type="submit"
onClick={() => setStatus("draft")}
onPress={() => setStatus("draft")}
>
<p className="text-white">Draft</p>
</Button>

View File

@ -26,22 +26,27 @@ import {
import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import {
Calendar,
Chip,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Popover,
PopoverContent,
PopoverTrigger,
useDisclosure,
} from "@heroui/react";
import GenerateSingleArticleForm from "./generate-ai-single-form";
import { htmlToString } from "@/utils/global";
import { convertDateFormatNoTime, htmlToString } from "@/utils/global";
import { close, error, loading } from "@/config/swal";
import { useParams, useRouter } from "next/navigation";
import { list } from "postcss";
import GetSeoScore from "./get-seo-score-form";
import Link from "next/link";
import { stringify } from "querystring";
import Cookies from "js-cookie";
const ViewEditor = dynamic(
() => {
@ -104,6 +109,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
const { isDetail } = props;
const params = useParams();
const id = params?.id;
const username = Cookies.get("username");
const animatedComponents = makeAnimated();
const MySwal = withReactContent(Swal);
const router = useRouter();
@ -125,6 +131,8 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
const [approvalStatus, setApprovalStatus] = useState<number>(2);
const [approvalMessage, setApprovalMessage] = useState("");
const [detailData, setDetailData] = useState<any>();
const [startDateValue, setStartDateValue] = useState<any>(null);
const [timeValue, setTimeValue] = useState("00:00");
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
@ -230,7 +238,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
const save = async (values: z.infer<typeof createArticleSchema>) => {
loading();
const formData = {
// id: Number(id),
id: Number(id),
title: values.title,
typeId: 1,
slug: values.slug,
@ -821,42 +829,87 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
{errors?.tags && (
<p className="text-red-400 text-sm mb-3">{errors.tags?.message}</p>
)}
{!isDetail && username === "admin-mabes" && (
<>
<p className="text-sm">Ubah Waktu Pembuatan</p>
<div className="flex flex-row gap-2">
<Popover
placement="bottom"
classNames={{ content: ["!bg-transparent", "p-0"] }}
>
<PopoverTrigger>
<Button
className="w-1/3 !h-[30px] lg:h-[40px] border-1 rounded-lg text-black"
variant="bordered"
>
{startDateValue
? convertDateFormatNoTime(startDateValue)
: "-"}
</Button>
</PopoverTrigger>
<PopoverContent className="bg-transparent">
<Calendar
value={startDateValue}
onChange={setStartDateValue}
/>
</PopoverContent>
</Popover>
<Input
type="time"
variant="bordered"
className="w-fit "
value={timeValue}
onValueChange={setTimeValue}
classNames={{
inputWrapper: [
"border-1 rounded-lg !h-[30px] lg:h-[40px]",
"dark:group-data-[focused=false]:bg-transparent !border-1 dark:!border-gray-400",
],
}}
/>
</div>
</>
)}
</div>
<div className="flex flex-row justify-end gap-3">
{isDetail && detailData?.statusId === 1 && (
<Button
color="primary"
type="button"
onPress={() => {
setApprovalStatus(2);
onOpen();
}}
>
Setujui
</Button>
)}
{isDetail && detailData?.statusId === 1 && (
<Button
color="danger"
type="button"
onPress={() => {
setApprovalStatus(3);
onOpen();
}}
>
Tolak
</Button>
)}
{isDetail &&
(detailData?.statusId === 1 || detailData?.statusId === null) && (
<Button
color="primary"
type="button"
onPress={() => {
setApprovalStatus(2);
onOpen();
}}
>
Setujui
</Button>
)}
{isDetail &&
(detailData?.statusId === 1 || detailData?.statusId === null) && (
<Button
color="danger"
type="button"
onPress={() => {
setApprovalStatus(3);
onOpen();
}}
>
Tolak
</Button>
)}
{!isDetail && (
<Button color="primary" type="submit">
Publish
Simpan
</Button>
)}
{!isDetail && (
{/* {!isDetail && (
<Button color="success" type="button">
<p className="text-white">Draft</p>
</Button>
)}
)} */}
<Link href="/admin/article">
<Button color="danger" variant="bordered" type="button">
Kembali
@ -873,11 +926,11 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
Status :
<span
className={
approvalStatus === 3 ? "text-primary" : "text-danger"
approvalStatus === 2 ? "text-primary" : "text-danger"
}
>
{" "}
{approvalStatus === 3 ? "Disetujui" : "Ditolak"}
{approvalStatus === 2 ? "Disetujui" : "Ditolak"}
</span>
</p>
<Textarea

View File

@ -1,5 +1,5 @@
"use client";
import { error } from "@/config/swal";
import { close, error, loading } from "@/config/swal";
import {
createMasterUser,
editMasterUsers,
@ -26,8 +26,17 @@ import Datepicker from "react-tailwindcss-datepicker";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { z } from "zod";
import { EyeFilledIcon, EyeSlashFilledIcon } from "../icons";
import ReactPasswordChecklist from "react-password-checklist";
import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import { getAllUserLevels } from "@/services/user-levels/user-levels-service";
import { listUserRole } from "@/service/master-user-role";
const userSchema = z.object({
id: z.number(),
label: z.string(),
value: z.string(),
});
const masterUserSchema = z.object({
fullname: z.string().min(1, { message: "Required" }),
@ -37,21 +46,18 @@ const masterUserSchema = z.object({
genderType: z.string().min(1, { message: "Required" }),
phoneNumber: z.string().min(1, { message: "Required" }),
address: z.string().min(1, { message: "Required" }),
userLevelType: userSchema,
userRoleType: userSchema,
});
export default function FormMasterUserEdit() {
const router = useRouter();
const MySwal = withReactContent(Swal);
const [isVisible, setIsVisible] = useState([false, false]);
const [isValidPassword, setIsValidPassword] = useState(false);
const animatedComponents = makeAnimated();
const params = useParams();
const id = params?.id;
const toggleVisibility = (type: number) => {
setIsVisible(
type === 0 ? [!isVisible[0], isVisible[1]] : [isVisible[0], !isVisible[1]]
);
};
const [parentList, setParentList] = useState<any>([]);
const [listRole, setListRole] = useState<any>([]);
const formOptions = {
resolver: zodResolver(masterUserSchema),
@ -74,12 +80,12 @@ export default function FormMasterUserEdit() {
genderType: data.genderType,
identityNumber: data.identityNumber,
phoneNumber: data.phoneNumber,
userLevelId: 2,
userRoleId: 2,
userLevelId: data.userLevelType.id,
userRoleId: data.userRoleType.id,
username: data.username,
};
const response = await editMasterUsers(formData);
const response = await editMasterUsers(formData, String(id));
if (response?.error) {
error(response.message);
@ -123,8 +129,13 @@ export default function FormMasterUserEdit() {
}, [id]);
const initFetch = async () => {
loading();
const res = await getDetailMasterUsers(String(id));
const profile = res?.data?.data;
const listLevel = await fetchUserLevel();
const listRole = await fetchUserRole();
const findLevel = listLevel?.find((a) => a.id === profile.userLevelId);
const findRole = listRole?.find((a) => a.id === profile.userRoleId);
setValue("fullname", profile?.fullname);
setValue("username", profile?.username);
setValue("email", profile?.email);
@ -132,11 +143,56 @@ export default function FormMasterUserEdit() {
setValue("identityNumber", profile?.identityNumber);
setValue("genderType", profile?.genderType);
setValue("phoneNumber", profile?.phoneNumber);
if (findLevel) {
setValue("userLevelType", findLevel);
}
if (findRole) {
setValue("userRoleType", findRole);
}
close();
};
const fetchUserLevel = async () => {
const res = await getAllUserLevels();
if (res?.data?.data) {
return setupParent(res?.data?.data, "level");
}
};
const fetchUserRole = async () => {
const request = {
limit: 100,
page: 1,
};
const res = await listUserRole(request);
if (res?.data?.data) {
return setupParent(res?.data?.data, "role");
}
};
const setupParent = (data: any, type: "level" | "role") => {
const temp = [];
for (const element of data) {
temp.push({
id: element.id,
label: element.name,
value: element.aliasName || element.code,
});
}
if (type === "level") {
setParentList(temp);
} else {
setListRole(temp);
}
return temp;
};
return (
<div className="mx-5 my-5 overflow-y-auto">
<form method="POST" onSubmit={handleSubmit(onSubmit)}>
<form
method="POST"
onSubmit={handleSubmit(onSubmit)}
className="w-full lg:w-1/2 lg:ml-4"
>
<Card className="rounded-md p-5 flex flex-col gap-3">
<Controller
control={control}
@ -313,50 +369,71 @@ export default function FormMasterUserEdit() {
{errors.genderType?.message && (
<p className="text-red-400 text-sm">{errors.genderType?.message}</p>
)}
{/* <Controller
<Controller
control={control}
name="birthDate"
name="userLevelType"
render={({ field: { onChange, value } }) => (
<Datepicker
useRange={false}
asSingle={true}
value={value}
displayFormat="DD/MM/YYYY"
onChange={onChange}
popoverDirection="down"
inputClassName="w-full bg-transparent border-1 border-gray-200 px-2 py-[6px] rounded-xl "
/>
<>
<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.birthDate?.startDate?.message && (
{errors.userLevelType?.message && (
<p className="text-red-400 text-sm">
{errors.birthDate?.startDate?.message}
{errors.userLevelType?.message}
</p>
)} */}
{/* <Controller
)}
<Controller
control={control}
name="lastEducation"
name="userRoleType"
render={({ field: { onChange, value } }) => (
<Select
variant="bordered"
labelPlacement="outside"
label="Last Education"
selectedKeys={[value]}
onChange={onChange}
placeholder="Select"
className="max-w-xs"
>
{educationGrade.map((grade) => (
<SelectItem key={grade.value}>{grade.value}</SelectItem>
))}
</Select>
<>
<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.lastEducation?.message && (
{errors.userRoleType?.message && (
<p className="text-red-400 text-sm">
{errors.lastEducation?.message}
{errors.userRoleType?.message}
</p>
)} */}
)}
<Controller
control={control}
name="phoneNumber"
@ -369,7 +446,7 @@ export default function FormMasterUserEdit() {
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
className="w-full z-0"
variant="bordered"
/>
)}
@ -379,30 +456,6 @@ export default function FormMasterUserEdit() {
{errors.phoneNumber?.message}
</p>
)}
{/* <Controller
control={control}
name="workType"
render={({ field: { onChange, value } }) => (
<Select
variant="bordered"
labelPlacement="outside"
label="Working Type"
placeholder="Select"
className="max-w-xs"
selectedKeys={[value]}
onChange={onChange}
>
{workingBackground.map((type) => (
<SelectItem key={type.id} value={type.id}>
{type.value}
</SelectItem>
))}
</Select>
)}
/>
{errors.workType?.message && (
<p className="text-red-400 text-sm">{errors.workType?.message}</p>
)} */}
<div className="flex justify-end gap-3">
<Link href={`/admin/master-user`}>

View File

@ -1,5 +1,5 @@
"use client";
import { error } from "@/config/swal";
import { close, error, loading } from "@/config/swal";
import { createMasterUser } from "@/service/master-user";
import { MasterUser } from "@/types/globals";
import { zodResolver } from "@hookform/resolvers/zod";
@ -16,7 +16,7 @@ import {
} from "@heroui/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import Datepicker from "react-tailwindcss-datepicker";
import Swal from "sweetalert2";
@ -24,6 +24,16 @@ import withReactContent from "sweetalert2-react-content";
import { z } from "zod";
import { EyeFilledIcon, EyeSlashFilledIcon } from "../icons";
import ReactPasswordChecklist from "react-password-checklist";
import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import { getAllUserLevels } from "@/services/user-levels/user-levels-service";
import { listUserRole } from "@/service/master-user-role";
const userSchema = z.object({
id: z.number(),
label: z.string(),
value: z.string(),
});
const masterUserSchema = z.object({
fullname: z.string().min(1, { message: "Required" }),
@ -41,95 +51,24 @@ const masterUserSchema = z.object({
message: "Password harus memiliki minimal satu simbol.",
}),
passwordValidate: z.string().min(1, { message: "Required" }),
email: z.string().min(1, { message: "Required" }),
// identityType: z.string().min(1, { message: "Required" }),
identityNumber: z.string().min(1, { message: "Required" }),
// lastEducation: z.string().min(1, { message: "Required" }),
genderType: z.string().min(1, { message: "Required" }),
phoneNumber: z.string().min(1, { message: "Required" }),
// workType: z.string().min(1, { message: "Required" }),
address: z.string().min(1, { message: "Required" }),
// birthDate: z.object({
// startDate: z.string().min(1, { message: "Required" }),
// endDate: z.string().min(1, { message: "Required" }),
// }),
userLevelType: userSchema,
userRoleType: userSchema,
});
const typeIdentity = [
{
id: 1,
value: "KTP",
},
{
id: 2,
value: "SIM",
},
{
id: 3,
value: "Passport",
},
];
const workingBackground = [
{
id: 1,
value: "Pegawai Negri Sipil",
},
{
id: 2,
value: "Wiraswasta",
},
{
id: 3,
value: "Guru",
},
{
id: 4,
value: "Dokter",
},
];
const educationGrade = [
{
id: 1,
value: "SMA / Sederajat",
},
{
id: 2,
value: "Diploma 1",
},
{
id: 3,
value: "Diploma 2",
},
{
id: 4,
value: "Diploma 3",
},
{
id: 5,
value: "Diploma 4",
},
{
id: 6,
value: "S1",
},
{
id: 7,
value: "S2",
},
{
id: 8,
value: "S3",
},
];
export default function FormMasterUser() {
const router = useRouter();
const animatedComponents = makeAnimated();
const MySwal = withReactContent(Swal);
const [isVisible, setIsVisible] = useState([false, false]);
const [isValidPassword, setIsValidPassword] = useState(false);
const [parentList, setParentList] = useState<any>([]);
const [listRole, setListRole] = useState<any>([]);
const toggleVisibility = (type: number) => {
setIsVisible(
@ -164,8 +103,8 @@ export default function FormMasterUser() {
identityNumber: data.identityNumber,
identityType: "nrp",
phoneNumber: data.phoneNumber,
userLevelId: 2,
userRoleId: 2,
userLevelId: data.userLevelType.id,
userRoleId: data.userRoleType.id,
username: data.username,
};
@ -249,6 +188,48 @@ export default function FormMasterUser() {
setValue("password", generatedPassword);
};
useEffect(() => {
fetchUserLevel();
fetchUserRole();
}, []);
const fetchUserLevel = async () => {
loading();
const res = await getAllUserLevels();
close();
if (res?.data?.data) {
setupParent(res?.data?.data, "level");
}
};
const fetchUserRole = async () => {
loading();
const request = {
limit: 100,
page: 1,
};
const res = await listUserRole(request);
close();
if (res?.data?.data) {
setupParent(res?.data?.data, "role");
}
};
const setupParent = (data: any, type: "level" | "role") => {
const temp = [];
for (const element of data) {
temp.push({
id: element.id,
label: element.name,
value: element.aliasName || element.code,
});
}
if (type === "level") {
setParentList(temp);
} else {
setListRole(temp);
}
};
return (
<div className="mx-5 my-5 overflow-y-auto">
<form method="POST" onSubmit={handleSubmit(onSubmit)}>
@ -333,30 +314,6 @@ export default function FormMasterUser() {
<p className="text-red-400 text-sm">{errors.email?.message}</p>
)}
{/* <Controller
control={control}
name="identityType"
render={({ field: { onChange, value } }) => (
<Select
variant="bordered"
labelPlacement="outside"
label="Identity Type"
placeholder="Select"
className="max-w-xs"
selectedKeys={[value]}
onChange={onChange}
>
{typeIdentity.map((type) => (
<SelectItem key={type.value}>{type.value}</SelectItem>
))}
</Select>
)}
/>
{errors.identityType?.message && (
<p className="text-red-400 text-sm">
{errors.identityType?.message}
</p>
)} */}
<Controller
control={control}
name="identityNumber"
@ -428,50 +385,7 @@ export default function FormMasterUser() {
{errors.genderType?.message && (
<p className="text-red-400 text-sm">{errors.genderType?.message}</p>
)}
{/* <Controller
control={control}
name="birthDate"
render={({ field: { onChange, value } }) => (
<Datepicker
useRange={false}
asSingle={true}
value={value}
displayFormat="DD/MM/YYYY"
onChange={onChange}
popoverDirection="down"
inputClassName="w-full bg-transparent border-1 border-gray-200 px-2 py-[6px] rounded-xl "
/>
)}
/>
{errors.birthDate?.startDate?.message && (
<p className="text-red-400 text-sm">
{errors.birthDate?.startDate?.message}
</p>
)} */}
{/* <Controller
control={control}
name="lastEducation"
render={({ field: { onChange, value } }) => (
<Select
variant="bordered"
labelPlacement="outside"
label="Last Education"
selectedKeys={[value]}
onChange={onChange}
placeholder="Select"
className="max-w-xs"
>
{educationGrade.map((grade) => (
<SelectItem key={grade.value}>{grade.value}</SelectItem>
))}
</Select>
)}
/>
{errors.lastEducation?.message && (
<p className="text-red-400 text-sm">
{errors.lastEducation?.message}
</p>
)} */}
<Controller
control={control}
name="phoneNumber"
@ -494,31 +408,68 @@ export default function FormMasterUser() {
{errors.phoneNumber?.message}
</p>
)}
{/* <Controller
<Controller
control={control}
name="workType"
name="userLevelType"
render={({ field: { onChange, value } }) => (
<Select
variant="bordered"
labelPlacement="outside"
label="Working Type"
placeholder="Select"
className="max-w-xs"
selectedKeys={[value]}
onChange={onChange}
>
{workingBackground.map((type) => (
<SelectItem key={type.id} value={type.id}>
{type.value}
</SelectItem>
))}
</Select>
<>
<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"
onChange={onChange}
closeMenuOnSelect={false}
components={animatedComponents}
isClearable={true}
isSearchable={true}
isMulti={false}
placeholder=""
name="sub-module"
options={parentList}
/>
</>
)}
/>
{errors.workType?.message && (
<p className="text-red-400 text-sm">{errors.workType?.message}</p>
)} */}
{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={{
control: (state: any) =>
"!rounded-lg bg-white !border-1 !border-gray-200 dark:!border-stone-500",
}}
classNamePrefix="select"
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>
)}
<Controller
control={control}
name="password"
@ -531,7 +482,7 @@ export default function FormMasterUser() {
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
className="w-full z-0"
variant="bordered"
endContent={
<button
@ -564,7 +515,7 @@ export default function FormMasterUser() {
value={value}
onChange={onChange}
labelPlacement="outside"
className="w-full"
className="w-full z-0"
variant="bordered"
endContent={
<button

View File

@ -11,6 +11,7 @@ import { useRouter } from "next/navigation";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { saveActivity } from "@/service/activity-log";
import Image from "next/image";
export default function Login() {
const router = useRouter();
@ -159,7 +160,13 @@ export default function Login() {
className="h-screen hidden md:block md:w-3/5"
>
<Link href="/">
<img src="divhumas.png" className="w-[120px]" />
<Image
width={480}
height={480}
alt="icon"
src="/divhumas.png"
className="w-[120px]"
/>
</Link>
</div>
{isResetPassword ? (

View File

@ -524,7 +524,7 @@ export const IdnIcon = ({
fill="white"
/>
</g>
<rect x="0.5" y="0.5" width="31" height="23" rx="5.5" stroke="white" />
<rect x="0.5" y="0.5" width="31" height="23" stroke="white" />
<defs>
<clipPath id="clip0_699_602">
<rect width="32" height="24" rx="6" fill="white" />
@ -1621,6 +1621,26 @@ export const DeleteIcon = ({
/>
</svg>
);
export const BannerIcon = ({
size,
height = 12,
width = 10,
fill = "none",
...props
}: IconSvgProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
height={size || height}
width={size || width}
{...props}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M6 22q-.825 0-1.412-.587T4 20V4q0-.825.588-1.412T6 2h12q.825 0 1.413.588T20 4v16q0 .825-.587 1.413T18 22zm1-4h10l-3.45-4.5l-2.3 3l-1.55-2z"
/>
</svg>
);
export const AccIcon = ({
size,

View File

@ -425,3 +425,23 @@ export const MasterCategoryIcon = ({
/>
</svg>
);
export const AddvertiseIcon = ({
size,
height = 24,
width = 24,
fill = "currentColor",
...props
}: IconSvgProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size || width}
height={size || height}
{...props}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 8q-1.65 0-2.825 1.175T8 12q0 1.125.563 2.075t1.562 1.475q.4.2.563.587t-.013.788q-.175.35-.525.525t-.7 0q-1.575-.75-2.512-2.225T6 12q0-2.5 1.75-4.25T12 6q1.775 0 3.263.938T17.475 9.5q.15.35-.012.7t-.513.5q-.4.175-.8 0t-.6-.575q-.525-1-1.475-1.562T12 8m0-4Q8.65 4 6.325 6.325T4 12q0 3.15 2.075 5.4t5.2 2.55q.425.05.737.375t.288.75t-.313.7t-.712.25q-1.95-.125-3.638-.975t-2.95-2.213t-1.975-3.125T2 12q0-2.075.788-3.9t2.137-3.175T8.1 2.788T12 2q3.925 0 6.838 2.675t3.187 6.6q.05.4-.237.688t-.713.312t-.762-.275t-.388-.725q-.375-3-2.612-5.137T12 4m7.55 17.5l-3.3-3.275l-.75 2.275q-.125.35-.475.338t-.475-.363L12.275 12.9q-.1-.275.125-.5t.5-.125l7.575 2.275q.35.125.363.475t-.338.475l-2.275.75l3.3 3.3q.425.425.425.975t-.425.975t-.987.425t-.988-.425"
/>
</svg>
);

View File

@ -28,22 +28,22 @@ export default function NewsTicker() {
};
const handlePrev = () => {
const newIndex = (currentNewsIndex - 1 + article.length) % article.length;
const newIndex = (currentNewsIndex - 1 + article?.length) % article?.length;
triggerAnimation(newIndex);
};
const handleNext = () => {
const newIndex = (currentNewsIndex + 1) % article.length;
const newIndex = (currentNewsIndex + 1) % article?.length;
triggerAnimation(newIndex);
};
useEffect(() => {
const interval = setInterval(() => {
triggerAnimation((currentNewsIndex + 1) % article.length);
triggerAnimation((currentNewsIndex + 1) % article?.length);
}, 7000);
return () => clearInterval(interval);
}, [article.length]);
}, [article]);
return (
<div className="fixed bottom-0 z-50 flex flex-row h-[60px] gap-3 w-full justify-between dark:bg-stone-800 bg-gray-50">

View File

@ -140,7 +140,7 @@ export default function NavbarHumas(props: { size: string }) {
height={size === "sm" ? "6rem" : "8rem"}
className={`transition-all duration-300 ease-in-out backdrop-opacity-10 ${
size === "sm"
? "lg:hidden !bg-white dark:!bg-black "
? "lg:hidden !bg-slate-50 dark:!bg-black "
: "hidden lg:block"
} ${isScrolled ? "bg-white dark:bg-[#1F1A17] " : "bg-opacity-50"}`}
classNames={{ wrapper: "px-0" }}
@ -1317,7 +1317,7 @@ export default function NavbarHumas(props: { size: string }) {
</div>
<NavbarContent
className={`px-2 lg:hidden transition-all duration-300 ease-in-out backdrop-opacity-10 ${
isScrolled ? "bg-white dark:bg-[#1F1A17] " : "bg-opacity-50"
isScrolled ? "bg-slate-100 dark:bg-[#1F1A17] " : "bg-opacity-50"
}`}
>
<div className="flex justify-between w-full">

View File

@ -13,6 +13,7 @@ import {
FormVerticalIcon,
} from "../../icons";
import {
AddvertiseIcon,
ArticleIcon,
DashboardIcon,
HomeIcon,
@ -56,6 +57,81 @@ interface SidebarProps {
updateSidebarData: (newData: boolean) => void;
}
const sidebarOtherRole = [
{
id: 1,
name: "Dashboard",
moduleId: 652,
moduleName: "Dashboard",
modulePathUrl: "/admin/dashboard",
isGroup: true,
parentId: -1,
icon: "dashboard",
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
{
id: 2,
name: "Dashboard",
moduleId: 652,
moduleName: "Dashboard",
modulePathUrl: "/admin/dashboard",
parentId: -1,
icon: <DashboardIcon />,
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
{
id: 3,
name: "Apps",
moduleId: 652,
moduleName: "Dashboard",
modulePathUrl: "/admin/basic",
isGroup: true,
parentId: -1,
icon: "table",
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
{
id: 4,
name: "Artikel",
moduleId: 652,
moduleName: "Dashboard",
modulePathUrl: "/admin/article",
parentId: -1,
icon: <ArticleIcon size={24} />,
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
{
id: 30,
name: "Kategori",
moduleId: 654,
moduleName: "Master",
modulePathUrl: "/admin/master-category",
parentId: -1,
icon: <MasterCategoryIcon size={22} />,
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
];
const sideBarDummyData = [
{
id: 1,
@ -143,6 +219,20 @@ const sideBarDummyData = [
statusName: "Active",
childModule: null,
},
{
id: 33,
name: "Advertise",
moduleId: 652,
moduleName: "Apps",
modulePathUrl: "/admin/advertise",
parentId: -1,
icon: <AddvertiseIcon size={23} />,
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
// {
// id: 4,
@ -268,6 +358,7 @@ const SidebarMobile: React.FC<SidebarProps> = ({ updateSidebarData }) => {
const token = Cookies.get("access_token");
const username = Cookies.get("username");
const isAuthenticated = Cookies.get("is_authenticated");
const roles = Cookies.get("ulie");
useEffect(() => {
if (!token) {
@ -331,7 +422,7 @@ const SidebarMobile: React.FC<SidebarProps> = ({ updateSidebarData }) => {
</div>
<SidebarMenu>
{sideBarDummyData
{roles?.includes("mabes") || username?.includes("mabes")
? sideBarDummyData?.map((list: any, index: number) =>
list.isGroup ? (
<p
@ -405,7 +496,79 @@ const SidebarMobile: React.FC<SidebarProps> = ({ updateSidebarData }) => {
/>
)
)
: ""}
: sidebarOtherRole?.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 hover:bg-zinc-400 dark:hover:text-zinc-600 flex flex-row gap-2 ${
pathname.includes(list.modulePathUrl)
? "bg-zinc-600 dark:bg-zinc-300 text-zinc-300 dark:text-zinc-500 font-bold"
: "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}
>
<Link key={list.id} href={list.modulePathUrl}>
<div
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>
)
) : (
<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>
</div>
<div

View File

@ -13,6 +13,7 @@ import {
FormVerticalIcon,
} from "../../icons";
import {
AddvertiseIcon,
ArticleIcon,
DashboardIcon,
HomeIcon,
@ -219,6 +220,20 @@ const sideBarDummyData = [
statusName: "Active",
childModule: null,
},
{
id: 33,
name: "Advertise",
moduleId: 652,
moduleName: "Apps",
modulePathUrl: "/admin/advertise",
parentId: -1,
icon: <AddvertiseIcon size={23} />,
position: 1,
statusId: 1,
childMenu: [],
statusName: "Active",
childModule: null,
},
// {
// id: 4,

View File

@ -95,12 +95,14 @@ export default function ListNews() {
key={news?.id}
>
<div className="">
<img
<Image
src={
news.thumbnailUrl == ""
? "/no-image.jpg"
: news.thumbnailUrl
}
width={1920}
height={1080}
alt="thumbnail"
className="rounded-t-md h-[27vh] w-full object-cover"
/>

View File

@ -0,0 +1,613 @@
"use client";
import {
BannerIcon,
CloudUploadIcon,
CreateIconIon,
DeleteIcon,
DotsYIcon,
EyeIconMdi,
SearchIcon,
TimesIcon,
} from "@/components/icons";
import { close, error, loading, success } from "@/config/swal";
import {
deleteArticle,
getArticleByCategory,
getListArticle,
} from "@/service/article";
import { Article } from "@/types/globals";
import { convertDateFormat } from "@/utils/global";
import { Button } from "@heroui/button";
import {
Chip,
ChipProps,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownTrigger,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Pagination,
Select,
SelectItem,
Spinner,
Switch,
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow,
Textarea,
useDisclosure,
} from "@heroui/react";
import Link from "next/link";
import { Fragment, Key, useCallback, useEffect, useState } from "react";
import Cookies from "js-cookie";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Controller, useForm } from "react-hook-form";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { useDropzone } from "react-dropzone";
import Image from "next/image";
const columns = [
{ name: "No", uid: "no" },
{ name: "Judul", uid: "title" },
{ name: "Deskripsi", uid: "descriptions" },
{ name: "Link", uid: "url" },
{ name: "Aksi", uid: "actions" },
];
interface Category {
id: number;
title: string;
}
const createArticleSchema = z.object({
id: z.string().optional(),
title: z.string().min(2, {
message: "Judul harus diisi",
}),
url: z.string().min(2, {
message: "Link harus diisi",
}),
description: z.string().min(2, {
message: "Deskripsi harus diisi",
}),
file: z.string().optional(),
});
export default function AdvertiseTable(props: { triggerRefresh: boolean }) {
const MySwal = withReactContent(Swal);
const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [article, setArticle] = useState<any[]>([]);
const [showData, setShowData] = useState("10");
const [search, setSearch] = useState("");
const [categories, setCategoies] = useState<any>([]);
const [selectedCategories, setSelectedCategories] = useState<any>([]);
const [startDateValue, setStartDateValue] = useState({
startDate: null,
endDate: null,
});
const [isHeader, setIsHeader] = useState(false);
const [files, setFiles] = useState<File[]>([]);
const formOptions = {
resolver: zodResolver(createArticleSchema),
defaultValues: { title: "", description: "", url: "", file: "" },
};
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => {
setFiles(acceptedFiles.map((file) => Object.assign(file)));
},
maxFiles: 1,
accept: {
"image/*": [],
},
});
type UserSettingSchema = z.infer<typeof createArticleSchema>;
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<UserSettingSchema>(formOptions);
useEffect(() => {
initState();
}, [
page,
showData,
startDateValue,
selectedCategories,
props.triggerRefresh,
]);
useEffect(() => {
getCategories();
}, []);
async function getCategories() {
const res = await getArticleByCategory();
const data = res?.data?.data;
setCategoies(data);
}
const handleRemoveFile = (file: File) => {
const uploadedFiles = files;
const filtered = uploadedFiles.filter((i) => i.name !== file.name);
setFiles([...filtered]);
};
async function initState() {
const req = {
limit: showData,
page: page,
search: search,
startDate:
startDateValue.startDate === null ? "" : startDateValue.startDate,
endDate: startDateValue.endDate === null ? "" : startDateValue.endDate,
category: Array.from(selectedCategories).join(","),
sort: "desc",
sortBy: "created_at",
};
const res = await getListArticle(req);
getTableNumber(parseInt(showData), res.data?.data);
setTotalPage(res?.data?.meta?.totalPage);
}
const getTableNumber = (limit: number, data: Article[]) => {
if (data) {
const startIndex = limit * (page - 1);
let iterate = 0;
const newData = data.map((value: any) => {
iterate++;
value.no = startIndex + iterate;
return value;
});
setArticle(newData);
}
};
async function doDelete(id: any) {
// loading();
const resDelete = await deleteArticle(id);
if (resDelete?.error) {
error(resDelete.message);
return false;
}
close();
success("Berhasil Hapus");
initState();
}
const handleDelete = (id: any) => {
MySwal.fire({
title: "Hapus Data",
icon: "warning",
showCancelButton: true,
cancelButtonColor: "#3085d6",
confirmButtonColor: "#d33",
confirmButtonText: "Hapus",
}).then((result) => {
if (result.isConfirmed) {
doDelete(id);
}
});
};
const onSubmit = async (values: z.infer<typeof createArticleSchema>) => {
loading();
const formData = {
title: values.title,
description: values.description,
isHeader: isHeader,
url: values.url,
};
console.log("dataas", formData);
close();
// setRefresh(!refresh);
// MySwal.fire({
// title: "Sukses",
// icon: "success",
// confirmButtonColor: "#3085d6",
// confirmButtonText: "OK",
// }).then((result) => {
// if (result.isConfirmed) {
// }
// });
};
const openModal = async (id: number) => {
// const res = await getCategoryById(Number(id));
// const data = res?.data?.data;
// setValue("id", String(data?.id));
// setValue("title", data?.title);
// setValue("description", data?.description);
// setValue("url", data?.url);
// setValue("file", data?.thumbnailUrl);
onOpen();
};
const renderCell = useCallback(
(advertise: any, columnKey: Key) => {
const cellValue = advertise[columnKey as keyof any];
switch (columnKey) {
case "url":
return (
<Link
href={`https://www.google.com/`}
target="_blank"
className="text-primary hover:underline"
>
https://www.google.com/
</Link>
);
case "actions":
return (
<div className="relative flex justify-star items-center gap-2">
<Dropdown className="lg:min-w-[150px] bg-black text-white shadow border ">
<DropdownTrigger>
<Button isIconOnly size="lg" variant="light">
<DotsYIcon className="text-default-300" />
</Button>
</DropdownTrigger>
<DropdownMenu>
{/* <DropdownItem key="detail">
<Link href={`/admin/advertise/detail/${article.id}`}>
<EyeIconMdi className="inline mr-2 mb-1" />
Detail
</Link>
</DropdownItem> */}
<DropdownItem
key="edit"
onPress={() => openModal(advertise.id)}
>
<CreateIconIon className="inline mr-2 mb-1" />
Edit
</DropdownItem>
<DropdownItem
key="delete"
// onPress={() => handleDelete(article.id)}
>
<DeleteIcon
color="red"
size={18}
className="inline ml-1 mr-2 mb-1"
/>
Delete
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
);
default:
return cellValue;
}
},
[article]
);
let typingTimer: NodeJS.Timeout;
const doneTypingInterval = 1500;
const handleKeyUp = () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
};
const handleKeyDown = () => {
clearTimeout(typingTimer);
};
async function doneTyping() {
initState();
}
return (
<>
<div className="py-3">
<div className="flex flex-col items-start rounded-2xl gap-3">
<div className="flex flex-col md:flex-row gap-3 w-full">
<div className="flex flex-col gap-1 w-full lg:w-1/3">
<p className="font-semibold text-sm">Pencarian</p>
<Input
aria-label="Search"
classNames={{
inputWrapper: "bg-default-100",
input: "text-sm",
}}
labelPlacement="outside"
startContent={
<SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" />
}
type="text"
onChange={(e) => setSearch(e.target.value)}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
/>
</div>
<div className="flex flex-col gap-1 w-full lg:w-[72px]">
<p className="font-semibold text-sm">Data</p>
<Select
label=""
variant="bordered"
labelPlacement="outside"
placeholder="Select"
selectedKeys={[showData]}
className="w-full"
classNames={{ trigger: "border-1" }}
onChange={(e) =>
e.target.value === "" ? "" : setShowData(e.target.value)
}
>
<SelectItem key="5" value="5">
5
</SelectItem>
<SelectItem key="10" value="10">
10
</SelectItem>
</Select>
</div>
</div>
<Table
aria-label="micro issue table"
className="rounded-3xl"
classNames={{
th: "bg-white dark:bg-black text-black dark:text-white border-b-1 text-md",
base: "bg-white dark:bg-black border",
wrapper:
"min-h-[50px] bg-transpararent text-black dark:text-white ",
}}
>
<TableHeader columns={columns}>
{(column) => (
<TableColumn key={column.uid}>{column.name}</TableColumn>
)}
</TableHeader>
<TableBody
items={article}
emptyContent={"No data to display."}
loadingContent={<Spinner label="Loading..." />}
>
{(item: any) => (
<TableRow key={item.id}>
{(columnKey) => (
<TableCell>{renderCell(item, columnKey)}</TableCell>
)}
</TableRow>
)}
</TableBody>
</Table>
<div className="my-2 w-full flex justify-center">
<Pagination
isCompact
showControls
showShadow
color="primary"
classNames={{
base: "bg-transparent",
wrapper: "bg-transparent",
}}
page={page}
total={totalPage}
onChange={(page) => setPage(page)}
/>
</div>
</div>
</div>
<Modal isOpen={isOpen} onOpenChange={onOpenChange} size="3xl">
<ModalContent>
{() => (
<>
<ModalHeader className="flex flex-col gap-1">
Advertise
</ModalHeader>
<ModalBody>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3"
>
<div className="flex flex-col gap-1">
<p className="text-sm">Judul</p>
<Controller
control={control}
name="title"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="title"
placeholder=""
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?.title && (
<p className="text-red-400 text-sm">
{errors.title?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Deskripsi</p>
<Controller
control={control}
name="description"
render={({ field: { onChange, value } }) => (
<Textarea
type="text"
id="description"
placeholder=""
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?.description && (
<p className="text-red-400 text-sm">
{errors.description?.message}
</p>
)}
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Link</p>
<Controller
control={control}
name="url"
render={({ field: { onChange, value } }) => (
<Input
type="text"
id="url"
placeholder=""
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?.url && (
<p className="text-red-400 text-sm">
{errors.url?.message}
</p>
)}
</div>
<p className="text-sm mt-3">Header</p>
<Switch
size="sm"
isSelected={isHeader}
onValueChange={setIsHeader}
>
<p className="text-sm"> {isHeader ? "Ya" : "Tidak"}</p>
</Switch>
<Controller
control={control}
name="file"
render={({ field: { onChange, value } }) => (
<div className="flex flex-col gap-1">
<p className="text-sm mt-3">Thumbnail</p>
{files.length < 1 && value === "" && (
<Fragment>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className=" w-full text-center border-dashed border border-default-200 dark:border-default-300 rounded-md py-[52px] flex items-center flex-col">
<CloudUploadIcon />
<h4 className=" text-2xl font-medium mb-1 mt-3 text-card-foreground/80">
Tarik file disini atau klik untuk upload.
</h4>
<div className=" text-xs text-muted-foreground">
( Upload file dengan format .jpg, .jpeg, atau
.png. Ukuran maksimal 100mb.)
</div>
</div>
</div>
</Fragment>
)}
{value !== "" && (
<div className="flex flex-row gap-2">
<Image
src={String(value)}
className="w-[30%]"
alt="thumbnail"
width={480}
height={480}
/>
<Button
onPress={() => setValue("file", "")}
variant="bordered"
size="sm"
className="cursor-pointer text-black border-none"
>
<TimesIcon />
</Button>
</div>
)}
{files.length > 0 && (
<div className="flex flex-row gap-2">
<img
src={URL.createObjectURL(files[0])}
className="w-[30%]"
alt="thumbnail"
/>
<Button
className=" border-none rounded-full"
variant="bordered"
onClick={() => handleRemoveFile(files[0])}
>
<TimesIcon />
</Button>
</div>
)}
</div>
)}
/>
<ModalFooter className="self-end grow items-end">
<Button
color="primary"
type="submit"
isDisabled={files.length < 1}
>
Simpan
</Button>
<Button color="danger" variant="light" onPress={onClose}>
Tutup
</Button>
</ModalFooter>
</form>
</ModalBody>
</>
)}
</ModalContent>
</Modal>
</>
);
}

View File

@ -1,5 +1,6 @@
"use client";
import {
BannerIcon,
CreateIconIon,
DeleteIcon,
DotsYIcon,
@ -39,14 +40,16 @@ import { Key, useCallback, useEffect, useState } from "react";
import Datepicker from "react-tailwindcss-datepicker";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import Cookies from "js-cookie";
const columns = [
{ name: "No", uid: "no" },
{ name: "Judul", uid: "title" },
{ name: "Banner", uid: "isBanner" },
{ name: "Kategori", uid: "category" },
{ name: "Tanggal Unggah", uid: "createdAt" },
{ name: "Kreator", uid: "createdByName" },
// { name: "Status", uid: "isPublish" },
{ name: "Status", uid: "isPublish" },
{ name: "Aksi", uid: "actions" },
];
@ -63,6 +66,8 @@ type ArticleData = Article & {
export default function ArticleTable() {
const MySwal = withReactContent(Swal);
const username = Cookies.get("username");
const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1);
const [article, setArticle] = useState<any[]>([]);
@ -166,6 +171,8 @@ export default function ArticleTable() {
// </Chip>
<p>{article.isPublish ? "Publish" : "Draft"}</p>
);
case "isBanner":
return <p>{article.isBanner ? "Ya" : "Tidak"}</p>;
case "createdAt":
return <p>{convertDateFormat(article.createdAt)}</p>;
case "category":
@ -198,15 +205,28 @@ export default function ArticleTable() {
Edit
</Link>
</DropdownItem>
<DropdownItem
key="setBanner"
// onPress={() => handleDelete(article.id)}
className={username === "admin-mabes" ? "" : "hidden"}
>
{username === "admin-mabes" && (
<>
<BannerIcon size={24} className="inline mr-2 mb-1" />
{article?.isBanner
? "Hapus dari Banner"
: "Jadikan Banner"}
</>
)}
</DropdownItem>
<DropdownItem
key="delete"
onClick={() => handleDelete(article.id)}
onPress={() => handleDelete(article.id)}
>
<DeleteIcon
color="red"
width={20}
height={16}
className="inline mr-2 mb-1"
size={18}
className="inline ml-1 mr-2 mb-1"
/>
Delete
</DropdownItem>

View File

@ -131,6 +131,9 @@ export default function CategoriesTable(props: { triggerRefresh: boolean }) {
setFiles(acceptedFiles.map((file) => Object.assign(file)));
},
maxFiles: 1,
accept: {
"image/*": [],
},
});
type UserSettingSchema = z.infer<typeof createArticleSchema>;
const {
@ -284,14 +287,14 @@ export default function CategoriesTable(props: { triggerRefresh: boolean }) {
<DropdownMenu>
<DropdownItem
key="Detail"
onClick={() => openModal(category.id, true)}
onPress={() => openModal(category.id, true)}
>
<EyeIconMdi className="inline mr-2 mb-1" />
Detail
</DropdownItem>
<DropdownItem
key="Edit"
onClick={() => openModal(category.id, false)}
onPress={() => openModal(category.id, false)}
>
<CreateIconIon className="inline mr-2 mb-1" />
Edit

View File

@ -313,16 +313,16 @@ export const siteConfig = {
},
],
},
{
key: "dashboard",
label: "Dashboard",
href: "/admin",
},
{
key: "login",
label: "Login",
href: "/auth",
},
// {
// key: "dashboard",
// label: "Dashboard",
// href: "/admin",
// },
// {
// key: "login",
// label: "Login",
// href: "/auth",
// },
],
links: {

View File

@ -60,6 +60,15 @@ export async function createArticle(data: any) {
return await httpPost(pathUrl, headers, data);
}
export async function createArticleSchedule(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
const pathUrl = `/articles/publish-scheduling?id=${data.id}&date=${data.date}`;
return await httpPost(pathUrl, headers, data);
}
export async function updateArticle(id: string, data: any) {
const headers = {
"content-type": "application/json",

View File

@ -31,7 +31,7 @@ export async function getDetailMasterUsers(id: string) {
return await httpGet(`/users/detail/${id}`, headers);
}
export async function editMasterUsers(data: any) {
export async function editMasterUsers(data: any, id: string) {
const headers = {
"content-type": "application/json",
};

View File

@ -5,12 +5,12 @@ import {
httpPut,
} from "@/service/http-config/axios-base-service";
export async function getAllUserLevels(data: any) {
export async function getAllUserLevels(data?: any) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`user-levels?limit=${data.limit || ""}&levelNumber=${
`user-levels?limit=${data?.limit || ""}&levelNumber=${
data?.levelNumber || ""
}`,
headers