diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9868b25..e187a42 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -60,6 +60,7 @@ model Village { Division Division[] ColorTheme ColorTheme[] BannerImage BannerImage[] + Discussion Discussion[] } model Group { @@ -75,6 +76,7 @@ model Group { Project Project[] Division Division[] AnnouncementMember AnnouncementMember[] + Discussion Discussion[] } model Position { @@ -125,6 +127,9 @@ model User { Notifications Notifications[] @relation("UserToUser") Notifications2 Notifications[] @relation("UserFromUser") Subscribe Subscribe? + Discussion Discussion[] + DiscussionMember DiscussionMember[] + DiscussionComment DiscussionComment[] } model UserLog { @@ -213,18 +218,18 @@ model ProjectFile { } model ProjectTask { - id String @id @default(cuid()) - Project Project @relation(fields: [idProject], references: [id]) - idProject String - title String - desc String? - status Int @default(0) // 0 = todo, 1 = done + id String @id @default(cuid()) + Project Project @relation(fields: [idProject], references: [id]) + idProject String + title String + desc String? + status Int @default(0) // 0 = todo, 1 = done notifikasi Boolean @default(false) - dateStart DateTime @db.Date - dateEnd DateTime @db.Date - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + dateStart DateTime @db.Date + dateEnd DateTime @db.Date + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model ProjectComment { @@ -521,3 +526,44 @@ model Subscribe { createdAt DateTime? @default(now()) updatedAt DateTime? @updatedAt } + +model Discussion { + id String @id @default(cuid()) + Village Village @relation(fields: [idVillage], references: [id]) + idVillage String + Group Group @relation(fields: [idGroup], references: [id]) + idGroup String + title String? + desc String @db.Text + status Int @default(1) // 1 = open, 2 = close + isActive Boolean @default(true) + User User @relation(fields: [createdBy], references: [id]) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + DiscussionMember DiscussionMember[] + DiscussionComment DiscussionComment[] +} + +model DiscussionMember { + id String @id @default(cuid()) + Discussion Discussion @relation(fields: [idDiscussion], references: [id]) + idDiscussion String + User User @relation(fields: [idUser], references: [id]) + idUser String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model DiscussionComment { + id String @id @default(cuid()) + Discussion Discussion @relation(fields: [idDiscussion], references: [id]) + idDiscussion String + User User @relation(fields: [idUser], references: [id]) + idUser String + comment String @db.Text + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/src/app/(application)/discussion/[id]/page.tsx b/src/app/(application)/discussion/[id]/page.tsx new file mode 100644 index 0000000..c306dfe --- /dev/null +++ b/src/app/(application)/discussion/[id]/page.tsx @@ -0,0 +1,7 @@ +export default function Page() { + return ( + <> + detail diskusi umum + + ) +} \ No newline at end of file diff --git a/src/app/(application)/discussion/create/page.tsx b/src/app/(application)/discussion/create/page.tsx new file mode 100644 index 0000000..0f1280f --- /dev/null +++ b/src/app/(application)/discussion/create/page.tsx @@ -0,0 +1,11 @@ +import { LayoutNavbarNew } from "@/module/_global"; +import { FormCreateDiscussionGeneral } from "@/module/discussion_general"; + +export default function Page() { + return ( + <> + } /> + + + ) +} \ No newline at end of file diff --git a/src/app/(application)/discussion/page.tsx b/src/app/(application)/discussion/page.tsx new file mode 100644 index 0000000..33890bc --- /dev/null +++ b/src/app/(application)/discussion/page.tsx @@ -0,0 +1,13 @@ +import { ListDiscussionGeneral, NavbarDiscussionGeneral } from '@/module/discussion_general'; +import React from 'react'; + +function Page() { + return ( +
+ + +
+ ); +} + +export default Page; diff --git a/src/app/api/discussion-general/route.ts b/src/app/api/discussion-general/route.ts new file mode 100644 index 0000000..b1696e6 --- /dev/null +++ b/src/app/api/discussion-general/route.ts @@ -0,0 +1,213 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + + +// GET ALL DISCUSSION GENERAL +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + let grup + const roleUser = user.idUserRole + const villageId = user.idVillage + const userId = user.id + const { searchParams } = new URL(request.url); + const idGroup = searchParams.get("group"); + const search = searchParams.get('search'); + const page = searchParams.get('page'); + const dataSkip = Number(page) * 10 - 10; + + if (idGroup == "null" || idGroup == undefined || idGroup == "") { + grup = user.idGroup + } else { + grup = idGroup + } + + const cek = await prisma.group.count({ + where: { + id: grup, + isActive: true + } + }) + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan data kegiatan, data tidak ditemukan", }, { status: 404 }); + } + + + let kondisi: any = { + isActive: true, + idVillage: String(villageId), + idGroup: grup, + title: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + }, + } + + if (roleUser != "supadmin" && roleUser != "cosupadmin" && roleUser != "admin") { + kondisi = { + isActive: true, + idVillage: String(villageId), + idGroup: grup, + title: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + }, + DiscussionMember: { + some: { + idUser: String(userId) + } + } + } + } + + 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 fixData = data.map((v: any) => ({ + ..._.omit(v, ["User", "DiscussionComment", "createdAt"]), + user_name: v.User.name, + img: v.User.img, + total_komentar: v.DiscussionComment.length, + createdAt: moment(v.createdAt).format("ll") + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: fixData, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// CREATE DISCUSSION GENERALE +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const userId = user.id + const userRoleLogin = user.idUserRole + const { idGroup, title, desc, member } = (await request.json()); + + const data = await prisma.discussion.create({ + data: { + idVillage: String(user.idVillage), + idGroup: idGroup, + title: title, + desc: desc, + createdBy: String(userId), + }, + select: { + id: true + } + }); + + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idDiscussion: data.id, + idUser: v.idUser, + })) + + const insertMember = await prisma.discussionMember.createMany({ + data: dataMember + }) + + const dataNotif = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idUserTo: v.idUser, + idUserFrom: userId, + category: 'discussion-general', + idContent: data.id, + title: 'Diskusi Umum Baru', + desc: 'Terdapat diskusi umum baru. Silahkan periksa detailnya.' + })) + + if (userRoleLogin != "supadmin") { + const perbekel = await prisma.user.findFirst({ + where: { + isActive: true, + idUserRole: "supadmin", + idVillage: user.idVillage + }, + select: { + id: true, + Subscribe: { + select: { + subscription: true + } + } + } + }) + + dataNotif.push({ + idUserTo: perbekel?.id, + idUserFrom: userId, + category: 'discussion-general', + idContent: data.id, + title: 'Diskusi Umum Baru', + desc: 'Terdapat diskusi umum baru. Silahkan periksa detailnya.' + }) + } + + const insertNotif = await prisma.notifications.createMany({ + data: dataNotif + }) + + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data diskusi umum', table: 'discussion', data: data.id }) + return NextResponse.json({ success: true, message: "Berhasil menambahkan diskusi umum", notif: dataNotif }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/module/discussion_general/index.ts b/src/module/discussion_general/index.ts new file mode 100644 index 0000000..e9e427b --- /dev/null +++ b/src/module/discussion_general/index.ts @@ -0,0 +1,7 @@ +import FormCreateDiscussionGeneral from "./ui/create_discussion"; +import ListDiscussionGeneral from "./ui/list_discussion"; +import NavbarDiscussionGeneral from "./ui/navbar_discussion"; + +export { ListDiscussionGeneral } +export { NavbarDiscussionGeneral } +export { FormCreateDiscussionGeneral } \ No newline at end of file diff --git a/src/module/discussion_general/lib/api_discussion_general.ts b/src/module/discussion_general/lib/api_discussion_general.ts new file mode 100644 index 0000000..6542402 --- /dev/null +++ b/src/module/discussion_general/lib/api_discussion_general.ts @@ -0,0 +1,4 @@ +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); +} \ No newline at end of file diff --git a/src/module/discussion_general/ui/create_discussion.tsx b/src/module/discussion_general/ui/create_discussion.tsx new file mode 100644 index 0000000..32d29a3 --- /dev/null +++ b/src/module/discussion_general/ui/create_discussion.tsx @@ -0,0 +1,140 @@ +'use client' +import { keyWibu, TEMA } from "@/module/_global"; +import LayoutModal from "@/module/_global/layout/layout_modal"; +import { funGetProfileByCookies } from "@/module/user/profile/lib/api_profile"; +import { useHookstate } from "@hookstate/core"; +import { Avatar, Box, Button, Grid, rem, Textarea } from "@mantine/core"; +import { useShallowEffect } from "@mantine/hooks"; +import { useParams, useRouter } from "next/navigation"; +import { useState } from "react"; +import toast from "react-hot-toast"; +import { useWibuRealtime } from "wibu-realtime"; + +export default function FormCreateDiscussionGeneral() { + const [isValModal, setValModal] = useState(false) + const [loadingModal, setLoadingModal] = useState(false) + const router = useRouter() + const param = useParams<{ id: string, detail: string }>() + const [loading, setLoading] = useState(true) + const [img, setIMG] = useState() + const tema = useHookstate(TEMA) + const [touched, setTouched] = useState({ + desc: false, + }); + const [isData, setData] = useState({ + desc: "", + idDivision: param.id + }) + const [data, setDataRealtime] = useWibuRealtime({ + WIBU_REALTIME_TOKEN: keyWibu, + project: "sdm" + }) + + async function getData() { + try { + setLoading(true) + const res = await funGetProfileByCookies() + setIMG(`https://wibu-storage.wibudev.com/api/files/${res.data.img}`) + setLoading(false) + } catch (error) { + console.error(error); + } finally { + setLoading(false) + } + } + + useShallowEffect(() => { + getData() + }, []) + + // async function createDiscussion(val: boolean) { + // try { + // if (val) { + // setLoadingModal(true) + // const response = await funCreateDiscussion({ + // desc: isData.desc, + // idDivision: id + // }) + + // if (response.success) { + // setDataRealtime(response.notif) + // toast.success(response.message) + // router.push(`/division/${param.id}/discussion/`) + // } else { + // toast.error(response.message) + // } + // } + // } catch (error) { + // console.error(error); + // toast.error("Gagal menambahkan diskusi, coba lagi nanti"); + // } finally { + // setLoadingModal(false) + // setValModal(false) + // } + // } + + + return ( + + + + + + + + +