feat:detail feedback, onoff userLevel
This commit is contained in:
parent
78be7bc0e1
commit
3817131f52
|
|
@ -41,7 +41,7 @@ const createArticleSchema = z.object({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function BasicPage() {
|
export default function AdvertisePage() {
|
||||||
const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
|
const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
|
||||||
const MySwal = withReactContent(Swal);
|
const MySwal = withReactContent(Swal);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
"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";
|
||||||
|
import CommentTable from "@/components/table/comment/comment-table";
|
||||||
|
|
||||||
|
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 AdvertisePage() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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">
|
||||||
|
<CommentTable triggerRefresh={refresh} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import SuggestionsTable from "@/components/table/suggestions/suggestions-table";
|
||||||
|
|
||||||
|
export default function SuggestionsPage() {
|
||||||
|
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">
|
||||||
|
<SuggestionsTable />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -7,12 +7,6 @@ import React from "react";
|
||||||
|
|
||||||
const AdminMasterUserLevel = () => {
|
const AdminMasterUserLevel = () => {
|
||||||
return (
|
return (
|
||||||
// <div>
|
|
||||||
// <MasterUserLevelTable />
|
|
||||||
// </div>
|
|
||||||
// <div>
|
|
||||||
// <MappingUserLevel />
|
|
||||||
// </div>
|
|
||||||
<div className="overflow-x-hidden overflow-y-scroll">
|
<div className="overflow-x-hidden overflow-y-scroll">
|
||||||
<div className="px-2 md:px-4 md:py-4 w-full">
|
<div className="px-2 md:px-4 md:py-4 w-full">
|
||||||
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
|
<div className="bg-white shadow-lg dark:bg-[#18181b] rounded-xl py-3">
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { useRouter } from "next/navigation";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { saveActivity } from "@/service/activity-log";
|
import { saveActivity } from "@/service/activity-log";
|
||||||
import Image from "next/image";
|
import { Image } from "@heroui/react";
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -165,7 +165,7 @@ export default function Login() {
|
||||||
height={480}
|
height={480}
|
||||||
alt="icon"
|
alt="icon"
|
||||||
src="/divhumas.png"
|
src="/divhumas.png"
|
||||||
className="w-[120px]"
|
className="w-[240px] !h-[240px]"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -445,3 +445,43 @@ export const AddvertiseIcon = ({
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
export const SuggestionsIcon = ({
|
||||||
|
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="M17.175 14H7.5q-1.875 0-3.187-1.312T3 9.5t1.313-3.187T7.5 5q.425 0 .713.288T8.5 6t-.288.713T7.5 7q-1.05 0-1.775.725T5 9.5t.725 1.775T7.5 12h9.675L14.3 9.1q-.275-.275-.288-.687T14.3 7.7q.275-.275.7-.275t.7.275l4.6 4.6q.3.3.3.7t-.3.7l-4.6 4.6q-.3.3-.7.288t-.7-.313q-.275-.3-.288-.7t.288-.7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
export const CommentIcon = ({
|
||||||
|
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="M6.5 13.5h11v-1h-11zm0-3h11v-1h-11zm0-3h11v-1h-11zM4.616 17q-.691 0-1.153-.462T3 15.385V4.615q0-.69.463-1.153T4.615 3h14.77q.69 0 1.152.462T21 4.615v15.462L17.923 17z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import {
|
||||||
Card,
|
Card,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
Image,
|
||||||
ScrollShadow,
|
ScrollShadow,
|
||||||
} from "@heroui/react";
|
} from "@heroui/react";
|
||||||
import Image from "next/image";
|
|
||||||
import { ChevronLeftIcon, ChevronRightIcon, EyeIcon } from "../icons";
|
import { ChevronLeftIcon, ChevronRightIcon, EyeIcon } from "../icons";
|
||||||
import { Swiper, SwiperSlide, useSwiper } from "swiper/react";
|
import { Swiper, SwiperSlide, useSwiper } from "swiper/react";
|
||||||
import "swiper/css";
|
import "swiper/css";
|
||||||
|
|
@ -101,8 +101,6 @@ export default function HeaderNews() {
|
||||||
className="border-none rounded-xl shadow-none"
|
className="border-none rounded-xl shadow-none"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
width={720}
|
|
||||||
height={480}
|
|
||||||
alt="headernews"
|
alt="headernews"
|
||||||
src={
|
src={
|
||||||
newsItem?.thumbnailUrl == ""
|
newsItem?.thumbnailUrl == ""
|
||||||
|
|
@ -235,8 +233,6 @@ export default function HeaderNews() {
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
alt="headernews"
|
alt="headernews"
|
||||||
width={1440}
|
|
||||||
height={1080}
|
|
||||||
src={
|
src={
|
||||||
newsItem?.thumbnailUrl == ""
|
newsItem?.thumbnailUrl == ""
|
||||||
? "/no-image.jpg"
|
? "/no-image.jpg"
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,6 @@ export default function PolriApps(props: {
|
||||||
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||||
const [scrollBehavior, setScrollBehavior] =
|
const [scrollBehavior, setScrollBehavior] =
|
||||||
React.useState<ModalProps["scrollBehavior"]>("inside");
|
React.useState<ModalProps["scrollBehavior"]>("inside");
|
||||||
const [modalPlacement, setModalPlacement] = React.useState<any>("auto");
|
|
||||||
const t = useTranslations("Landing");
|
const t = useTranslations("Landing");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -138,24 +137,6 @@ export default function PolriApps(props: {
|
||||||
}
|
}
|
||||||
}, [props.opened]);
|
}, [props.opened]);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// function updateLimitedData() {
|
|
||||||
// if (window.matchMedia("(max-width: 767px)").matches) {
|
|
||||||
// setLimitedData(list.slice(0, 2));
|
|
||||||
// } else if (window.matchMedia("(min-width: 768px) and (max-width: 1023px)").matches) {
|
|
||||||
// setLimitedData(list.slice(0, 3));
|
|
||||||
// } else {
|
|
||||||
// setLimitedData(list.slice(0, 5));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// updateLimitedData();
|
|
||||||
// window.addEventListener('resize', updateLimitedData);
|
|
||||||
// return () => {
|
|
||||||
// window.removeEventListener('resize', updateLimitedData);
|
|
||||||
// };
|
|
||||||
// }, [list]);
|
|
||||||
|
|
||||||
const changeNameToSlug = (name: string) => {
|
const changeNameToSlug = (name: string) => {
|
||||||
const cleaned = name.replace("Polda ", "").trim().toLowerCase();
|
const cleaned = name.replace("Polda ", "").trim().toLowerCase();
|
||||||
const slug = cleaned.replace(/\s+/g, "-");
|
const slug = cleaned.replace(/\s+/g, "-");
|
||||||
|
|
@ -164,16 +145,6 @@ export default function PolriApps(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <div className="flex justify-center w-full items-center">
|
|
||||||
<Button
|
|
||||||
onPress={onOpen}
|
|
||||||
className="border-red-700 text-white w-fit"
|
|
||||||
size="sm"
|
|
||||||
variant="bordered"
|
|
||||||
>
|
|
||||||
{t("lebihBanyak")}
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onOpenChange={() => {
|
onOpenChange={() => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import Image from "next/image";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
|
|
@ -35,6 +34,7 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
|
Image,
|
||||||
} from "@heroui/react";
|
} from "@heroui/react";
|
||||||
import storedLanguage from "@/store/language-store";
|
import storedLanguage from "@/store/language-store";
|
||||||
import { ThemeSwitch } from "../theme-switch";
|
import { ThemeSwitch } from "../theme-switch";
|
||||||
|
|
@ -107,8 +107,6 @@ export default function BannerHumasNew() {
|
||||||
>
|
>
|
||||||
{withImage && (
|
{withImage && (
|
||||||
<Image
|
<Image
|
||||||
width={720}
|
|
||||||
height={720}
|
|
||||||
alt="logo"
|
alt="logo"
|
||||||
src={menu.img}
|
src={menu.img}
|
||||||
className="w-[45px] h-[45px]"
|
className="w-[45px] h-[45px]"
|
||||||
|
|
@ -167,8 +165,6 @@ export default function BannerHumasNew() {
|
||||||
>
|
>
|
||||||
{withImage && (
|
{withImage && (
|
||||||
<Image
|
<Image
|
||||||
width={720}
|
|
||||||
height={720}
|
|
||||||
alt="logo"
|
alt="logo"
|
||||||
src={menu.img}
|
src={menu.img}
|
||||||
className="w-[35px] h-[35px]"
|
className="w-[35px] h-[35px]"
|
||||||
|
|
@ -201,8 +197,6 @@ export default function BannerHumasNew() {
|
||||||
<Link href="" key={index} className="w-full shrink-0">
|
<Link href="" key={index} className="w-full shrink-0">
|
||||||
<Image
|
<Image
|
||||||
src={img}
|
src={img}
|
||||||
width={1440}
|
|
||||||
height={1080}
|
|
||||||
alt={`humasbanner-${index}`}
|
alt={`humasbanner-${index}`}
|
||||||
className="w-full h-full object-cover object-center opacity-[25] dark:opacity-70"
|
className="w-full h-full object-cover object-center opacity-[25] dark:opacity-70"
|
||||||
/>
|
/>
|
||||||
|
|
@ -221,13 +215,7 @@ export default function BannerHumasNew() {
|
||||||
OBYEKTIF - DIPERCAYA - PARTISIPASI
|
OBYEKTIF - DIPERCAYA - PARTISIPASI
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Image
|
<Image src="/divhumas.png" alt="logo-humas" className="w-[200px]" />
|
||||||
src="/divhumas.png"
|
|
||||||
width={720}
|
|
||||||
height={720}
|
|
||||||
alt="logo-humas"
|
|
||||||
className="w-[200px]"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col lg:flex-row lg:justify-between gap-4 mt-10 lg:mt-24">
|
<div className="flex flex-col lg:flex-row lg:justify-between gap-4 mt-10 lg:mt-24">
|
||||||
<div className="flex flex-row gap-6">
|
<div className="flex flex-row gap-6">
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
"use client";
|
"use client";
|
||||||
import Image from "next/image";
|
|
||||||
import RegionalNews from "./RegionalNews";
|
import RegionalNews from "./RegionalNews";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import CategorySatker from "./CategorySatker";
|
import CategorySatker from "./CategorySatker";
|
||||||
import PolriApps from "./PolriApps";
|
import PolriApps from "./PolriApps";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import SuggestionsModal from "./suggestions";
|
import SuggestionsModal from "./suggestions";
|
||||||
|
import { Image } from "@heroui/react";
|
||||||
|
|
||||||
export default function DigitalServices() {
|
export default function DigitalServices() {
|
||||||
const [isPoldaOpen, setIsPoldaOpen] = useState(false);
|
const [isPoldaOpen, setIsPoldaOpen] = useState(false);
|
||||||
|
|
@ -27,8 +27,6 @@ export default function DigitalServices() {
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/indonesia-map.png"
|
src="/indonesia-map.png"
|
||||||
width={480}
|
|
||||||
height={480}
|
|
||||||
alt="indonesia"
|
alt="indonesia"
|
||||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||||
/>
|
/>
|
||||||
|
|
@ -45,8 +43,6 @@ export default function DigitalServices() {
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/satker.png"
|
src="/satker.png"
|
||||||
width={480}
|
|
||||||
height={480}
|
|
||||||
alt="satker"
|
alt="satker"
|
||||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||||
/>
|
/>
|
||||||
|
|
@ -63,8 +59,6 @@ export default function DigitalServices() {
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/presisi.png"
|
src="/presisi.png"
|
||||||
width={480}
|
|
||||||
height={480}
|
|
||||||
alt="presisi"
|
alt="presisi"
|
||||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||||
/>
|
/>
|
||||||
|
|
@ -81,8 +75,6 @@ export default function DigitalServices() {
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/kritik-saran.png"
|
src="/kritik-saran.png"
|
||||||
width={480}
|
|
||||||
height={480}
|
|
||||||
alt="kritik-saran"
|
alt="kritik-saran"
|
||||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||||
/>
|
/>
|
||||||
|
|
@ -99,8 +91,6 @@ export default function DigitalServices() {
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/survey.png"
|
src="/survey.png"
|
||||||
width={480}
|
|
||||||
height={480}
|
|
||||||
alt="survey"
|
alt="survey"
|
||||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import OTPInput from "react-otp-input";
|
||||||
import { otpRequest, otpValidation } from "@/service/master-user";
|
import { otpRequest, otpValidation } from "@/service/master-user";
|
||||||
import Swal from "sweetalert2";
|
import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
|
import { createFeedback } from "@/service/feedbacks";
|
||||||
|
|
||||||
const createArticleSchema = z.object({
|
const createArticleSchema = z.object({
|
||||||
email: z.string().min(2, {
|
email: z.string().min(2, {
|
||||||
|
|
@ -71,26 +72,32 @@ export default function SuggestionsModal(props: {
|
||||||
const onSubmit = async (values: z.infer<typeof createArticleSchema>) => {
|
const onSubmit = async (values: z.infer<typeof createArticleSchema>) => {
|
||||||
if (!needOtp) {
|
if (!needOtp) {
|
||||||
loading();
|
loading();
|
||||||
// const res = await otpRequest(values.email, values?.name);
|
const res = await otpRequest(values.email, values?.name);
|
||||||
// if (res?.error) {
|
if (res?.error) {
|
||||||
// error(res.message);
|
error(res.message);
|
||||||
// return false;
|
return false;
|
||||||
// }
|
}
|
||||||
close();
|
close();
|
||||||
setNeedOtp(true);
|
setNeedOtp(true);
|
||||||
} else {
|
} else {
|
||||||
// const validation = await otpValidation(values.email, otpValue);
|
const validation = await otpValidation(values.email, otpValue);
|
||||||
// if (validation?.error) {
|
if (validation?.error) {
|
||||||
// error("OTP Tidak Sesuai");
|
error("OTP Tidak Sesuai");
|
||||||
// return false;
|
return false;
|
||||||
// }
|
}
|
||||||
|
|
||||||
const req = {
|
const req = {
|
||||||
name: values.name,
|
commentFromName: values.name,
|
||||||
description: values.description,
|
message: values.description,
|
||||||
email: values.email,
|
commentFromEmail: values.email,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const res = await createFeedback(req);
|
||||||
|
if (res?.error) {
|
||||||
|
error(res?.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
title: "Berhasil Kirim",
|
title: "Berhasil Kirim",
|
||||||
text: "",
|
text: "",
|
||||||
|
|
@ -108,17 +115,6 @@ export default function SuggestionsModal(props: {
|
||||||
props.modalStatus(!props.opened);
|
props.modalStatus(!props.opened);
|
||||||
onOpenChange();
|
onOpenChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
// setRefresh(!refresh);
|
|
||||||
// MySwal.fire({
|
|
||||||
// title: "Sukses",
|
|
||||||
// icon: "success",
|
|
||||||
// confirmButtonColor: "#3085d6",
|
|
||||||
// confirmButtonText: "OK",
|
|
||||||
// }).then((result) => {
|
|
||||||
// if (result.isConfirmed) {
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import {
|
import {
|
||||||
AddvertiseIcon,
|
AddvertiseIcon,
|
||||||
ArticleIcon,
|
ArticleIcon,
|
||||||
|
CommentIcon,
|
||||||
DashboardIcon,
|
DashboardIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
InfoCircleIcon,
|
InfoCircleIcon,
|
||||||
|
|
@ -25,6 +26,7 @@ import {
|
||||||
MasterUsersIcon,
|
MasterUsersIcon,
|
||||||
MinusCircleIcon,
|
MinusCircleIcon,
|
||||||
StaticPageIcon,
|
StaticPageIcon,
|
||||||
|
SuggestionsIcon,
|
||||||
TableIcon,
|
TableIcon,
|
||||||
} from "../../icons/sidebar-icon";
|
} from "../../icons/sidebar-icon";
|
||||||
import { ThemeSwitch } from "../../theme-switch";
|
import { ThemeSwitch } from "../../theme-switch";
|
||||||
|
|
@ -233,7 +235,34 @@ const sideBarDummyData = [
|
||||||
statusName: "Active",
|
statusName: "Active",
|
||||||
childModule: null,
|
childModule: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 34,
|
||||||
|
name: "Kritik & Saran",
|
||||||
|
moduleId: 652,
|
||||||
|
moduleName: "Apps",
|
||||||
|
modulePathUrl: "/admin/feedbacks",
|
||||||
|
parentId: -1,
|
||||||
|
icon: <SuggestionsIcon size={23} />,
|
||||||
|
position: 1,
|
||||||
|
statusId: 1,
|
||||||
|
childMenu: [],
|
||||||
|
statusName: "Active",
|
||||||
|
childModule: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 35,
|
||||||
|
name: "Komentar",
|
||||||
|
moduleId: 652,
|
||||||
|
moduleName: "Apps",
|
||||||
|
modulePathUrl: "/admin/comment",
|
||||||
|
parentId: -1,
|
||||||
|
icon: <CommentIcon size={23} />,
|
||||||
|
position: 1,
|
||||||
|
statusId: 1,
|
||||||
|
childMenu: [],
|
||||||
|
statusName: "Active",
|
||||||
|
childModule: null,
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// id: 4,
|
// id: 4,
|
||||||
// name: "E-Magazine",
|
// name: "E-Magazine",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import {
|
import {
|
||||||
AddvertiseIcon,
|
AddvertiseIcon,
|
||||||
ArticleIcon,
|
ArticleIcon,
|
||||||
|
CommentIcon,
|
||||||
DashboardIcon,
|
DashboardIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
InfoCircleIcon,
|
InfoCircleIcon,
|
||||||
|
|
@ -25,6 +26,7 @@ import {
|
||||||
MasterUsersIcon,
|
MasterUsersIcon,
|
||||||
MinusCircleIcon,
|
MinusCircleIcon,
|
||||||
StaticPageIcon,
|
StaticPageIcon,
|
||||||
|
SuggestionsIcon,
|
||||||
TableIcon,
|
TableIcon,
|
||||||
} from "../../icons/sidebar-icon";
|
} from "../../icons/sidebar-icon";
|
||||||
import { ThemeSwitch } from "../../theme-switch";
|
import { ThemeSwitch } from "../../theme-switch";
|
||||||
|
|
@ -234,6 +236,34 @@ const sideBarDummyData = [
|
||||||
statusName: "Active",
|
statusName: "Active",
|
||||||
childModule: null,
|
childModule: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 34,
|
||||||
|
name: "Kritik & Saran",
|
||||||
|
moduleId: 652,
|
||||||
|
moduleName: "Apps",
|
||||||
|
modulePathUrl: "/admin/feedbacks",
|
||||||
|
parentId: -1,
|
||||||
|
icon: <SuggestionsIcon size={23} />,
|
||||||
|
position: 1,
|
||||||
|
statusId: 1,
|
||||||
|
childMenu: [],
|
||||||
|
statusName: "Active",
|
||||||
|
childModule: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 35,
|
||||||
|
name: "Komentar",
|
||||||
|
moduleId: 652,
|
||||||
|
moduleName: "Apps",
|
||||||
|
modulePathUrl: "/admin/comment",
|
||||||
|
parentId: -1,
|
||||||
|
icon: <CommentIcon size={23} />,
|
||||||
|
position: 1,
|
||||||
|
statusId: 1,
|
||||||
|
childMenu: [],
|
||||||
|
statusName: "Active",
|
||||||
|
childModule: null,
|
||||||
|
},
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// id: 4,
|
// id: 4,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
"use client";
|
||||||
|
import React, { Component, useEffect, useState } from "react";
|
||||||
|
import ReactApexChart from "react-apexcharts";
|
||||||
|
import dummyData from "../../../../const/dummy.json";
|
||||||
|
import { getStatisticMonthly } from "@/service/article";
|
||||||
|
|
||||||
|
type WeekData = {
|
||||||
|
week: number;
|
||||||
|
days: number[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RemainingDays = {
|
||||||
|
days: number[];
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function processMonthlyData(count: number[]): {
|
||||||
|
weeks: WeekData[];
|
||||||
|
remaining_days: RemainingDays;
|
||||||
|
} {
|
||||||
|
const weeks: WeekData[] = [];
|
||||||
|
let weekIndex = 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < count.length; i += 7) {
|
||||||
|
const weekData = count.slice(i, i + 7);
|
||||||
|
weeks.push({
|
||||||
|
week: weekIndex,
|
||||||
|
days: weekData,
|
||||||
|
total: weekData.reduce((sum, day) => sum + day, 0),
|
||||||
|
});
|
||||||
|
weekIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingDays: RemainingDays = {
|
||||||
|
days: count.length % 7 === 0 ? [] : count.slice(-count.length % 7),
|
||||||
|
total: count.slice(-count.length % 7).reduce((sum, day) => sum + day, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
weeks,
|
||||||
|
remaining_days: remainingDays,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const SuggestionsChart = (props: { type: string; date: string }) => {
|
||||||
|
const { date, type } = props;
|
||||||
|
const [categories, setCategories] = useState<string[]>([]);
|
||||||
|
const [seriesSuggestions, setSeriesSuggestions] = useState<number[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initFetch();
|
||||||
|
}, [date, type]);
|
||||||
|
|
||||||
|
function processYearlyData(data: any) {
|
||||||
|
const months = [
|
||||||
|
"Januari",
|
||||||
|
"Februari",
|
||||||
|
"Maret",
|
||||||
|
"April",
|
||||||
|
"Mei",
|
||||||
|
"Juni",
|
||||||
|
"Juli",
|
||||||
|
"Agustus",
|
||||||
|
"September",
|
||||||
|
"Oktober",
|
||||||
|
"November",
|
||||||
|
"Desember",
|
||||||
|
];
|
||||||
|
const category = [];
|
||||||
|
const temp = [];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const total = data[i].suggestions.reduce(
|
||||||
|
(sum: number, list: number) => sum + list,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
temp.push(total);
|
||||||
|
category.push(months[data[i].month - 1]);
|
||||||
|
}
|
||||||
|
return { categories: category, series: temp };
|
||||||
|
}
|
||||||
|
|
||||||
|
const initFetch = async () => {
|
||||||
|
const splitDate = date.split(" ");
|
||||||
|
// const res = await getStatisticMonthly(splitDate[1]);
|
||||||
|
// const data = res?.data?.data;
|
||||||
|
const data = dummyData.data;
|
||||||
|
if (type === "monthly") {
|
||||||
|
const getDatas = data?.filter(
|
||||||
|
(a: any) => a.year === Number(splitDate[1])
|
||||||
|
);
|
||||||
|
if (getDatas) {
|
||||||
|
const temp = processYearlyData(getDatas);
|
||||||
|
console.log("temp", temp);
|
||||||
|
setSeriesSuggestions(temp.series);
|
||||||
|
setCategories(temp.categories);
|
||||||
|
} else {
|
||||||
|
setSeriesSuggestions([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const getDatas = data?.find(
|
||||||
|
(a: any) =>
|
||||||
|
a.month == Number(splitDate[0]) && a.year === Number(splitDate[1])
|
||||||
|
);
|
||||||
|
if (getDatas) {
|
||||||
|
const temp = processMonthlyData(getDatas?.suggestions);
|
||||||
|
if (type == "weekly") {
|
||||||
|
setSeriesSuggestions(
|
||||||
|
temp.weeks.map((list) => {
|
||||||
|
return list.total;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setSeriesSuggestions(getDatas.suggestions);
|
||||||
|
}
|
||||||
|
if (type === "weekly") {
|
||||||
|
const category = [];
|
||||||
|
for (let i = 1; i <= temp.weeks.length; i++) {
|
||||||
|
category.push(`Minggu ke-${i}`);
|
||||||
|
}
|
||||||
|
setCategories(category);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSeriesSuggestions([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full">
|
||||||
|
<div id="chart" className="h-full">
|
||||||
|
<ReactApexChart
|
||||||
|
options={{
|
||||||
|
chart: {
|
||||||
|
height: "100%",
|
||||||
|
type: "area",
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: type == "daily" ? [] : categories,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
series={[
|
||||||
|
{
|
||||||
|
name: "Kritik & Saran",
|
||||||
|
data: seriesSuggestions,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
type="area"
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="html-dist"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SuggestionsChart;
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
|
Image,
|
||||||
Pagination,
|
Pagination,
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
|
|
@ -138,7 +139,9 @@ export default function DashboardContainer() {
|
||||||
}, [postContentDate]);
|
}, [postContentDate]);
|
||||||
async function fetchPostCount() {
|
async function fetchPostCount() {
|
||||||
const getDate = (data: any) => {
|
const getDate = (data: any) => {
|
||||||
return `${data.year}-${data.month}-${data.day}`;
|
return `${data.year}-${data.month < 10 ? `0${data.month}` : data.month}-${
|
||||||
|
data.day < 10 ? `0${data.day}` : data.day
|
||||||
|
}`;
|
||||||
};
|
};
|
||||||
const res = await getUserLevelDataStat(
|
const res = await getUserLevelDataStat(
|
||||||
getDate(postContentDate.startDate),
|
getDate(postContentDate.startDate),
|
||||||
|
|
@ -164,7 +167,6 @@ export default function DashboardContainer() {
|
||||||
return date.month + " " + date.year;
|
return date.month + " " + date.year;
|
||||||
};
|
};
|
||||||
const getMonthYearName = (date: any) => {
|
const getMonthYearName = (date: any) => {
|
||||||
console.log("dateee", date);
|
|
||||||
const newDate = new Date(date);
|
const newDate = new Date(date);
|
||||||
|
|
||||||
const months = [
|
const months = [
|
||||||
|
|
@ -249,14 +251,6 @@ export default function DashboardContainer() {
|
||||||
Rekapitulasi Post Berita Polda/Polres Pada Website
|
Rekapitulasi Post Berita Polda/Polres Pada Website
|
||||||
</p>
|
</p>
|
||||||
<div className="w-[220px] flex flex-row gap-2 justify-between font-semibold">
|
<div className="w-[220px] flex flex-row gap-2 justify-between font-semibold">
|
||||||
{/* <Datepicker
|
|
||||||
value={postContentDate}
|
|
||||||
displayFormat="DD/MM/YYYY"
|
|
||||||
asSingle={false}
|
|
||||||
useRange={true}
|
|
||||||
onChange={(e: any) => setPostContentDate(e)}
|
|
||||||
inputClassName="z-50 w-full text-xs lg:text-sm bg-transparent border-1 border-gray-200 px-2 py-[6px] rounded-sm lg:rounded-lg h-[30px] lg:h-[40px] text-gray-600 dark:text-gray-300"
|
|
||||||
/> */}
|
|
||||||
<Popover
|
<Popover
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
||||||
|
|
@ -344,7 +338,8 @@ export default function DashboardContainer() {
|
||||||
key={list?.id}
|
key={list?.id}
|
||||||
className="flex flex-row gap-2 items-center border-b-2 py-2"
|
className="flex flex-row gap-2 items-center border-b-2 py-2"
|
||||||
>
|
>
|
||||||
<img
|
<Image
|
||||||
|
alt="thumbnail"
|
||||||
src={list?.thumbnailUrl || `/no-image.jpg`}
|
src={list?.thumbnailUrl || `/no-image.jpg`}
|
||||||
className="h-[70px] w-[70px] object-cover rounded-lg"
|
className="h-[70px] w-[70px] object-cover rounded-lg"
|
||||||
/>
|
/>
|
||||||
|
|
@ -419,14 +414,14 @@ export default function DashboardContainer() {
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant={typeDate === "monthly" ? "solid" : "bordered"}
|
variant={typeDate === "monthly" ? "solid" : "bordered"}
|
||||||
onClick={() => setTypeDate("monthly")}
|
onPress={() => setTypeDate("monthly")}
|
||||||
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg"
|
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg"
|
||||||
>
|
>
|
||||||
Bulanan
|
Bulanan
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setTypeDate("weekly")}
|
onPress={() => setTypeDate("weekly")}
|
||||||
variant={typeDate === "weekly" ? "solid" : "bordered"}
|
variant={typeDate === "weekly" ? "solid" : "bordered"}
|
||||||
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg"
|
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-sm lg:rounded-lg"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,12 @@ export default function ArticleTable() {
|
||||||
<SelectItem key="10" value="10">
|
<SelectItem key="10" value="10">
|
||||||
10
|
10
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
<SelectItem key="25" value="25">
|
||||||
|
25
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem key="50" value="50">
|
||||||
|
50
|
||||||
|
</SelectItem>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 w-full lg:w-[230px]">
|
<div className="flex flex-col gap-1 w-full lg:w-[230px]">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,422 @@
|
||||||
|
"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";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: "No", uid: "no" },
|
||||||
|
{ name: "Nama", uid: "name" },
|
||||||
|
{ name: "Email", uid: "email" },
|
||||||
|
{ name: "Komentar", uid: "comment" },
|
||||||
|
{ 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 CommentTable(props: { triggerRefresh: boolean }) {
|
||||||
|
const MySwal = withReactContent(Swal);
|
||||||
|
const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
|
||||||
|
const router = useRouter();
|
||||||
|
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(
|
||||||
|
(comment: any, columnKey: Key) => {
|
||||||
|
const cellValue = comment[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/comment/detail/${article.id}`}>
|
||||||
|
<EyeIconMdi className="inline mr-2 mb-1" />
|
||||||
|
Detail
|
||||||
|
</Link>
|
||||||
|
</DropdownItem> */}
|
||||||
|
<DropdownItem
|
||||||
|
key="edit"
|
||||||
|
onPress={() =>
|
||||||
|
router.push(`/admin/comment/review${comment.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CreateIconIon className="inline mr-2 mb-1" />
|
||||||
|
Review
|
||||||
|
</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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
Divider,
|
Divider,
|
||||||
Chip,
|
Chip,
|
||||||
ChipProps,
|
ChipProps,
|
||||||
|
Checkbox,
|
||||||
} from "@heroui/react";
|
} from "@heroui/react";
|
||||||
import { Button } from "@heroui/button";
|
import { Button } from "@heroui/button";
|
||||||
import React, { Key, useCallback, useEffect, useMemo, useState } from "react";
|
import React, { Key, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
@ -32,6 +33,7 @@ import {
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getAllUserLevels } from "@/services/user-levels/user-levels-service";
|
import { getAllUserLevels } from "@/services/user-levels/user-levels-service";
|
||||||
import { close, loading } from "@/config/swal";
|
import { close, loading } from "@/config/swal";
|
||||||
|
import { stringify } from "querystring";
|
||||||
|
|
||||||
type UserObject = {
|
type UserObject = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -45,10 +47,12 @@ type UserObject = {
|
||||||
|
|
||||||
export default function MasterUserLevelTable() {
|
export default function MasterUserLevelTable() {
|
||||||
const [totalPage, setTotalPage] = useState(1);
|
const [totalPage, setTotalPage] = useState(1);
|
||||||
const [masterUserLevelTable, setmasterUserLevel] = useState<UserObject[]>([]);
|
const [masterUserLevelTable, setMasterUserLevel] = useState<UserObject[]>([]);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const [doSetup, setDoSetup] = useState(false);
|
||||||
type TableRow = (typeof masterUserLevelTable)[0];
|
const [userLevelAll, setUserLevelAll] = useState<UserObject[]>([]);
|
||||||
|
const [selectAllLevel, setSelectAllLevel] = useState(false);
|
||||||
|
const [selectedLevel, setSelectedLevel] = useState<string[]>([]);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: "No", uid: "no" },
|
{ name: "No", uid: "no" },
|
||||||
|
|
@ -56,8 +60,15 @@ export default function MasterUserLevelTable() {
|
||||||
{ name: "User Name", uid: "aliasName" },
|
{ name: "User Name", uid: "aliasName" },
|
||||||
{ name: "Level Number", uid: "levelNumber" },
|
{ name: "Level Number", uid: "levelNumber" },
|
||||||
{ name: "Parent", uid: "parentLevelId" },
|
{ name: "Parent", uid: "parentLevelId" },
|
||||||
// { name: "Province", uid: "province_id" },
|
{ name: "Action", uid: "actions" },
|
||||||
// { name: "Status", uid: "is_active" },
|
];
|
||||||
|
const columns2 = [
|
||||||
|
{ name: "No", uid: "no" },
|
||||||
|
{ name: "Setup", uid: "setup" },
|
||||||
|
{ name: "Name", uid: "name" },
|
||||||
|
{ name: "User Name", uid: "aliasName" },
|
||||||
|
{ name: "Level Number", uid: "levelNumber" },
|
||||||
|
{ name: "Parent", uid: "parentLevelId" },
|
||||||
{ name: "Action", uid: "actions" },
|
{ name: "Action", uid: "actions" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -65,7 +76,11 @@ export default function MasterUserLevelTable() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [page]);
|
}, [page, selectedLevel]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDataAll();
|
||||||
|
}, []);
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
loading();
|
loading();
|
||||||
|
|
@ -80,6 +95,18 @@ export default function MasterUserLevelTable() {
|
||||||
await initUserData(10, data);
|
await initUserData(10, data);
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
async function fetchDataAll() {
|
||||||
|
loading();
|
||||||
|
const request = {
|
||||||
|
page: 1,
|
||||||
|
limit: -1,
|
||||||
|
search: "",
|
||||||
|
};
|
||||||
|
const res = await getAllUserLevels(request);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
setUserLevelAll(data);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
async function initUserData(limit: number, data?: any) {
|
async function initUserData(limit: number, data?: any) {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
@ -91,24 +118,10 @@ export default function MasterUserLevelTable() {
|
||||||
value.no = startIndex + iterate;
|
value.no = startIndex + iterate;
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
setmasterUserLevel(newData);
|
setMasterUserLevel(newData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const findParentName = (data: string | number) => {
|
|
||||||
let name = "-";
|
|
||||||
|
|
||||||
for (let i = 9; i < masterUserLevelTable?.length; i++) {
|
|
||||||
const temp = masterUserLevelTable[i];
|
|
||||||
if (temp.id === Number(data)) {
|
|
||||||
name = temp.name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
};
|
|
||||||
|
|
||||||
let typingTimer: NodeJS.Timeout;
|
let typingTimer: NodeJS.Timeout;
|
||||||
const doneTypingInterval = 1500;
|
const doneTypingInterval = 1500;
|
||||||
|
|
||||||
|
|
@ -125,13 +138,68 @@ export default function MasterUserLevelTable() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderCell = useCallback(
|
const doMapping = (status: boolean) => {
|
||||||
(masterUserLevel: TableRow, columnKey: Key) => {
|
setSelectAllLevel(status);
|
||||||
const cellValue = masterUserLevel[columnKey as keyof UserObject];
|
if (status) {
|
||||||
|
const temp = [];
|
||||||
|
for (const element of userLevelAll) {
|
||||||
|
temp.push(String(element.id));
|
||||||
|
}
|
||||||
|
setSelectedLevel(temp);
|
||||||
|
} else {
|
||||||
|
setSelectedLevel([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectedLevel = (id: string, checked: boolean) => {
|
||||||
|
console.log("change", id, checked);
|
||||||
|
const temp = [...selectedLevel];
|
||||||
|
if (checked) {
|
||||||
|
temp.push(id);
|
||||||
|
setSelectedLevel(temp);
|
||||||
|
if (temp.length === userLevelAll.length) {
|
||||||
|
setSelectAllLevel(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const newTemp = temp.filter((a) => a !== id);
|
||||||
|
setSelectedLevel(newTemp);
|
||||||
|
if (newTemp.length !== userLevelAll.length) {
|
||||||
|
setSelectAllLevel(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCell = useCallback(
|
||||||
|
(masterUserLevel: UserObject, columnKey: Key) => {
|
||||||
|
const findParentName = (data: string | number) => {
|
||||||
|
let name = "-";
|
||||||
|
|
||||||
|
for (let i = 9; i < userLevelAll?.length; i++) {
|
||||||
|
const temp = userLevelAll[i];
|
||||||
|
if (temp.id === Number(data)) {
|
||||||
|
name = temp.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cellValue = masterUserLevel[columnKey as keyof UserObject];
|
||||||
switch (columnKey) {
|
switch (columnKey) {
|
||||||
case "parentLevelId":
|
case "parentLevelId":
|
||||||
return <p className="text-black">{findParentName(cellValue)}</p>;
|
return <p className="text-black">{findParentName(cellValue)}</p>;
|
||||||
|
|
||||||
|
case "setup":
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
key={masterUserLevel.id}
|
||||||
|
isSelected={selectedLevel?.includes(String(masterUserLevel.id))}
|
||||||
|
onValueChange={(e) =>
|
||||||
|
handleSelectedLevel(String(masterUserLevel.id), e)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case "actions":
|
case "actions":
|
||||||
return (
|
return (
|
||||||
<div className="relative flex justify-star items-center gap-2">
|
<div className="relative flex justify-star items-center gap-2">
|
||||||
|
|
@ -173,38 +241,43 @@ export default function MasterUserLevelTable() {
|
||||||
return cellValue;
|
return cellValue;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[masterUserLevelTable]
|
[selectedLevel, userLevelAll, masterUserLevelTable]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-3 my-5">
|
<div className="mx-3 my-5">
|
||||||
{/* <Link href="/admin/master/master-user-level/create">
|
<div className="flex flex-col items-start rounded-2xl">
|
||||||
<Button className="my-3 bg-blue-600 text-white">
|
<div className="flex flex-row items-end gap-3 mb-3">
|
||||||
<CreateIconIon />
|
<div className="flex flex-col gap-1 w-full justify-start">
|
||||||
Create New User Level
|
<p className="font-semibold text-sm">Pencarian</p>
|
||||||
</Button>
|
<Input
|
||||||
</Link> */}
|
aria-label="Search"
|
||||||
<div className="flex flex-col items-center rounded-2xl">
|
classNames={{
|
||||||
<div className="flex flex-col gap-1 w-full justify-start mb-3">
|
inputWrapper: "bg-default-100",
|
||||||
<p className="font-semibold text-sm">Pencarian</p>
|
input: "text-sm",
|
||||||
<Input
|
}}
|
||||||
aria-label="Search"
|
className="max-w-md"
|
||||||
classNames={{
|
labelPlacement="outside"
|
||||||
inputWrapper: "bg-default-100",
|
startContent={
|
||||||
input: "text-sm",
|
<SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" />
|
||||||
}}
|
}
|
||||||
className="max-w-md"
|
type="text"
|
||||||
labelPlacement="outside"
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
startContent={
|
onKeyUp={handleKeyUp}
|
||||||
<SearchIcon className="text-base text-default-400 pointer-events-none flex-shrink-0" />
|
onKeyDown={handleKeyDown}
|
||||||
}
|
/>
|
||||||
type="text"
|
</div>
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
<Button color="primary" onPress={() => setDoSetup(!doSetup)}>
|
||||||
onKeyUp={handleKeyUp}
|
Settings
|
||||||
onKeyDown={handleKeyDown}
|
</Button>
|
||||||
/>
|
{doSetup && (
|
||||||
|
<Button color="success" className="text-white">
|
||||||
|
Save ({selectedLevel.length})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
// selectionMode="multiple"
|
// selectionMode="multiple"
|
||||||
aria-label="micro issue table"
|
aria-label="micro issue table"
|
||||||
|
|
@ -216,9 +289,20 @@ export default function MasterUserLevelTable() {
|
||||||
"min-h-[50px] bg-transpararent text-black dark:text-white ",
|
"min-h-[50px] bg-transpararent text-black dark:text-white ",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableHeader columns={columns}>
|
<TableHeader columns={doSetup ? columns2 : columns}>
|
||||||
{(column) => (
|
{(column) => (
|
||||||
<TableColumn key={column.uid}>{column.name}</TableColumn>
|
<TableColumn key={column.uid}>
|
||||||
|
{column.uid === "setup" ? (
|
||||||
|
<Checkbox
|
||||||
|
isSelected={selectAllLevel}
|
||||||
|
onValueChange={(e) => {
|
||||||
|
doMapping(e);
|
||||||
|
}}
|
||||||
|
></Checkbox>
|
||||||
|
) : (
|
||||||
|
column.name
|
||||||
|
)}
|
||||||
|
</TableColumn>
|
||||||
)}
|
)}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody
|
<TableBody
|
||||||
|
|
@ -234,7 +318,7 @@ export default function MasterUserLevelTable() {
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
<div className="mt-2">
|
<div className="mt-2 justify-center flex w-full">
|
||||||
<Pagination
|
<Pagination
|
||||||
isCompact
|
isCompact
|
||||||
showControls
|
showControls
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,607 @@
|
||||||
|
"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, convertDateFormatNoTimeV2 } from "@/utils/global";
|
||||||
|
import { Button } from "@heroui/button";
|
||||||
|
import {
|
||||||
|
Calendar,
|
||||||
|
Chip,
|
||||||
|
ChipProps,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownTrigger,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
Pagination,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
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";
|
||||||
|
import SuggestionsChart from "@/components/main/dashboard/chart/suggestions-line-chart";
|
||||||
|
import { parseDate } from "@internationalized/date";
|
||||||
|
import { getFeedbacks, getFeedbacksById } from "@/service/feedbacks";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: "No", uid: "no" },
|
||||||
|
{ name: "Nama", uid: "name" },
|
||||||
|
{ name: "Email", uid: "email" },
|
||||||
|
{ name: "Kritik & Saran", uid: "suggestions" },
|
||||||
|
{ name: "Aksi", uid: "actions" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const createArticleSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
commentFromName: z.string().min(2, {
|
||||||
|
message: "Nama harus diisi",
|
||||||
|
}),
|
||||||
|
commentFromEmail: z.string().min(2, {
|
||||||
|
message: "Email harus diisi",
|
||||||
|
}),
|
||||||
|
description: z.string().min(2, {
|
||||||
|
message: "Pesan harus diisi",
|
||||||
|
}),
|
||||||
|
file: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function SuggestionsTable() {
|
||||||
|
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 [isReply, setIsReply] = useState(false);
|
||||||
|
const [replyValue, setReplyValue] = useState("");
|
||||||
|
|
||||||
|
const formOptions = {
|
||||||
|
resolver: zodResolver(createArticleSchema),
|
||||||
|
defaultValues: {
|
||||||
|
commentFromName: "",
|
||||||
|
description: "",
|
||||||
|
commentFromEmail: "",
|
||||||
|
file: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserSettingSchema = z.infer<typeof createArticleSchema>;
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<UserSettingSchema>(formOptions);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initState();
|
||||||
|
}, [page, showData]);
|
||||||
|
|
||||||
|
async function initState() {
|
||||||
|
const res = await getFeedbacks({ limit: showData, search: search });
|
||||||
|
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 = {
|
||||||
|
commentFromName: values.commentFromName,
|
||||||
|
message: values.description,
|
||||||
|
commentFromEmail: values.commentFromEmail,
|
||||||
|
};
|
||||||
|
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 getFeedbacksById(id);
|
||||||
|
const data = res?.data?.data;
|
||||||
|
setValue("id", String(data?.id));
|
||||||
|
setValue("commentFromName", data?.commentFromName);
|
||||||
|
setValue("commentFromEmail", data?.commentFromEmail);
|
||||||
|
setValue("description", data?.message);
|
||||||
|
|
||||||
|
onOpen();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMonthYearName = (date: any) => {
|
||||||
|
const newDate = new Date(date);
|
||||||
|
|
||||||
|
const months = [
|
||||||
|
"Januari",
|
||||||
|
"Februari",
|
||||||
|
"Maret",
|
||||||
|
"April",
|
||||||
|
"Mei",
|
||||||
|
"Juni",
|
||||||
|
"Juli",
|
||||||
|
"Agustus",
|
||||||
|
"September",
|
||||||
|
"Oktober",
|
||||||
|
"November",
|
||||||
|
"Desember",
|
||||||
|
];
|
||||||
|
const year = newDate.getFullYear();
|
||||||
|
|
||||||
|
const month = months[newDate.getMonth()];
|
||||||
|
return month + " " + year;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCell = useCallback(
|
||||||
|
(suggestion: any, columnKey: Key) => {
|
||||||
|
const cellValue = suggestion[columnKey as keyof any];
|
||||||
|
|
||||||
|
switch (columnKey) {
|
||||||
|
case "commentFromEmail":
|
||||||
|
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/suggestion/detail/${article.id}`}>
|
||||||
|
<EyeIconMdi className="inline mr-2 mb-1" />
|
||||||
|
Detail
|
||||||
|
</Link>
|
||||||
|
</DropdownItem> */}
|
||||||
|
<DropdownItem
|
||||||
|
key="edit"
|
||||||
|
onPress={() => openModal(suggestion.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [startDateValue, setStartDateValue] = useState(
|
||||||
|
parseDate(convertDateFormatNoTimeV2(new Date()))
|
||||||
|
);
|
||||||
|
const [typeDate, setTypeDate] = useState("monthly");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
variant={typeDate === "monthly" ? "solid" : "bordered"}
|
||||||
|
onPress={() => setTypeDate("monthly")}
|
||||||
|
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-md lg:rounded-lg"
|
||||||
|
>
|
||||||
|
Bulanan
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onPress={() => setTypeDate("weekly")}
|
||||||
|
variant={typeDate === "weekly" ? "solid" : "bordered"}
|
||||||
|
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-md lg:rounded-lg"
|
||||||
|
>
|
||||||
|
Mingguan
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onPress={() => setTypeDate("daily")}
|
||||||
|
variant={typeDate === "daily" ? "solid" : "bordered"}
|
||||||
|
className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-md lg:rounded-lg"
|
||||||
|
>
|
||||||
|
Harian
|
||||||
|
</Button>
|
||||||
|
<div className="w-[140px]">
|
||||||
|
<Popover
|
||||||
|
placement="bottom"
|
||||||
|
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
||||||
|
>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button className="w-[140px] text-xs lg:text-sm h-[30px] lg:h-[40px] rounded-md lg:rounded-lg">
|
||||||
|
{getMonthYearName(startDateValue)}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="bg-transparent">
|
||||||
|
<Calendar value={startDateValue} onChange={setStartDateValue} />
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row w-full h-full">
|
||||||
|
<div className="w-full h-[30vh] lg:h-[300px] text-black">
|
||||||
|
<SuggestionsChart
|
||||||
|
type={typeDate}
|
||||||
|
date={`${startDateValue.month} ${startDateValue.year}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<Button onPress={() => openModal(4)}>test</Button>
|
||||||
|
</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">
|
||||||
|
Kritik Saran
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
className="flex flex-col gap-3"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="text-sm">Nama</p>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="commentFromName"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="commentFromName"
|
||||||
|
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?.commentFromName && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.commentFromName?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<p className="text-sm">Email</p>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="commentFromEmail"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="commentFromEmail"
|
||||||
|
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?.commentFromEmail && (
|
||||||
|
<p className="text-red-400 text-sm">
|
||||||
|
{errors.commentFromEmail?.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">
|
||||||
|
<a
|
||||||
|
className="text-sm cursor-pointer hover:underline text-primary"
|
||||||
|
onClick={() => setIsReply(!isReply)}
|
||||||
|
>
|
||||||
|
Balas
|
||||||
|
</a>
|
||||||
|
{isReply && (
|
||||||
|
<Textarea
|
||||||
|
type="text"
|
||||||
|
id="description"
|
||||||
|
placeholder=""
|
||||||
|
label=""
|
||||||
|
value={replyValue}
|
||||||
|
onValueChange={setReplyValue}
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ModalFooter className="self-end grow items-end">
|
||||||
|
{isReply && (
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
isDisabled={replyValue == ""}
|
||||||
|
>
|
||||||
|
Kirim
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button color="danger" variant="light" onPress={onClose}>
|
||||||
|
Tutup
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
|
</ModalBody>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,55 +1,46 @@
|
||||||
{
|
{
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"id": "1",
|
"id": 1,
|
||||||
"year": "2024",
|
"year": 2024,
|
||||||
"month": "november",
|
"month": 11,
|
||||||
"comment": [
|
"suggestions": [
|
||||||
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
|
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
|
||||||
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22
|
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22
|
||||||
],
|
|
||||||
"view": [
|
|
||||||
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 14, 32, 10, 21, 15, 18,
|
|
||||||
24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13
|
|
||||||
],
|
|
||||||
"share": [
|
|
||||||
24, 30, 12, 25, 19, 28, 14, 2, 31, 27, 13, 17, 22, 31, 27, 13, 18, 21,
|
|
||||||
26, 23, 14, 18, 24, 30, 12, 25, 19, 28, 14, 17
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2",
|
"id": 2,
|
||||||
"year": "2024",
|
"year": 2024,
|
||||||
"month": "december",
|
"month": 12,
|
||||||
"comment": [
|
"suggestions": [
|
||||||
15, 23, 19, 14, 18, 20, 22, 17, 21, 19, 23, 16, 25, 20, 18, 19, 22, 24,
|
15, 23, 19, 14, 18, 20, 22, 17, 21, 19, 23, 16, 25, 20, 18, 19, 22, 24,
|
||||||
15, 18, 21, 26, 28, 23, 17, 20, 19, 22, 22, 42, 32
|
15, 18, 21, 26, 28, 23, 17, 20, 19, 22, 22, 42, 32
|
||||||
],
|
|
||||||
"view": [
|
|
||||||
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 14, 32, 10, 21, 15, 18,
|
|
||||||
24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13, 21
|
|
||||||
],
|
|
||||||
"share": [
|
|
||||||
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
|
|
||||||
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 32
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "3",
|
"id": 3,
|
||||||
"year": "2025",
|
"year": 2025,
|
||||||
"month": "january",
|
"month": 1,
|
||||||
"comment": [
|
"suggestions": [
|
||||||
21, 24, 19, 27, 29, 23, 18, 20, 26, 22, 24, 30, 25, 19, 17, 21, 27, 23,
|
14, 32, 10, 21, 15, 18, 24, 30, 12, 25, 19, 28, 14, 17, 22, 31, 27, 13,
|
||||||
29, 25, 22
|
20, 24, 29, 18, 21, 26, 23, 14, 19, 17, 28, 22, 21
|
||||||
],
|
|
||||||
"view": [
|
|
||||||
21, 24, 19, 25, 19, 17, 21, 27, 23, 29, 25, 22, 27, 29, 23, 18, 20, 26,
|
|
||||||
22, 24, 30
|
|
||||||
],
|
|
||||||
"share": [
|
|
||||||
22, 24, 30, 25, 19, 17, 21, 27, 23, 29, 25, 22, 21, 24, 19, 27, 29, 23,
|
|
||||||
18, 20, 26
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"year": 2025,
|
||||||
|
"month": 2,
|
||||||
|
"suggestions": [
|
||||||
|
15, 23, 19, 14, 18, 20, 22, 17, 21, 19, 23, 16, 25, 20, 18, 19, 22, 24,
|
||||||
|
15, 18, 21, 26, 28, 23, 17, 20, 19, 22
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"year": 2025,
|
||||||
|
"month": 3,
|
||||||
|
"suggestions": [14, 32, 10, 21, 15, 18]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import {
|
||||||
|
httpDeleteInterceptor,
|
||||||
|
httpGet,
|
||||||
|
httpPost,
|
||||||
|
httpPut,
|
||||||
|
} from "./http-config/axios-base-service";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
|
const token = Cookies.get("access_token");
|
||||||
|
|
||||||
|
export async function createFeedback(data: any) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
|
const pathUrl = `/feedbacks`;
|
||||||
|
return await httpPost(pathUrl, headers, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFeedbacks(data: any) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
|
const pathUrl = `/feedbacks?limit=${data?.limit || ""}&message=${
|
||||||
|
data?.search || ""
|
||||||
|
}`;
|
||||||
|
return await httpGet(pathUrl, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFeedbacksById(id: number) {
|
||||||
|
const headers = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
|
const pathUrl = `/feedbacks/${id}`;
|
||||||
|
return await httpGet(pathUrl, headers);
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ export async function getAllUserLevels(data?: any) {
|
||||||
return await httpGet(
|
return await httpGet(
|
||||||
`user-levels?limit=${data?.limit || ""}&levelNumber=${
|
`user-levels?limit=${data?.limit || ""}&levelNumber=${
|
||||||
data?.levelNumber || ""
|
data?.levelNumber || ""
|
||||||
}&name=${data?.search || ""}`,
|
}&name=${data?.search || ""}&page=${data?.page || "1"}`,
|
||||||
headers
|
headers
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue