diff --git a/src/app/(application)/discussion/create/page.tsx b/src/app/(application)/discussion/create/page.tsx index 0f1280f..ac26ec4 100644 --- a/src/app/(application)/discussion/create/page.tsx +++ b/src/app/(application)/discussion/create/page.tsx @@ -1,10 +1,8 @@ -import { LayoutNavbarNew } from "@/module/_global"; import { FormCreateDiscussionGeneral } from "@/module/discussion_general"; export default function Page() { return ( <> - } /> ) diff --git a/src/app/api/discussion-general/route.ts b/src/app/api/discussion-general/route.ts index b1696e6..e9b01ce 100644 --- a/src/app/api/discussion-general/route.ts +++ b/src/app/api/discussion-general/route.ts @@ -70,47 +70,37 @@ export async function GET(request: Request) { } } - const data = await prisma.discussion.findMany() - - // const data = await prisma.discussion.findMany({ - // skip: dataSkip, - // take: 10, - // where: { - // isActive: true, - // idVillage: String(villageId), - // idGroup: grup, - // title: { - // contains: (search == undefined || search == "null") ? "" : search, - // mode: "insensitive" - // }, - // }, - // orderBy: { - // status: 'desc' - // }, - // select: { - // id: true, - // title: true, - // desc: true, - // status: true, - // createdAt: true, - // User: { - // select: { - // name: true, - // img: true - // } - // }, - // DiscussionComment: { - // select: { - // id: true, - // } - // } - // } - // }); + const data = await prisma.discussion.findMany({ + skip: dataSkip, + take: 10, + where: { + isActive: true, + idVillage: String(villageId), + idGroup: grup, + title: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + }, + }, + orderBy: { + status: 'desc' + }, + select: { + id: true, + title: true, + desc: true, + status: true, + createdAt: true, + DiscussionComment: { + select: { + id: true, + } + } + } + }); const fixData = data.map((v: any) => ({ - ..._.omit(v, ["User", "DiscussionComment", "createdAt"]), - user_name: v.User.name, - img: v.User.img, + ..._.omit(v, ["DiscussionComment", "createdAt"]), total_komentar: v.DiscussionComment.length, createdAt: moment(v.createdAt).format("ll") })) diff --git a/src/module/discussion_general/lib/api_discussion_general.ts b/src/module/discussion_general/lib/api_discussion_general.ts index 6542402..d1fb6f3 100644 --- a/src/module/discussion_general/lib/api_discussion_general.ts +++ b/src/module/discussion_general/lib/api_discussion_general.ts @@ -1,4 +1,17 @@ +import { IFormMemberDisscussionGeneral } from "./type_discussion_general"; + export const funGetAllDiscussionGeneral = async (path?: string) => { const response = await fetch(`/api/discussion-general${(path) ? path : ''}`, { next: { tags: ['discussion-general'] } }); return await response.json().catch(() => null); +} + +export const funCreateDiscussionGeneral = async (data: { idGroup: string, title: string, desc: string, member: IFormMemberDisscussionGeneral[] }) => { + const response = await fetch(`/api/discussion-general`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); } \ No newline at end of file diff --git a/src/module/discussion_general/lib/type_discussion_general.ts b/src/module/discussion_general/lib/type_discussion_general.ts new file mode 100644 index 0000000..a27cefe --- /dev/null +++ b/src/module/discussion_general/lib/type_discussion_general.ts @@ -0,0 +1,5 @@ +export interface IFormMemberDisscussionGeneral { + idUser: string, + name: string, + img: string + } \ No newline at end of file diff --git a/src/module/discussion_general/lib/val_discussion_general.ts b/src/module/discussion_general/lib/val_discussion_general.ts new file mode 100644 index 0000000..3c3fb26 --- /dev/null +++ b/src/module/discussion_general/lib/val_discussion_general.ts @@ -0,0 +1,4 @@ +import { hookstate } from "@hookstate/core"; +import { IFormMemberDisscussionGeneral } from "./type_discussion_general"; + +export const globalMemberDiscussionGeneral = hookstate([]); \ No newline at end of file diff --git a/src/module/discussion_general/ui/choose_user.tsx b/src/module/discussion_general/ui/choose_user.tsx new file mode 100644 index 0000000..e8c7c41 --- /dev/null +++ b/src/module/discussion_general/ui/choose_user.tsx @@ -0,0 +1,249 @@ +"use client" +import { LayoutNavbarNew, SkeletonList, TEMA } from '@/module/_global'; +import { funGetAllmember, TypeUser } from '@/module/user'; +import { useHookstate } from '@hookstate/core'; +import { Carousel } from '@mantine/carousel'; +import { ActionIcon, Avatar, Box, Button, Center, Divider, Flex, Grid, Indicator, rem, Stack, Text, TextInput } from '@mantine/core'; +import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { FaCheck } from 'react-icons/fa6'; +import { HiChevronLeft, HiMagnifyingGlass } from 'react-icons/hi2'; +import { IoArrowBackOutline, IoClose } from 'react-icons/io5'; +import { globalMemberDiscussionGeneral } from '../lib/val_discussion_general'; + + +export default function ChooseUsersDiscussion({ grup, onClose }: { grup?: string, onClose: (val: any) => void }) { + const member = useHookstate(globalMemberDiscussionGeneral) + const [selectedFiles, setSelectedFiles] = useState([]); + const [dataMember, setDataMember] = useState([]) + const [loading, setLoading] = useState(true) + const [onClickSearch, setOnClickSearch] = useState(false) + const tema = useHookstate(TEMA) + const isMobile2 = useMediaQuery("(max-width: 438px)"); + + const handleFileClick = (index: number) => { + if (selectedFiles.some((i: any) => i.idUser == dataMember[index].id)) { + setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != dataMember[index].id)) + } else { + setSelectedFiles([...selectedFiles, { idUser: dataMember[index].id, name: dataMember[index].name, img: dataMember[index].img }]) + } + }; + + + async function loadData(search: string) { + try { + setLoading(true) + const res = await funGetAllmember('?active=true&group=' + grup + '&search=' + search); + // const user = await funGetUserByCookies(); + if (res.success) { + // setDataMember(res.data.filter((i: any) => i.id != user.id && i.idUserRole != 'supadmin' && i.idUserRole != 'cosupadmin')) + setDataMember(res.data.filter((i: any) => i.idUserRole != 'supadmin')) + // cek data member sebelumnya + if (member.length > 0) { + setSelectedFiles(JSON.parse(JSON.stringify(member.get()))) + } + } else { + toast.error("Gagal mendapatkan data, coba lagi nanti") + } + } catch (error) { + console.error(error) + toast.error("Gagal mendapatkan data, coba lagi nanti") + } finally { + setLoading(false) + } + } + + + function onSubmit() { + if (selectedFiles.length == 0) { + return toast.error("Error! silahkan pilih anggota") + } + member.set(selectedFiles) + onClose(true) + } + + useShallowEffect(() => { + loadData("") + }, []); + + const handleSearchClick = () => { + setOnClickSearch(true); + }; + + const handleClose = () => { + setOnClickSearch(false); + }; + + function handleXMember(id: number) { + setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != id)) + } + + return ( + + + { onClose(true) }} bg={tema.get().bgIcon} size="lg" radius="lg" aria-label="Settings"> + + + + } title="Pilih Anggota" + menu={ + + } + /> + {/* SEARCH */} + {onClickSearch + ? ( + + + + + + + + + loadData(e.target.value)} + /> + + + + ) + : null + } + {/* Close User */} + + {selectedFiles.length > 0 ? ( + + {selectedFiles.map((v: any, i: any) => { + return ( + + { handleXMember(v.idUser) }} + > +
+ }> + + +
+ {v.name} +
+
+ ) + })} +
+ ) : ( + + + Tidak ada anggota yang dipilih + + + )} +
+ + + + {loading ? + Array(6) + .fill(null) + .map((_, i) => ( + + + + )) + : + (dataMember.length === 0) ? + + Tidak ada anggota + + : + dataMember.map((v, index) => { + const isSelected = selectedFiles.some((i: any) => i.idUser == dataMember[index].id); + return ( + handleFileClick(index)}> + + + + + + + + {v.name} + + {isSelected ? : null} + + + + + + + + ); + }) + } + + + + + + + + ); +} + diff --git a/src/module/discussion_general/ui/create_discussion.tsx b/src/module/discussion_general/ui/create_discussion.tsx index f730a86..bb3161e 100644 --- a/src/module/discussion_general/ui/create_discussion.tsx +++ b/src/module/discussion_general/ui/create_discussion.tsx @@ -1,13 +1,53 @@ 'use client' import { funGetAllGroup, IDataGroup } from "@/module/group"; -import { Box, Group, Select, Text, TextInput } from "@mantine/core"; -import { useShallowEffect } from "@mantine/hooks"; +import { Avatar, Box, Button, Divider, Grid, Group, rem, Select, Text, TextInput } from "@mantine/core"; +import { useMediaQuery, useShallowEffect } from "@mantine/hooks"; import { useState } from "react"; import toast from "react-hot-toast"; import { IoIosArrowDropright } from "react-icons/io"; +import ChooseUsersDiscussion from "./choose_user"; +import { useHookstate } from "@hookstate/core"; +import { globalRole, keyWibu, LayoutNavbarNew, TEMA } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { globalMemberDiscussionGeneral } from "../lib/val_discussion_general"; +import { IFormMemberDisscussionGeneral } from "../lib/type_discussion_general"; +import LayoutModal from "@/module/_global/layout/layout_modal"; +import { funCreateDiscussionGeneral } from "../lib/api_discussion_general"; +import { useRouter } from "next/navigation"; +import { useWibuRealtime } from "wibu-realtime"; export default function FormCreateDiscussionGeneral() { - const [dataGroup, setDataGroup] = useState([]); + const router = useRouter() + const isMobile = useMediaQuery('(max-width: 369px)') + const roleLogin = useHookstate(globalRole) + const [isModal, setModal] = useState(false) + const [dataGroup, setDataGroup] = useState([]) + const [isChooseAnggota, setChooseAnggota] = useState(false) + const member = useHookstate(globalMemberDiscussionGeneral) + const memberValue = member.get() as IFormMemberDisscussionGeneral[] + const tema = useHookstate(TEMA) + const [loadingModal, setLoadingModal] = useState(false) + const [data, setDataRealtime] = useWibuRealtime({ + WIBU_REALTIME_TOKEN: keyWibu, + project: "sdm" + }) + const [body, setBody] = useState({ + idGroup: "", + title: "", + desc: "" + }); + + const [touched, setTouched] = useState({ + title: false, + idGroup: false, + desc: false + }); + + function onToChooseAnggota() { + if (roleLogin.get() == "supadmin" && body.idGroup == "") + return toast.error("Error! grup harus diisi") + setChooseAnggota(true) + } async function loadData() { const loadGroup = await funGetAllGroup('?active=true') @@ -16,71 +56,270 @@ export default function FormCreateDiscussionGeneral() { } else { toast.error(loadGroup.message); } + + if (roleLogin.get() != "supadmin") { + const loadUser = await funGetUserByCookies(); + setBody({ ...body, idGroup: loadUser.idGroup }) + } } useShallowEffect(() => { loadData(); }, []); + + function onCheck() { + const cek = checkAll() + if (!cek) + return false + + if (memberValue.length <= 1) + return toast.error("Error! Silahkan pilih anggota lebih dari 1") + + setModal(true) + } + + function checkAll() { + let nilai = true + if (body.idGroup === "" || String(body.idGroup) == "null") { + setTouched(touched => ({ ...touched, idGroup: true })) + nilai = false + } + if (body.title === "") { + setTouched(touched => ({ ...touched, title: true })) + nilai = false + } + if (body.desc === "") { + setTouched(touched => ({ ...touched, desc: true })) + nilai = false + } + return nilai + } + + + function onValidation(kategori: string, val: string) { + if (kategori == 'idGroup') { + setBody({ ...body, idGroup: val }) + if (val === "" || String(val) == "null") { + setTouched({ ...touched, idGroup: true }) + } else { + setTouched({ ...touched, idGroup: false }) + } + } else if (kategori == 'title') { + setBody({ ...body, title: val }) + if (val === "") { + setTouched({ ...touched, title: true }) + } else { + setTouched({ ...touched, title: false }) + } + } else if (kategori == 'diskusi') { + setBody({ ...body, desc: val }) + if (val === "") { + setTouched({ ...touched, desc: true }) + } else { + setTouched({ ...touched, desc: false }) + } + } + } + + function onChooseGroup(val: any) { + member.set([]) + onValidation('idGroup', val) + } + + async function onSubmit() { + try { + setLoadingModal(true) + const res = await funCreateDiscussionGeneral({ idGroup: body.idGroup, title: body.title, desc: body.desc, member: memberValue }) + if (res.success) { + setDataRealtime(res.notif) + member.set([]) + toast.success(res.message) + router.push('/discussion') + } else { + toast.error(res.message) + } + } catch (error) { + console.error(error) + toast.error("Gagal membuat diskusi umum, coba lagi nanti") + } finally { + setLoadingModal(false) + setModal(false) + } + } + + if (isChooseAnggota) return setChooseAnggota(false)} /> + return ( - - - ({ + value: String(pro.id), + label: pro.name + }))} + onChange={(val) => { + onChooseGroup(val) + }} + value={(body.idGroup == "") ? null : body.idGroup} + error={ + touched.idGroup && ( + body.idGroup == "" || String(body.idGroup) == "null" ? "Grup Tidak Boleh Kosong" : null + ) + } + /> + ) + } + + - Pilih Anggota - - + mt={10} + required withAsterisk + placeholder="Judul" + size="md" + value={body.title} + onChange={(e) => { onValidation('title', e.target.value) }} + error={ + touched.title && ( + body.title == "" ? "Judul Tidak Boleh Kosong" : null + ) + } + /> + { onValidation('diskusi', e.target.value) }} + error={ + touched.desc && ( + body.desc == "" ? "Diskusi Tidak Boleh Kosong" : null + ) + } + /> + + onToChooseAnggota()} + justify="space-between" + p={10} + style={{ + border: `1px solid ${"#D6D8F6"}`, + borderRadius: 10, + }} + > + Pilih Anggota + + + + + + { + member.length > 0 && + + + Anggota Terpilih + Total {member.length} Anggota + + + + + {member.get().map((v: any, i: any) => { + return ( + + + + + + + + {v.name} + + + + + + + Anggota + + + + + + + + ); + })} + + + + + } + + + + + + setModal(false)} + description="Apakah Anda yakin ingin menambahkan data?" + onYes={(val) => { + if (val) { + onSubmit() + } else { + setModal(false) + } + }} /> ) } \ No newline at end of file diff --git a/src/module/discussion_general/ui/list_discussion.tsx b/src/module/discussion_general/ui/list_discussion.tsx index 0e27fa3..99d96b9 100644 --- a/src/module/discussion_general/ui/list_discussion.tsx +++ b/src/module/discussion_general/ui/list_discussion.tsx @@ -1,7 +1,7 @@ 'use client' import { currentScroll, globalNotifPage, keyWibu, ReloadButtonTop, TEMA } from "@/module/_global"; import { useHookstate } from "@hookstate/core"; -import { Avatar, Badge, Box, Divider, Flex, Grid, Group, Skeleton, Spoiler, Text, TextInput } from "@mantine/core"; +import { ActionIcon, Avatar, Badge, Box, Divider, Flex, Grid, Group, Skeleton, Spoiler, Text, TextInput } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; import _ from "lodash"; import { useParams, useRouter } from "next/navigation"; @@ -11,6 +11,7 @@ import { GrChatOption } from "react-icons/gr"; import { HiMagnifyingGlass } from "react-icons/hi2"; import { useWibuRealtime } from "wibu-realtime"; import { funGetAllDiscussionGeneral } from "../lib/api_discussion_general"; +import { BiSolidCommentDetail } from "react-icons/bi"; export default function ListDiscussionGeneral() { @@ -175,7 +176,7 @@ export default function ListDiscussionGeneral() { return ( { - router.push(`/division/${param.id}/discussion/${v.id}`) + router.push(`discussion/${v.id}`) }}> - + + + - {v.user_name} + {v.title} {v.status === 1 ? "BUKA" : "TUTUP"}