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 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 = () => {
|
||||
return (
|
||||
// <div>
|
||||
// <MasterUserLevelTable />
|
||||
// </div>
|
||||
// <div>
|
||||
// <MappingUserLevel />
|
||||
// </div>
|
||||
<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 py-3">
|
||||
|
|
|
|||
|
|
@ -11,7 +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";
|
||||
import { Image } from "@heroui/react";
|
||||
|
||||
export default function Login() {
|
||||
const router = useRouter();
|
||||
|
|
@ -165,7 +165,7 @@ export default function Login() {
|
|||
height={480}
|
||||
alt="icon"
|
||||
src="/divhumas.png"
|
||||
className="w-[120px]"
|
||||
className="w-[240px] !h-[240px]"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -445,3 +445,43 @@ export const AddvertiseIcon = ({
|
|||
/>
|
||||
</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,
|
||||
CardFooter,
|
||||
CircularProgress,
|
||||
Image,
|
||||
ScrollShadow,
|
||||
} from "@heroui/react";
|
||||
import Image from "next/image";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, EyeIcon } from "../icons";
|
||||
import { Swiper, SwiperSlide, useSwiper } from "swiper/react";
|
||||
import "swiper/css";
|
||||
|
|
@ -101,8 +101,6 @@ export default function HeaderNews() {
|
|||
className="border-none rounded-xl shadow-none"
|
||||
>
|
||||
<Image
|
||||
width={720}
|
||||
height={480}
|
||||
alt="headernews"
|
||||
src={
|
||||
newsItem?.thumbnailUrl == ""
|
||||
|
|
@ -235,8 +233,6 @@ export default function HeaderNews() {
|
|||
>
|
||||
<Image
|
||||
alt="headernews"
|
||||
width={1440}
|
||||
height={1080}
|
||||
src={
|
||||
newsItem?.thumbnailUrl == ""
|
||||
? "/no-image.jpg"
|
||||
|
|
|
|||
|
|
@ -129,7 +129,6 @@ export default function PolriApps(props: {
|
|||
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||
const [scrollBehavior, setScrollBehavior] =
|
||||
React.useState<ModalProps["scrollBehavior"]>("inside");
|
||||
const [modalPlacement, setModalPlacement] = React.useState<any>("auto");
|
||||
const t = useTranslations("Landing");
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -138,24 +137,6 @@ export default function PolriApps(props: {
|
|||
}
|
||||
}, [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 cleaned = name.replace("Polda ", "").trim().toLowerCase();
|
||||
const slug = cleaned.replace(/\s+/g, "-");
|
||||
|
|
@ -164,16 +145,6 @@ export default function PolriApps(props: {
|
|||
|
||||
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
|
||||
isOpen={isOpen}
|
||||
onOpenChange={() => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use client";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
|
|
@ -35,6 +34,7 @@ import {
|
|||
PopoverContent,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
Image,
|
||||
} from "@heroui/react";
|
||||
import storedLanguage from "@/store/language-store";
|
||||
import { ThemeSwitch } from "../theme-switch";
|
||||
|
|
@ -107,8 +107,6 @@ export default function BannerHumasNew() {
|
|||
>
|
||||
{withImage && (
|
||||
<Image
|
||||
width={720}
|
||||
height={720}
|
||||
alt="logo"
|
||||
src={menu.img}
|
||||
className="w-[45px] h-[45px]"
|
||||
|
|
@ -167,8 +165,6 @@ export default function BannerHumasNew() {
|
|||
>
|
||||
{withImage && (
|
||||
<Image
|
||||
width={720}
|
||||
height={720}
|
||||
alt="logo"
|
||||
src={menu.img}
|
||||
className="w-[35px] h-[35px]"
|
||||
|
|
@ -201,8 +197,6 @@ export default function BannerHumasNew() {
|
|||
<Link href="" key={index} className="w-full shrink-0">
|
||||
<Image
|
||||
src={img}
|
||||
width={1440}
|
||||
height={1080}
|
||||
alt={`humasbanner-${index}`}
|
||||
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
|
||||
</p>
|
||||
</div>
|
||||
<Image
|
||||
src="/divhumas.png"
|
||||
width={720}
|
||||
height={720}
|
||||
alt="logo-humas"
|
||||
className="w-[200px]"
|
||||
/>
|
||||
<Image src="/divhumas.png" alt="logo-humas" className="w-[200px]" />
|
||||
</div>
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
import Image from "next/image";
|
||||
import RegionalNews from "./RegionalNews";
|
||||
import { useEffect, useState } from "react";
|
||||
import CategorySatker from "./CategorySatker";
|
||||
import PolriApps from "./PolriApps";
|
||||
import Link from "next/link";
|
||||
import SuggestionsModal from "./suggestions";
|
||||
import { Image } from "@heroui/react";
|
||||
|
||||
export default function DigitalServices() {
|
||||
const [isPoldaOpen, setIsPoldaOpen] = useState(false);
|
||||
|
|
@ -27,8 +27,6 @@ export default function DigitalServices() {
|
|||
>
|
||||
<Image
|
||||
src="/indonesia-map.png"
|
||||
width={480}
|
||||
height={480}
|
||||
alt="indonesia"
|
||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||
/>
|
||||
|
|
@ -45,8 +43,6 @@ export default function DigitalServices() {
|
|||
>
|
||||
<Image
|
||||
src="/satker.png"
|
||||
width={480}
|
||||
height={480}
|
||||
alt="satker"
|
||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||
/>
|
||||
|
|
@ -63,8 +59,6 @@ export default function DigitalServices() {
|
|||
>
|
||||
<Image
|
||||
src="/presisi.png"
|
||||
width={480}
|
||||
height={480}
|
||||
alt="presisi"
|
||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||
/>
|
||||
|
|
@ -81,8 +75,6 @@ export default function DigitalServices() {
|
|||
>
|
||||
<Image
|
||||
src="/kritik-saran.png"
|
||||
width={480}
|
||||
height={480}
|
||||
alt="kritik-saran"
|
||||
className="w-[100px] group-hover:scale-125 transition duration-300 ease-in-out"
|
||||
/>
|
||||
|
|
@ -99,8 +91,6 @@ export default function DigitalServices() {
|
|||
>
|
||||
<Image
|
||||
src="/survey.png"
|
||||
width={480}
|
||||
height={480}
|
||||
alt="survey"
|
||||
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 Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { createFeedback } from "@/service/feedbacks";
|
||||
|
||||
const createArticleSchema = z.object({
|
||||
email: z.string().min(2, {
|
||||
|
|
@ -71,26 +72,32 @@ export default function SuggestionsModal(props: {
|
|||
const onSubmit = async (values: z.infer<typeof createArticleSchema>) => {
|
||||
if (!needOtp) {
|
||||
loading();
|
||||
// const res = await otpRequest(values.email, values?.name);
|
||||
// if (res?.error) {
|
||||
// error(res.message);
|
||||
// return false;
|
||||
// }
|
||||
const res = await otpRequest(values.email, values?.name);
|
||||
if (res?.error) {
|
||||
error(res.message);
|
||||
return false;
|
||||
}
|
||||
close();
|
||||
setNeedOtp(true);
|
||||
} else {
|
||||
// const validation = await otpValidation(values.email, otpValue);
|
||||
// if (validation?.error) {
|
||||
// error("OTP Tidak Sesuai");
|
||||
// return false;
|
||||
// }
|
||||
const validation = await otpValidation(values.email, otpValue);
|
||||
if (validation?.error) {
|
||||
error("OTP Tidak Sesuai");
|
||||
return false;
|
||||
}
|
||||
|
||||
const req = {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
email: values.email,
|
||||
commentFromName: values.name,
|
||||
message: values.description,
|
||||
commentFromEmail: values.email,
|
||||
};
|
||||
|
||||
const res = await createFeedback(req);
|
||||
if (res?.error) {
|
||||
error(res?.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
MySwal.fire({
|
||||
title: "Berhasil Kirim",
|
||||
text: "",
|
||||
|
|
@ -108,17 +115,6 @@ export default function SuggestionsModal(props: {
|
|||
props.modalStatus(!props.opened);
|
||||
onOpenChange();
|
||||
}
|
||||
|
||||
// setRefresh(!refresh);
|
||||
// MySwal.fire({
|
||||
// title: "Sukses",
|
||||
// icon: "success",
|
||||
// confirmButtonColor: "#3085d6",
|
||||
// confirmButtonText: "OK",
|
||||
// }).then((result) => {
|
||||
// if (result.isConfirmed) {
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import {
|
||||
AddvertiseIcon,
|
||||
ArticleIcon,
|
||||
CommentIcon,
|
||||
DashboardIcon,
|
||||
HomeIcon,
|
||||
InfoCircleIcon,
|
||||
|
|
@ -25,6 +26,7 @@ import {
|
|||
MasterUsersIcon,
|
||||
MinusCircleIcon,
|
||||
StaticPageIcon,
|
||||
SuggestionsIcon,
|
||||
TableIcon,
|
||||
} from "../../icons/sidebar-icon";
|
||||
import { ThemeSwitch } from "../../theme-switch";
|
||||
|
|
@ -233,7 +235,34 @@ const sideBarDummyData = [
|
|||
statusName: "Active",
|
||||
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,
|
||||
// name: "E-Magazine",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import {
|
||||
AddvertiseIcon,
|
||||
ArticleIcon,
|
||||
CommentIcon,
|
||||
DashboardIcon,
|
||||
HomeIcon,
|
||||
InfoCircleIcon,
|
||||
|
|
@ -25,6 +26,7 @@ import {
|
|||
MasterUsersIcon,
|
||||
MinusCircleIcon,
|
||||
StaticPageIcon,
|
||||
SuggestionsIcon,
|
||||
TableIcon,
|
||||
} from "../../icons/sidebar-icon";
|
||||
import { ThemeSwitch } from "../../theme-switch";
|
||||
|
|
@ -234,6 +236,34 @@ const sideBarDummyData = [
|
|||
statusName: "Active",
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
Image,
|
||||
Pagination,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -138,7 +139,9 @@ export default function DashboardContainer() {
|
|||
}, [postContentDate]);
|
||||
async function fetchPostCount() {
|
||||
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(
|
||||
getDate(postContentDate.startDate),
|
||||
|
|
@ -164,7 +167,6 @@ export default function DashboardContainer() {
|
|||
return date.month + " " + date.year;
|
||||
};
|
||||
const getMonthYearName = (date: any) => {
|
||||
console.log("dateee", date);
|
||||
const newDate = new Date(date);
|
||||
|
||||
const months = [
|
||||
|
|
@ -249,14 +251,6 @@ export default function DashboardContainer() {
|
|||
Rekapitulasi Post Berita Polda/Polres Pada Website
|
||||
</p>
|
||||
<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
|
||||
placement="bottom"
|
||||
classNames={{ content: ["!bg-transparent", "p-0"] }}
|
||||
|
|
@ -344,7 +338,8 @@ export default function DashboardContainer() {
|
|||
key={list?.id}
|
||||
className="flex flex-row gap-2 items-center border-b-2 py-2"
|
||||
>
|
||||
<img
|
||||
<Image
|
||||
alt="thumbnail"
|
||||
src={list?.thumbnailUrl || `/no-image.jpg`}
|
||||
className="h-[70px] w-[70px] object-cover rounded-lg"
|
||||
/>
|
||||
|
|
@ -419,14 +414,14 @@ export default function DashboardContainer() {
|
|||
<Button
|
||||
color="primary"
|
||||
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"
|
||||
>
|
||||
Bulanan
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => setTypeDate("weekly")}
|
||||
onPress={() => setTypeDate("weekly")}
|
||||
variant={typeDate === "weekly" ? "solid" : "bordered"}
|
||||
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">
|
||||
10
|
||||
</SelectItem>
|
||||
<SelectItem key="25" value="25">
|
||||
25
|
||||
</SelectItem>
|
||||
<SelectItem key="50" value="50">
|
||||
50
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
<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,
|
||||
Chip,
|
||||
ChipProps,
|
||||
Checkbox,
|
||||
} from "@heroui/react";
|
||||
import { Button } from "@heroui/button";
|
||||
import React, { Key, useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
|
@ -32,6 +33,7 @@ import {
|
|||
import Link from "next/link";
|
||||
import { getAllUserLevels } from "@/services/user-levels/user-levels-service";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import { stringify } from "querystring";
|
||||
|
||||
type UserObject = {
|
||||
id: number;
|
||||
|
|
@ -45,10 +47,12 @@ type UserObject = {
|
|||
|
||||
export default function MasterUserLevelTable() {
|
||||
const [totalPage, setTotalPage] = useState(1);
|
||||
const [masterUserLevelTable, setmasterUserLevel] = useState<UserObject[]>([]);
|
||||
const [masterUserLevelTable, setMasterUserLevel] = useState<UserObject[]>([]);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
type TableRow = (typeof masterUserLevelTable)[0];
|
||||
const [doSetup, setDoSetup] = useState(false);
|
||||
const [userLevelAll, setUserLevelAll] = useState<UserObject[]>([]);
|
||||
const [selectAllLevel, setSelectAllLevel] = useState(false);
|
||||
const [selectedLevel, setSelectedLevel] = useState<string[]>([]);
|
||||
|
||||
const columns = [
|
||||
{ name: "No", uid: "no" },
|
||||
|
|
@ -56,8 +60,15 @@ export default function MasterUserLevelTable() {
|
|||
{ name: "User Name", uid: "aliasName" },
|
||||
{ name: "Level Number", uid: "levelNumber" },
|
||||
{ name: "Parent", uid: "parentLevelId" },
|
||||
// { name: "Province", uid: "province_id" },
|
||||
// { name: "Status", uid: "is_active" },
|
||||
{ name: "Action", uid: "actions" },
|
||||
];
|
||||
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" },
|
||||
];
|
||||
|
||||
|
|
@ -65,7 +76,11 @@ export default function MasterUserLevelTable() {
|
|||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [page]);
|
||||
}, [page, selectedLevel]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDataAll();
|
||||
}, []);
|
||||
|
||||
async function fetchData() {
|
||||
loading();
|
||||
|
|
@ -80,6 +95,18 @@ export default function MasterUserLevelTable() {
|
|||
await initUserData(10, data);
|
||||
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) {
|
||||
if (data) {
|
||||
|
|
@ -91,24 +118,10 @@ export default function MasterUserLevelTable() {
|
|||
value.no = startIndex + iterate;
|
||||
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;
|
||||
const doneTypingInterval = 1500;
|
||||
|
||||
|
|
@ -125,13 +138,68 @@ export default function MasterUserLevelTable() {
|
|||
fetchData();
|
||||
}
|
||||
|
||||
const renderCell = useCallback(
|
||||
(masterUserLevel: TableRow, columnKey: Key) => {
|
||||
const cellValue = masterUserLevel[columnKey as keyof UserObject];
|
||||
const doMapping = (status: boolean) => {
|
||||
setSelectAllLevel(status);
|
||||
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) {
|
||||
case "parentLevelId":
|
||||
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":
|
||||
return (
|
||||
<div className="relative flex justify-star items-center gap-2">
|
||||
|
|
@ -173,38 +241,43 @@ export default function MasterUserLevelTable() {
|
|||
return cellValue;
|
||||
}
|
||||
},
|
||||
[masterUserLevelTable]
|
||||
[selectedLevel, userLevelAll, masterUserLevelTable]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mx-3 my-5">
|
||||
{/* <Link href="/admin/master/master-user-level/create">
|
||||
<Button className="my-3 bg-blue-600 text-white">
|
||||
<CreateIconIon />
|
||||
Create New User Level
|
||||
</Button>
|
||||
</Link> */}
|
||||
<div className="flex flex-col items-center rounded-2xl">
|
||||
<div className="flex flex-col gap-1 w-full justify-start mb-3">
|
||||
<p className="font-semibold text-sm">Pencarian</p>
|
||||
<Input
|
||||
aria-label="Search"
|
||||
classNames={{
|
||||
inputWrapper: "bg-default-100",
|
||||
input: "text-sm",
|
||||
}}
|
||||
className="max-w-md"
|
||||
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 className="flex flex-col items-start rounded-2xl">
|
||||
<div className="flex flex-row items-end gap-3 mb-3">
|
||||
<div className="flex flex-col gap-1 w-full justify-start">
|
||||
<p className="font-semibold text-sm">Pencarian</p>
|
||||
<Input
|
||||
aria-label="Search"
|
||||
classNames={{
|
||||
inputWrapper: "bg-default-100",
|
||||
input: "text-sm",
|
||||
}}
|
||||
className="max-w-md"
|
||||
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>
|
||||
<Button color="primary" onPress={() => setDoSetup(!doSetup)}>
|
||||
Settings
|
||||
</Button>
|
||||
{doSetup && (
|
||||
<Button color="success" className="text-white">
|
||||
Save ({selectedLevel.length})
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Table
|
||||
// selectionMode="multiple"
|
||||
aria-label="micro issue table"
|
||||
|
|
@ -216,9 +289,20 @@ export default function MasterUserLevelTable() {
|
|||
"min-h-[50px] bg-transpararent text-black dark:text-white ",
|
||||
}}
|
||||
>
|
||||
<TableHeader columns={columns}>
|
||||
<TableHeader columns={doSetup ? columns2 : columns}>
|
||||
{(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>
|
||||
<TableBody
|
||||
|
|
@ -234,7 +318,7 @@ export default function MasterUserLevelTable() {
|
|||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div className="mt-2">
|
||||
<div className="mt-2 justify-center flex w-full">
|
||||
<Pagination
|
||||
isCompact
|
||||
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": [
|
||||
{
|
||||
"id": "1",
|
||||
"year": "2024",
|
||||
"month": "november",
|
||||
"comment": [
|
||||
"id": 1,
|
||||
"year": 2024,
|
||||
"month": 11,
|
||||
"suggestions": [
|
||||
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
|
||||
],
|
||||
"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",
|
||||
"year": "2024",
|
||||
"month": "december",
|
||||
"comment": [
|
||||
"id": 2,
|
||||
"year": 2024,
|
||||
"month": 12,
|
||||
"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, 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",
|
||||
"year": "2025",
|
||||
"month": "january",
|
||||
"comment": [
|
||||
21, 24, 19, 27, 29, 23, 18, 20, 26, 22, 24, 30, 25, 19, 17, 21, 27, 23,
|
||||
29, 25, 22
|
||||
],
|
||||
"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": 3,
|
||||
"year": 2025,
|
||||
"month": 1,
|
||||
"suggestions": [
|
||||
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, 21
|
||||
]
|
||||
},
|
||||
{
|
||||
"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(
|
||||
`user-levels?limit=${data?.limit || ""}&levelNumber=${
|
||||
data?.levelNumber || ""
|
||||
}&name=${data?.search || ""}`,
|
||||
}&name=${data?.search || ""}&page=${data?.page || "1"}`,
|
||||
headers
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue