diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index a45f6eb..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,17 +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
- dateStart DateTime @db.Date
- dateEnd DateTime @db.Date
- isActive Boolean @default(true)
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ 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
}
model ProjectComment {
@@ -303,6 +309,7 @@ model DivisionProjectTask {
title String
desc String? @db.Text
status Int @default(0) // 0 = todo, 1 = done
+ notifikasi Boolean @default(false)
dateStart DateTime @db.Date
dateEnd DateTime @db.Date
isActive Boolean @default(true)
@@ -519,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]/add-member/page.tsx b/src/app/(application)/discussion/[id]/add-member/page.tsx
new file mode 100644
index 0000000..7f96823
--- /dev/null
+++ b/src/app/(application)/discussion/[id]/add-member/page.tsx
@@ -0,0 +1,9 @@
+import { AddMemberDiscussionGeneral } from "@/module/discussion_general";
+
+export default function Page() {
+ return (
+ <>
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/app/(application)/discussion/[id]/edit/page.tsx b/src/app/(application)/discussion/[id]/edit/page.tsx
new file mode 100644
index 0000000..7164db5
--- /dev/null
+++ b/src/app/(application)/discussion/[id]/edit/page.tsx
@@ -0,0 +1,14 @@
+import { LayoutNavbarNew } from "@/module/_global";
+import { FormEditDiscussionGeneral } from "@/module/discussion_general";
+import { Box } from "@mantine/core";
+
+export default function Page() {
+ return (
+ <>
+
+ >} />
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/app/(application)/discussion/[id]/member/page.tsx b/src/app/(application)/discussion/[id]/member/page.tsx
new file mode 100644
index 0000000..bf272ef
--- /dev/null
+++ b/src/app/(application)/discussion/[id]/member/page.tsx
@@ -0,0 +1,9 @@
+import { MemberDiscussionGeneral } from "@/module/discussion_general";
+
+export default function Page() {
+ return (
+ <>
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/src/app/(application)/discussion/[id]/page.tsx b/src/app/(application)/discussion/[id]/page.tsx
new file mode 100644
index 0000000..d179343
--- /dev/null
+++ b/src/app/(application)/discussion/[id]/page.tsx
@@ -0,0 +1,9 @@
+import { DetailDiscussionGeneral } from "@/module/discussion_general";
+
+export default function Page() {
+ return (
+ <>
+
+ >
+ )
+}
\ 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..ac26ec4
--- /dev/null
+++ b/src/app/(application)/discussion/create/page.tsx
@@ -0,0 +1,9 @@
+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/calender/route.ts b/src/app/api/calender/route.ts
index 37cbc76..320d328 100644
--- a/src/app/api/calender/route.ts
+++ b/src/app/api/calender/route.ts
@@ -193,4 +193,44 @@ export async function POST(request: Request) {
console.error(error);
return NextResponse.json({ success: false, message: "Gagal membuat acara kalender, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });
}
+}
+
+
+
+// CEK TGL AVAILABLE
+export async function PUT(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 { idDivision, title, desc, timeStart, timeEnd, dateStart, dateEnd, repeatEventTyper, member, linkMeet, repeatValue } = (await request.json());
+ const division = await prisma.division.findUnique({
+ where: {
+ id: idDivision
+ }
+ })
+
+ const cek = await prisma.divisionCalendarReminder.count({
+ where: {
+ isActive: true,
+ dateStart: new Date(dateStart),
+ Division: {
+ idGroup: division?.idGroup
+ }
+ }
+ })
+
+ if (cek > 0) {
+ return NextResponse.json({ success: false, message: "Tidak dapat membuat acara kalender, acara kalender sudah ada pada tanggal tersebut" }, { status: 400 });
+ } else {
+ return NextResponse.json({ success: true, message: "Berhasil membuat acara kalender" }, { status: 200 });
+ }
+
+
+ } catch (error) {
+ console.error(error)
+ return NextResponse.json({ success: false, message: "Gagal membuat acara kalender, coba lagi nanti (error: 500)" }, { status: 404 });
+ }
}
\ No newline at end of file
diff --git a/src/app/api/discussion-general/[id]/comment/route.ts b/src/app/api/discussion-general/[id]/comment/route.ts
new file mode 100644
index 0000000..37edf90
--- /dev/null
+++ b/src/app/api/discussion-general/[id]/comment/route.ts
@@ -0,0 +1,46 @@
+import { prisma } from "@/module/_global";
+import { funGetUserByCookies } from "@/module/auth";
+import { createLogUser } from "@/module/user";
+import { NextResponse } from "next/server";
+
+
+// KIRIM KOMENTAR DISKUSI UMUM
+export async function POST(request: Request, context: { params: { id: string } }) {
+ try {
+ const { id } = context.params
+ const { desc } = (await request.json());
+
+ const user = await funGetUserByCookies()
+ if (user.id == undefined) {
+ return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
+ }
+
+ const cek = await prisma.discussion.count({
+ where: {
+ id,
+ isActive: true
+ }
+ })
+
+ if (cek == 0) {
+ return NextResponse.json({ success: false, message: "Gagal menambahkan komentar, data tidak ditemukan" }, { status: 404 });
+ }
+
+
+ const data = await prisma.discussionComment.create({
+ data: {
+ comment: desc,
+ idDiscussion: id,
+ idUser: user.id
+ }
+ })
+
+ // create log user
+ const log = await createLogUser({ act: 'CREATE', desc: 'User menambah komentar pada diskusi umum', table: 'discussionComment', data: data.id })
+ return NextResponse.json({ success: true, message: "Berhasil menambah komentar" }, { status: 200 });
+
+ } catch (error) {
+ console.error(error)
+ return NextResponse.json({ success: false, message: "Gagal menambahkan komentar, coba lagi nanti (error: 500)" })
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/discussion-general/[id]/member/route.ts b/src/app/api/discussion-general/[id]/member/route.ts
new file mode 100644
index 0000000..806923d
--- /dev/null
+++ b/src/app/api/discussion-general/[id]/member/route.ts
@@ -0,0 +1,92 @@
+import { prisma } from "@/module/_global";
+import { funGetUserByCookies } from "@/module/auth";
+import { createLogUser } from "@/module/user";
+import _ from "lodash";
+import { NextResponse } from "next/server";
+
+
+// ADD MEMBER DISCUSSION GENERAL
+export async function POST(request: Request, context: { params: { id: string } }) {
+ try {
+ const user = await funGetUserByCookies()
+ if (user.id == undefined) {
+ return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
+ }
+
+ const { id } = context.params
+ const { member } = (await request.json())
+
+ const cek = await prisma.discussion.count({
+ where: {
+ id,
+ isActive: true
+ }
+ })
+
+ if (cek == 0) {
+ return NextResponse.json({ success: false, message: "Gagal menambahkan anggota, data tidak ditemukan" }, { status: 404 });
+ }
+
+ if (member.length > 0) {
+ const dataMember = member.map((v: any) => ({
+ ..._.omit(v, ["idUser", "name", "img"]),
+ idDiscussion: id,
+ idUser: v.idUser
+ }))
+
+ const insertMember = await prisma.discussionMember.createMany({
+ data: dataMember
+ })
+ }
+
+ // create log user
+ const log = await createLogUser({ act: 'CREATE', desc: 'User menambah anggota diskusi umum', table: 'discussion', data: String(id) })
+ return NextResponse.json({ success: true, message: "Berhasil menambahkan anggota diskusi umum" }, { status: 200 });
+
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({ success: false, message: "Gagal menambahkan anggota, coba lagi nanti (error : 500)" }, { status: 500 });
+ }
+}
+
+
+// MENGELUARKAN ANGGOTA
+export async function DELETE(request: Request, context: { params: { id: string } }) {
+ try {
+ const user = await funGetUserByCookies()
+ if (user.id == undefined) {
+ return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
+ }
+
+ const { id } = context.params
+ const { idUser } = (await request.json());
+
+
+ const cek = await prisma.discussion.count({
+ where: {
+ id,
+ isActive: true
+ }
+ })
+
+ if (cek == 0) {
+ return NextResponse.json({ success: false, message: "Gagal, data tidak ditemukan" }, { status: 404 });
+ }
+
+ const deleteMember = await prisma.discussionMember.deleteMany({
+ where: {
+ idDiscussion: id,
+ idUser
+ }
+ })
+
+ // create log user
+ const log = await createLogUser({ act: 'DELETE', desc: 'User mengeluarkan anggota diskusi umum', table: 'discussion', data: String(id) })
+
+ return NextResponse.json({ success: true, message: "Berhasil mengeluarkan anggota diskusi umum" }, { status: 200 });
+
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({ success: false, message: "Gagal mengeluarkan anggota, coba lagi nanti (error : 500)" }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/discussion-general/[id]/route.ts b/src/app/api/discussion-general/[id]/route.ts
new file mode 100644
index 0000000..899097b
--- /dev/null
+++ b/src/app/api/discussion-general/[id]/route.ts
@@ -0,0 +1,263 @@
+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 ONE DETAIL DISKUSI UMUM
+export async function GET(request: Request, context: { params: { id: string } }) {
+ try {
+ let dataFix
+ const { id } = context.params
+ const { searchParams } = new URL(request.url);
+ const kategori = searchParams.get("cat");
+
+ const user = await funGetUserByCookies()
+ if (user.id == undefined) {
+ return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
+ }
+
+ const cek = await prisma.discussion.count({
+ where: {
+ id,
+ isActive: true
+ }
+ })
+
+ if (cek == 0) {
+ return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, { status: 404 });
+ }
+
+ if (kategori == "detail") {
+ const data = await prisma.discussion.findUnique({
+ where: {
+ id,
+ isActive: true
+ },
+ select: {
+ id: true,
+ title: true,
+ idGroup: true,
+ desc: true,
+ status: true,
+ createdAt: true,
+ }
+ })
+
+ dataFix = {
+ id: data?.id,
+ idGroup:data?.idGroup,
+ title: data?.title,
+ desc: data?.desc,
+ status: data?.status,
+ createdAt: moment(data?.createdAt).format("ll"),
+ }
+
+ } else if (kategori == "komentar") {
+ const data = await prisma.discussionComment.findMany({
+ where: {
+ idDiscussion: id,
+ isActive: true
+ },
+ select: {
+ id: true,
+ comment: true,
+ createdAt: true,
+ idUser: true,
+ User: {
+ select: {
+ name: true,
+ img: true
+ }
+ }
+ }
+ })
+
+ dataFix = data.map((v: any) => ({
+ ..._.omit(v, ["createdAt", "User",]),
+ createdAt: moment(v.createdAt).format("lll"),
+ username: v.User.name,
+ img: v.User.img
+ }))
+
+ } else if (kategori == "anggota") {
+ const data = await prisma.discussionMember.findMany({
+ where: {
+ idDiscussion: id,
+ isActive: true
+ },
+ select: {
+ idUser: true,
+ User: {
+ select: {
+ name: true,
+ img: true
+ }
+ }
+ }
+ })
+
+ dataFix = data.map((v: any) => ({
+ ..._.omit(v, ["User",]),
+ name: v.User.name,
+ img: v.User.img
+ }))
+ } else if (kategori == "cek-anggota") {
+ const cek = await prisma.discussionMember.count({
+ where: {
+ idDiscussion: id,
+ isActive: true,
+ idUser: user.id
+ }
+ })
+
+ if (cek > 0) {
+ dataFix = true
+ } else {
+ dataFix = false
+ }
+ }
+
+
+ return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: dataFix }, { 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 });
+ }
+}
+
+
+// OPEN OR CLOSE DISKUSI UMUM
+export async function POST(request: Request, context: { params: { id: string } }) {
+ try {
+ const user = await funGetUserByCookies()
+ if (user.id == undefined) {
+ return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
+ }
+ const { id } = context.params
+ const { status } = (await request.json());
+ let newStatus;
+ if (status === 1) {
+ newStatus = 2;
+ } else if (status === 2) {
+ newStatus = 1;
+ } else {
+ return NextResponse.json({ success: false, message: "Invalid status" }, { status: 400 });
+ }
+
+ const data = await prisma.discussion.count({
+ where: {
+ id: id
+ },
+ });
+
+ if (data == 0) {
+ return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, { status: 404 });
+ }
+
+ const result = await prisma.discussion.update({
+ where: {
+ id
+ },
+ data: {
+ status: newStatus
+ }
+ });
+
+ // create log user
+ const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate status diskusi umum', table: 'discussion', data: id })
+
+ return NextResponse.json({ success: true, message: "Berhasil mengedit diskusi umum" }, { status: 200 });
+
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({ success: false, message: "Gagal mengedit diskusi umum, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });
+ }
+}
+
+
+// DELETE DISCUSSION
+export async function DELETE(request: Request, context: { params: { id: string } }) {
+ try {
+ const user = await funGetUserByCookies()
+ if (user.id == undefined) {
+ return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
+ }
+ const { id } = context.params
+
+ const cek = await prisma.discussion.count({
+ where: {
+ id: id
+ },
+ });
+
+ if (cek == 0) {
+ return NextResponse.json({ success: false, message: "Gagal menghapus diskusi umum, data tidak ditemukan" }, { status: 404 });
+ }
+
+
+ const data = await prisma.discussion.update({
+ where: {
+ id
+ },
+ data: {
+ isActive: false
+ }
+ });
+
+
+ // create log user
+ const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus data diskusi umum', table: 'disscussion', data: id })
+ return NextResponse.json({ success: true, message: "Berhasil menghapus diskusi umum", user: user.id }, { status: 200 });
+
+
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({ success: false, message: "Gagal menghapus diskusi umum, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });
+ }
+}
+
+
+// EDIT DISCUSSION
+export async function PUT(request: Request, context: { params: { id: string } }) {
+ try {
+ const user = await funGetUserByCookies()
+ if (user.id == undefined) {
+ return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 });
+ }
+ const { id } = context.params
+ const { title, desc } = (await request.json());
+
+ const data = await prisma.discussion.count({
+ where: {
+ id: id
+ },
+ });
+
+ if (data == 0) {
+ return NextResponse.json({ success: false, message: "Gagal mengedit diskusi umum, data tidak ditemukan" }, { status: 404 });
+ }
+
+ const update = await prisma.discussion.update({
+ where: {
+ id
+ },
+ data: {
+ desc,
+ title
+ }
+ });
+
+ // create log user
+ const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data diskusi umum', table: 'discussion', data: id })
+ return NextResponse.json({ success: true, message: "Berhasil mengedit diskusi umum" }, { status: 200 });
+
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({ success: false, message: "Gagal mengedit diskusi umum, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/discussion-general/route.ts b/src/app/api/discussion-general/route.ts
new file mode 100644
index 0000000..5061216
--- /dev/null
+++ b/src/app/api/discussion-general/route.ts
@@ -0,0 +1,210 @@
+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";
+import "moment/locale/id";
+
+
+
+// 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({
+ skip: dataSkip,
+ take: 10,
+ where: {
+ isActive: true,
+ idVillage: String(villageId),
+ idGroup: grup,
+ title: {
+ contains: (search == undefined || search == "null") ? "" : search,
+ mode: "insensitive"
+ },
+ },
+ orderBy: [
+ {
+ status: 'desc'
+ },
+ {
+ createdAt: 'desc'
+ }
+ ],
+
+ select: {
+ id: true,
+ title: true,
+ desc: true,
+ status: true,
+ createdAt: true,
+ DiscussionComment: {
+ select: {
+ id: true,
+ }
+ }
+ }
+ });
+
+ const fixData = data.map((v: any) => ({
+ ..._.omit(v, ["DiscussionComment", "createdAt"]),
+ 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/app/api/home/route.ts b/src/app/api/home/route.ts
index f900c8d..d0ef436 100644
--- a/src/app/api/home/route.ts
+++ b/src/app/api/home/route.ts
@@ -1,3 +1,4 @@
+import { DivisionProject } from './../../../../node_modules/.prisma/client/index.d';
import { prisma } from "@/module/_global";
import { funGetUserByCookies } from "@/module/auth";
import _, { ceil } from "lodash";
@@ -445,6 +446,134 @@ export async function GET(request: Request) {
totalNotif: total,
village: desa?.name
}
+ } else if (kategori == "check-late-project") {
+ const supadmin = await prisma.user.findFirst({
+ where: {
+ idUserRole: "supadmin",
+ idVillage: idVillage
+ }
+ })
+
+ const dataUmum = await prisma.projectTask.findMany({
+ where: {
+ Project: {
+ idVillage: idVillage,
+ isActive: true,
+ status: {
+ not: {
+ in: [2, 3]
+ }
+ }
+ },
+ notifikasi: false,
+ isActive: true,
+ status: 0,
+ dateEnd: {
+ lt: new Date()
+ }
+ },
+ select: {
+ id: true,
+ title: true,
+ dateEnd: true,
+ Project: {
+ select: {
+ id: true,
+ },
+ }
+ }
+ })
+
+
+ for (let index = 0; index < dataUmum.length; index++) {
+ await prisma.projectTask.update({
+ where: {
+ id: dataUmum[index].id
+ },
+ data: {
+ notifikasi: true
+ }
+ })
+ }
+
+ const pertama = dataUmum.map((v: any) => ({
+ ..._.omit(v, ["Project", "title", "id", "dateEnd"]),
+ idUserTo: String(supadmin?.id),
+ idUserFrom: String(user.id),
+ category: 'project',
+ idContent: v.Project.id,
+ title: `Tugas ${v.title} Telah Melewati Batas Waktu`,
+ desc: `Tugas dengan deadline ${moment(v.dateEnd).format('DD-MM-yyyy')} telah berakhir. Silakan segera melakukan tindakan yang diperlukan.`
+ }))
+
+ const insertNotif = await prisma.notifications.createMany({
+ data: pertama
+ })
+
+ const dataDivisi = await prisma.divisionProjectTask.findMany({
+ where: {
+ Division: {
+ idVillage: idVillage,
+ isActive: true
+ },
+ DivisionProject: {
+ isActive: true,
+ status: {
+ not: {
+ in: [2, 3]
+ }
+ }
+ },
+ notifikasi: false,
+ isActive: true,
+ status: 0,
+ dateEnd: {
+ lt: new Date()
+ }
+ },
+ select: {
+ id: true,
+ title: true,
+ dateEnd: true,
+ DivisionProject: {
+ select: {
+ id: true,
+ idDivision: true
+ },
+ }
+ }
+ })
+
+ for (let index = 0; index < dataDivisi.length; index++) {
+ await prisma.divisionProjectTask.update({
+ where: {
+ id: dataDivisi[index].id
+ },
+ data: {
+ notifikasi: true
+ }
+ })
+ }
+
+ const kedua = dataDivisi.map((v: any) => ({
+ ..._.omit(v, ["DivisionProject", "title", "id", "dateEnd"]),
+ idUserTo: String(supadmin?.id),
+ idUserFrom: String(user.id),
+ category: 'division/' + v.DivisionProject.idDivision + '/task',
+ idContent: v.DivisionProject.id,
+ title: `Tugas ${v.title} Telah Melewati Batas Waktu`,
+ desc: `Tugas dengan deadline ${moment(v.dateEnd).format('DD-MM-yyyy')} telah berakhir. Silakan segera melakukan tindakan yang diperlukan.`
+ }))
+
+ const insertNotif2 = await prisma.notifications.createMany({
+ data: kedua
+ })
+
+
+ const merge = [...pertama, ...kedua]
+
+ allData = merge
+
}
return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: allData }, { status: 200 });
diff --git a/src/app/api/version-app/route.ts b/src/app/api/version-app/route.ts
index 2343ad3..1741163 100644
--- a/src/app/api/version-app/route.ts
+++ b/src/app/api/version-app/route.ts
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
export async function GET(request: Request) {
try {
- return NextResponse.json({ success: true, version: "0.2.9", tahap: "beta" }, { status: 200 });
+ return NextResponse.json({ success: true, version: "1.0.0", tahap: "beta" }, { status: 200 });
} catch (error) {
console.error(error);
return NextResponse.json({ success: false, version: "Gagal mendapatkan version, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });
diff --git a/src/module/calender/lib/api_calender.ts b/src/module/calender/lib/api_calender.ts
index 92786d1..8166de4 100644
--- a/src/module/calender/lib/api_calender.ts
+++ b/src/module/calender/lib/api_calender.ts
@@ -19,6 +19,18 @@ export const funGetOneCalenderByIdCalendar = async (path: string) => {
return await response.json().catch(() => null);
}
+
+export const funCheckCalender = async (data: IFormCreateCalender) => {
+ const response = await fetch("/api/calender", {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+ return await response.json().catch(() => null);
+}
+
export const funCreateCalender = async (data: IFormCreateCalender) => {
const response = await fetch("/api/calender", {
method: "POST",
@@ -67,11 +79,11 @@ export const funAddMemberCalender = async (path: string, data: IFormMemberCalend
export const funDeleteMemberCalender = async (path: string, data: { idUser: string }) => {
const response = await fetch(`/api/calender/${path}/member`, {
- method: "DELETE",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(data),
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
});
return await response.json().catch(() => null);
- };
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/module/calender/ui/create_calender_division_caleder.tsx b/src/module/calender/ui/create_calender_division_caleder.tsx
index fd3f4d0..8730faa 100644
--- a/src/module/calender/ui/create_calender_division_caleder.tsx
+++ b/src/module/calender/ui/create_calender_division_caleder.tsx
@@ -11,7 +11,7 @@ import { useState } from 'react';
import toast from 'react-hot-toast';
import { IoIosArrowDropright } from 'react-icons/io';
import { useWibuRealtime } from 'wibu-realtime';
-import { funCreateCalender } from '../lib/api_calender';
+import { funCheckCalender, funCreateCalender } from '../lib/api_calender';
import { IFormMemberCalender } from '../lib/type_calender';
import { globalCalender } from '../lib/val_calender';
import CreateUserCalender from './create_user_calender';
@@ -20,7 +20,9 @@ export default function CreateCalenderDivisionCaleder() {
const [value, setValue] = useState(null);
const router = useRouter()
const [isModal, setModal] = useState(false)
+ const [isModalKonfirmasiTglSama, setModalKonfirmasiTglSama] = useState(false)
const [loadingModal, setLoadingModal] = useState(false)
+ const [loadingModalKonfirmasiTglSama, setLoadingModalKonfirmasiTglSama] = useState(false)
const member = useHookstate(globalCalender)
const memberValue = member.get() as IFormMemberCalender[]
const [openMember, setOpenMember] = useState(false)
@@ -53,10 +55,40 @@ export default function CreateCalenderDivisionCaleder() {
project: "sdm"
})
-
- async function onSubmit(val: boolean) {
+ async function onCheckDate() {
try {
setLoadingModal(true)
+ const response = await funCheckCalender({
+ idDivision: param.id,
+ title: isData.title,
+ dateStart: isData.dateStart,
+ timeStart: isData.timeStart,
+ timeEnd: isData.timeEnd,
+ linkMeet: isData.linkMeet,
+ repeatEventTyper: isData.repeatEventTyper,
+ desc: isData.desc,
+ repeatValue: isData.repeatValue,
+ member: memberValue
+ })
+
+ if (response.success) {
+ onSubmit()
+ } else {
+ setModalKonfirmasiTglSama(true)
+ }
+ } catch (error) {
+ console.error(error)
+ toast.error("Gagal menambahkan acara, coba lagi nanti");
+ } finally {
+ setLoadingModal(false)
+ }
+ }
+
+
+ async function onSubmit() {
+ try {
+ setLoadingModal(true)
+ setLoadingModalKonfirmasiTglSama(true)
const response = await funCreateCalender({
idDivision: param.id,
title: isData.title,
@@ -76,20 +108,20 @@ export default function CreateCalenderDivisionCaleder() {
division: param.id,
date: isData.dateStart
}])
- setModal(false)
router.push(`/division/${param.id}/calender`)
toast.success(response.message)
member.set([])
} else {
toast.error(response.message)
- setModal(false)
}
} catch (error) {
console.error(error)
- toast.error("Gagal menambahkan pengumuman, coba lagi nanti");
+ toast.error("Gagal menambahkan acara, coba lagi nanti");
} finally {
setModal(false)
+ setModalKonfirmasiTglSama(false)
setLoadingModal(false)
+ setLoadingModalKonfirmasiTglSama(false)
}
}
@@ -381,8 +413,7 @@ export default function CreateCalenderDivisionCaleder() {
member.get().map((v: any, i: any) => {
return (
-
+
@@ -430,11 +461,27 @@ export default function CreateCalenderDivisionCaleder() {
description="Apakah Anda yakin ingin menambahkan data?"
onYes={(val) => {
if (val) {
- onSubmit(val)
+ onCheckDate()
} else {
setModal(false)
}
}} />
+
+
+ {
+ setModalKonfirmasiTglSama(false)
+ setModal(false)
+ }}
+ description="Sudah ada acara pada tanggal yang sama. Apakah Anda yakin ingin menambahkan data?"
+ onYes={(val) => {
+ if (val) {
+ onSubmit()
+ } else {
+ setModalKonfirmasiTglSama(false)
+ setModal(false)
+ }
+ }} />
);
}
\ No newline at end of file
diff --git a/src/module/discussion/ui/detail_discussion.tsx b/src/module/discussion/ui/detail_discussion.tsx
index 5731dfa..163da33 100644
--- a/src/module/discussion/ui/detail_discussion.tsx
+++ b/src/module/discussion/ui/detail_discussion.tsx
@@ -275,7 +275,7 @@ export default function DetailDiscussion({ id, idDivision }: { id: string, idDiv
- {moment(v.createdAt).format("ll")}
+ {moment(v.createdAt).format("lll").replace('pukul', '')}
diff --git a/src/module/discussion_general/index.ts b/src/module/discussion_general/index.ts
new file mode 100644
index 0000000..bb5e452
--- /dev/null
+++ b/src/module/discussion_general/index.ts
@@ -0,0 +1,15 @@
+import AddMemberDiscussionGeneral from "./ui/add_member";
+import FormCreateDiscussionGeneral from "./ui/create_discussion";
+import DetailDiscussionGeneral from "./ui/detail_discussion_general";
+import FormEditDiscussionGeneral from "./ui/edit_discussion_general";
+import ListDiscussionGeneral from "./ui/list_discussion";
+import MemberDiscussionGeneral from "./ui/member_discussion_general";
+import NavbarDiscussionGeneral from "./ui/navbar_discussion";
+
+export { ListDiscussionGeneral }
+export { NavbarDiscussionGeneral }
+export { FormCreateDiscussionGeneral }
+export { DetailDiscussionGeneral }
+export { MemberDiscussionGeneral }
+export { FormEditDiscussionGeneral }
+export { AddMemberDiscussionGeneral }
\ 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..1d7a9bd
--- /dev/null
+++ b/src/module/discussion_general/lib/api_discussion_general.ts
@@ -0,0 +1,91 @@
+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);
+}
+
+export const funGetOneDiscussionGeneral = async (id: string, path: string) => {
+ const response = await fetch(`/api/discussion-general/${id}${(path) ? path : ''}`, { next: { tags: ['discussion-general'] } });
+ return await response.json().catch(() => null);
+}
+
+export const funCreateComentDiscussionGeneral = async (path: string, data: { desc: string }) => {
+ const response = await fetch(`/api/discussion-general/${path}/comment`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+ return await response.json().catch(() => null);
+}
+
+
+
+export const funEditStatusDiscussionGeneral = async (path: string, data: { status: number }) => {
+ const response = await fetch(`/api/discussion-general/${path}`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+ return await response.json().catch(() => null);
+}
+
+
+export const funEditDiscussionGeneral = async (path: string, data: { title: string, desc: string }) => {
+ const response = await fetch(`/api/discussion-general/${path}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(data),
+ });
+ return await response.json().catch(() => null);
+}
+
+
+export const funDeleteDiscussionGeneral = async (path: string) => {
+ const response = await fetch(`/api/discussion-general/${path}`, {
+ method: "DELETE",
+ });
+ return await response.json().catch(() => null);
+}
+
+export const funAddMemberDiscussionGeneral = async (path: string, data: { member: IFormMemberDisscussionGeneral[] }) => {
+ const response = await fetch(`/api/discussion-general/${path}/member`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(data)
+ })
+
+ return await response.json().catch(() => null)
+}
+
+
+export const funDelMemberDiscussionGeneral = async (path: string, data: { idUser: string }) => {
+ const response = await fetch(`/api/discussion-general/${path}/member`, {
+ method: "DELETE",
+ 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..62be53f
--- /dev/null
+++ b/src/module/discussion_general/lib/type_discussion_general.ts
@@ -0,0 +1,22 @@
+export interface IFormMemberDisscussionGeneral {
+ idUser: string,
+ name: string,
+ img: string
+ }
+
+ export interface IDetailDiscussionGeneral{
+ id: string
+ title: string
+ desc: string
+ status: number
+ createdAt: string
+ }
+
+ export interface IComentsDisscussionGeneral{
+ id: string
+ comment: string
+ createdAt: string
+ idUser: string
+ img: string
+ username: string
+}
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..321771d
--- /dev/null
+++ b/src/module/discussion_general/lib/val_discussion_general.ts
@@ -0,0 +1,5 @@
+import { hookstate } from "@hookstate/core";
+import { IFormMemberDisscussionGeneral } from "./type_discussion_general";
+
+export const globalMemberDiscussionGeneral = hookstate([]);
+export const globalRefreshDiscussionGeneral = hookstate(false);
\ No newline at end of file
diff --git a/src/module/discussion_general/ui/add_member.tsx b/src/module/discussion_general/ui/add_member.tsx
new file mode 100644
index 0000000..ab2a6b9
--- /dev/null
+++ b/src/module/discussion_general/ui/add_member.tsx
@@ -0,0 +1,293 @@
+"use client"
+import { LayoutNavbarNew, SkeletonList, TEMA } from '@/module/_global';
+import LayoutModal from '@/module/_global/layout/layout_modal';
+import { funGetUserByCookies } from '@/module/auth';
+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 { useParams, useRouter } from 'next/navigation';
+import { useState } from 'react';
+import toast from 'react-hot-toast';
+import { FaCheck } from 'react-icons/fa6';
+import { HiMagnifyingGlass } from 'react-icons/hi2';
+import { IoArrowBackOutline, IoClose } from 'react-icons/io5';
+import { funAddMemberDiscussionGeneral, funGetOneDiscussionGeneral } from '../lib/api_discussion_general';
+import { IFormMemberDisscussionGeneral } from '../lib/type_discussion_general';
+
+
+
+export default function AddMemberDiscussionGeneral() {
+ const router = useRouter()
+ const [selectedFiles, setSelectedFiles] = useState([]);
+ const [dataMember, setDataMember] = useState([])
+ const [memberDb, setMemberDb] = useState([])
+ const [group, setGroup] = useState("")
+ const [isOpen, setOpen] = useState(false)
+ const param = useParams<{ id: string }>()
+ const [loading, setLoading] = useState(true)
+ const [onClickSearch, setOnClickSearch] = useState(false)
+ const tema = useHookstate(TEMA)
+ const isMobile2 = useMediaQuery("(max-width: 438px)")
+ const [loadingModal, setLoadingModal] = useState(false)
+
+ 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 }])
+ }
+ };
+
+ function handleXMember(id: number) {
+ setSelectedFiles(selectedFiles.filter((i: any) => i.idUser != id))
+ }
+
+
+ async function loadMember(group: string, search: string) {
+ try {
+ setLoading(true)
+ const res = await funGetAllmember('?active=true&group=' + group + '&search=' + search);
+ const user = await funGetUserByCookies();
+
+ if (res.success) {
+ // const dariUserLogin = res.data.filter((i: any) => i.id != user.id)
+ // const fixListUser = dariUserLogin.filter((i: any) => i.idUserRole == 'coadmin' || i.idUserRole == 'user')
+ const fixListUser = res.data.filter((i: any) => i.idUserRole != 'supadmin')
+ setDataMember(fixListUser)
+ } else {
+ toast.error(res.message)
+ }
+ } catch (error) {
+ console.error(error)
+ toast.error("Gagal memuat data, coba lagi nanti")
+ } finally {
+ setLoading(false)
+ }
+
+ }
+
+ async function loadFirst() {
+ const respon = await funGetOneDiscussionGeneral(param.id, '?cat=detail');
+ const respon2 = await funGetOneDiscussionGeneral(param.id, '?cat=anggota');
+ if (respon.success) {
+ setGroup(respon.data.idGroup)
+ setMemberDb(respon2.data)
+ loadMember(respon.data.idGroup, "")
+ } else {
+ toast.error(respon.message);
+ }
+ }
+
+ async function addMember() {
+ try {
+ setLoadingModal(true)
+ const res = await funAddMemberDiscussionGeneral(param.id, { member: selectedFiles })
+ if (res.success) {
+ toast.success(res.message)
+ router.push("/discussion/" + param.id + "/member")
+ } else {
+ toast.error(res.message)
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error("Gagal menambahkan anggota diskusi umum, coba lagi nanti");
+ } finally {
+ setLoadingModal(false)
+ setOpen(false)
+ }
+ }
+
+ function onCheck() {
+ if (selectedFiles.length == 0) {
+ return toast.error("Error! silahkan pilih anggota")
+ }
+ setOpen(true)
+ }
+
+
+ useShallowEffect(() => {
+ loadFirst()
+ }, []);
+
+ const handleSearchClick = () => {
+ setOnClickSearch(true);
+ };
+
+ const handleClose = () => {
+ setOnClickSearch(false);
+ };
+
+ return (
+
+
+
+ }
+ />
+ {onClickSearch
+ ? (
+
+
+
+
+
+
+
+
+ loadMember(group, e.target.value)}
+ />
+
+
+
+ )
+ : null
+ }
+
+ {selectedFiles.length > 0 ? (
+
+ {selectedFiles.map((v: any, i: any) => {
+ return (
+
+ { handleXMember(v.idUser) }}
+ >
+
+ }>
+
+
+
+ {v.name}
+
+
+ )
+ })}
+
+ ) : (
+
+
+ Tidak ada anggota yang dipilih
+
+
+ )}
+
+
+ {loading ?
+ Array(8)
+ .fill(null)
+ .map((_, i) => (
+
+
+
+ ))
+ :
+ (dataMember.length === 0) ?
+
+ Tidak ada anggota
+
+ :
+
+ {dataMember.map((v: any, index: any) => {
+ const isSelected = selectedFiles.some((i: any) => i.idUser == dataMember[index].id)
+ const found = memberDb.some((i: any) => i.idUser == v.id)
+ return (
+ (!found) ? handleFileClick(index) : null}>
+
+
+
+
+
+
+
+ {v.name}
+ {(found) ? "sudah menjadi anggota divisi" : ""}
+
+ {isSelected ? : null}
+
+
+
+
+
+
+
+ )
+ })}
+
+ }
+
+
+
+
+ setOpen(false)}
+ description="Apakah Anda yakin ingin menambahkan anggota diskusi umum?"
+ onYes={(val) => {
+ if (val) {
+ addMember()
+ } else {
+ setOpen(false)
+ }
+ }} />
+
+ );
+}
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
new file mode 100644
index 0000000..bb3161e
--- /dev/null
+++ b/src/module/discussion_general/ui/create_discussion.tsx
@@ -0,0 +1,325 @@
+'use client'
+import { funGetAllGroup, IDataGroup } from "@/module/group";
+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 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')
+ if (loadGroup.success) {
+ setDataGroup(loadGroup.data);
+ } 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 (
+
+ >} />
+
+
+ {
+ (roleLogin.get() == "supadmin") && (
+
+
+ {
+ 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/detail_discussion_general.tsx b/src/module/discussion_general/ui/detail_discussion_general.tsx
new file mode 100644
index 0000000..5a64475
--- /dev/null
+++ b/src/module/discussion_general/ui/detail_discussion_general.tsx
@@ -0,0 +1,389 @@
+"use client"
+import { globalRole, keyWibu, LayoutDrawer, LayoutNavbarNew, TEMA } from "@/module/_global";
+import { useHookstate } from "@hookstate/core";
+import { ActionIcon, Avatar, Badge, Box, Center, Divider, Flex, Grid, Group, rem, Skeleton, Spoiler, Text, TextInput } from "@mantine/core";
+import { useMediaQuery, useShallowEffect } from "@mantine/hooks";
+import moment from "moment";
+import "moment/locale/id";
+import { useParams, useRouter } from "next/navigation";
+import { useState } from "react";
+import toast from "react-hot-toast";
+import { BiSolidCommentDetail } from "react-icons/bi";
+import { GrChatOption } from "react-icons/gr";
+import { HiMenu } from "react-icons/hi";
+import { VscSend } from "react-icons/vsc";
+import { useWibuRealtime } from "wibu-realtime";
+import { funCreateComentDiscussionGeneral, funGetOneDiscussionGeneral } from "../lib/api_discussion_general";
+import { IComentsDisscussionGeneral, IDetailDiscussionGeneral } from "../lib/type_discussion_general";
+import { globalRefreshDiscussionGeneral } from "../lib/val_discussion_general";
+import DrawerDetailDiscussionGeneral from "./drawer_detail_discussion_general";
+import { funGetUserByCookies } from "@/module/auth";
+
+export default function DetailDiscussionGeneral() {
+ const [isData, setData] = useState()
+ const [isDataKomentar, setDataKomentar] = useState([])
+ const [isComent, setIsComent] = useState("")
+ const param = useParams<{ id: string }>()
+ const [isLoad, setIsLoad] = useState(true)
+ const [loadingKomentar, setLoadingKomentar] = useState(true)
+ const refresh = useHookstate(globalRefreshDiscussionGeneral)
+ const roleLogin = useHookstate(globalRole)
+ const [memberDiscussion, setMemberDiscussion] = useState(false)
+ const tema = useHookstate(TEMA)
+ const router = useRouter()
+ const [isUser, setUser] = useState('')
+ const [openDrawer, setOpenDrawer] = useState(false)
+ const isMobile = useMediaQuery('(max-width: 369px)')
+ const isMobile2 = useMediaQuery("(max-width: 438px)")
+ const [dataRealTime, setDataRealtime] = useWibuRealtime({
+ WIBU_REALTIME_TOKEN: keyWibu,
+ project: "sdm"
+ })
+
+ const getData = async (loading: boolean) => {
+ try {
+ setIsLoad(loading)
+ const response = await funGetOneDiscussionGeneral(param.id, "?cat=detail")
+ const cekAnggota = await funGetOneDiscussionGeneral(param.id, "?cat=cek-anggota")
+ const userLogin = await funGetUserByCookies()
+ if (response.success) {
+ setData(response.data)
+ setMemberDiscussion(cekAnggota.data)
+ setUser(String(userLogin?.id))
+ } else {
+ toast.error(response.message)
+ }
+ } catch (error) {
+ console.error(error)
+ toast.error("Gagal mendapatkan data, coba lagi nanti")
+ } finally {
+ setIsLoad(false)
+ }
+ }
+
+ useShallowEffect(() => {
+ getData(true)
+ }, [refresh.get()])
+
+ useShallowEffect(() => {
+ getKomentar(true)
+ }, [])
+
+
+
+ useShallowEffect(() => {
+ if (dataRealTime && dataRealTime.some((i: any) => i.category == 'discussion-general-detail' && i.id == param.id)) {
+ getData(false)
+ }
+
+ if (dataRealTime && dataRealTime.some((i: any) => i.category == 'discussion-general-comment' && i.id == param.id)) {
+ getKomentar(false)
+ }
+
+ if (dataRealTime && dataRealTime.some((i: any) => i.category == 'discussion-general-delete' && i.id == param.id && i.user != isUser)) {
+ toast.error("Data telah di hapus, anda akan beralih ke halaman list diskusi umum")
+ setTimeout(() => {
+ router.push(`/discussion`)
+ }, 1000)
+ }
+ }, [dataRealTime])
+
+ async function getKomentar(loading: boolean) {
+ try {
+ setLoadingKomentar(loading)
+ const response = await funGetOneDiscussionGeneral(param.id, "?cat=komentar")
+ if (response.success) {
+ setDataKomentar(response.data)
+ } else {
+ toast.error(response.message)
+ }
+ } catch (error) {
+ console.error(error)
+ } finally {
+ setLoadingKomentar(false)
+ }
+ }
+
+ const sendComent = async () => {
+ try {
+ if (isComent.trim() == "") {
+ return toast.error("Masukkan Komentar Anda")
+ }
+ const response = await funCreateComentDiscussionGeneral(param.id, { desc: isComent })
+
+ if (response.success) {
+ setIsComent("")
+ setDataRealtime([{
+ category: "discussion-general-comment",
+ id: param.id,
+ }])
+ } else {
+ toast.error(response.message)
+ }
+ } catch (error) {
+ console.error(error)
+ }
+ }
+
+
+
+
+
+ return (
+
+ setOpenDrawer(true)} bg={tema.get().bgIcon} size="lg" radius="lg" aria-label="Settings">
+
+
+ }
+ />
+ setOpenDrawer(false)}>
+ setOpenDrawer(false)} id={param.id} status={Number(isData?.status)} />
+
+
+
+
+ {isLoad ?
+ Array(1)
+ .fill(null)
+ .map((_, i) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )) :
+ <>
+ {isDataKomentar?.length == 0 ?
+
+
+
+
+
+
+
+
+
+ {isData?.title}
+ {isData?.status === 1 ? "BUKA" : "TUTUP"}
+
+
+
+
+ {isData?.createdAt}
+
+
+
+
+
+
+ {isData?.desc}
+
+
+
+
+ {isDataKomentar?.length ?
+
+ {isDataKomentar?.length} Komentar
+ : ""}
+
+
+
+ :
+
+
+
+
+
+
+
+
+
+ {isData?.title}
+ {isData?.status === 1 ? "BUKA" : "TUTUP"}
+
+
+
+
+ {moment(isData?.createdAt).format('ll')}
+
+
+
+
+
+
+ {isData?.desc}
+
+
+
+
+ {isDataKomentar?.length ?
+
+ {isDataKomentar.length} Komentar
+ : ""}
+
+
+
+ }
+ >
+ }
+
+ {loadingKomentar ?
+ Array(2)
+ .fill(0)
+ .map((_, i) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )) :
+ isDataKomentar.map((v, i) => {
+ return (
+
+
+
+
+
+
+
+
+ {v.username}
+
+
+
+
+ {moment(v.createdAt).format('lll').replace('pukul', '')}
+
+
+
+
+
+ {v.comment}
+
+
+
+
+
+
+
+ );
+ })
+ }
+
+
+ {isLoad ?
+
+ :
+
+
+
+ {300 - isComent.length} karakter tersisa
+
+
+
+
+ setIsComent(e.target.value)}
+ value={isComent}
+ maxLength={300}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+ )
+}
+
diff --git a/src/module/discussion_general/ui/drawer_detail_discussion_general.tsx b/src/module/discussion_general/ui/drawer_detail_discussion_general.tsx
new file mode 100644
index 0000000..c5a656d
--- /dev/null
+++ b/src/module/discussion_general/ui/drawer_detail_discussion_general.tsx
@@ -0,0 +1,173 @@
+import { globalRole, keyWibu, TEMA } from "@/module/_global";
+import LayoutModal from "@/module/_global/layout/layout_modal";
+import { useHookstate } from "@hookstate/core";
+import { Box, Flex, SimpleGrid, Stack, Text } from "@mantine/core";
+import { useParams, useRouter } from "next/navigation";
+import { useState } from "react";
+import { BsTrash3 } from "react-icons/bs";
+import { FaCheck, FaPencil, FaUsers } from "react-icons/fa6";
+import { MdClose } from "react-icons/md";
+import { useWibuRealtime } from "wibu-realtime";
+import { funDeleteDiscussionGeneral, funEditStatusDiscussionGeneral } from "../lib/api_discussion_general";
+import { globalRefreshDiscussionGeneral } from "../lib/val_discussion_general";
+import toast from "react-hot-toast";
+
+export default function DrawerDetailDiscussionGeneral({ onSuccess, id, status }: { onSuccess: (val: boolean) => void, id: string, status: number }) {
+ const [isValModal, setValModal] = useState(false)
+ const [isValModalStatus, setValModalStatus] = useState(false)
+ const router = useRouter()
+ const param = useParams<{ id: string, detail: string }>()
+ const roleLogin = useHookstate(globalRole)
+ const refresh = useHookstate(globalRefreshDiscussionGeneral)
+ const tema = useHookstate(TEMA)
+ const [dataRealTime, setDataRealtime] = useWibuRealtime({
+ WIBU_REALTIME_TOKEN: keyWibu,
+ project: "sdm"
+ })
+ const [loadingUpdate, setLoadingUpdate] = useState(false)
+ const [loadingDelete, setLoadingDelete] = useState(false)
+
+
+ async function fetchStatusDiscussion(val: boolean) {
+ try {
+ setLoadingUpdate(true)
+ if (val) {
+ const response = await funEditStatusDiscussionGeneral(id, { status: status })
+ if (response.success) {
+ toast.success(response.message)
+ refresh.set(!refresh.get())
+ setDataRealtime([{
+ category: "discussion-general-detail",
+ id: id,
+ }])
+ onSuccess(false)
+ } else {
+ toast.error(response.message)
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error("Gagal mengupdate diskusi umum, coba lagi nanti");
+ } finally {
+ setLoadingUpdate(false)
+ setValModalStatus(false)
+ }
+ }
+
+ async function fetchDeleteDiscussion(val: boolean) {
+ try {
+ if (val) {
+ setLoadingDelete(true)
+ const response = await funDeleteDiscussionGeneral(id)
+ if (response.success) {
+ setDataRealtime([
+ {
+ category: "discussion-general-delete",
+ id: id,
+ user: response.user
+ },
+ {
+ category: "discussion",
+ }
+ ])
+ toast.success(response.message)
+ onSuccess(false)
+ router.push(`/discussion`)
+ } else {
+ toast.error(response.message)
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error("Gagal hapus diskusi umum, coba lagi nanti");
+ } finally {
+ setLoadingDelete(false)
+ setValModal(false)
+ }
+ }
+
+
+ return (
+
+
+
+
+ window.location.href = `/discussion/${param.id}/member/`} justify={'center'} align={'center'} direction={'column'} >
+
+
+
+
+ Anggota
+
+
+
+ {
+ (roleLogin.get() != "user" && roleLogin.get() != "coadmin") ? (
+ <>
+ window.location.href = `/discussion/${param.id}/edit/`} justify={'center'} align={'center'} direction={'column'} >
+
+
+
+
+ Edit
+
+
+
+ setValModalStatus(true)} >
+ {status === 1 ? (
+ <>
+
+
+
+
+ Tutup Diskusi
+
+ >
+ ) : (
+ <>
+
+
+
+
+
+ Buka Diskusi
+
+ >
+ )}
+
+
+ setValModal(true)} justify={'center'} align={'center'} direction={'column'} >
+
+
+
+
+ Hapus
+
+
+ >
+ )
+ : (<>>)
+ }
+
+
+
+
+ setValModal(false)}
+ description="Apakah Anda yakin ingin menghapus diskusi ini?"
+ onYes={(val) => {
+ fetchDeleteDiscussion(val)
+ }} />
+
+
+ setValModalStatus(false)}
+ description="Apakah Anda yakin ingin mengubah status diskusi ini?"
+ onYes={(val) => {
+ fetchStatusDiscussion(val)
+ }} />
+
+ )
+}
\ No newline at end of file
diff --git a/src/module/discussion_general/ui/drawer_discussion.tsx b/src/module/discussion_general/ui/drawer_discussion.tsx
new file mode 100644
index 0000000..2310337
--- /dev/null
+++ b/src/module/discussion_general/ui/drawer_discussion.tsx
@@ -0,0 +1,25 @@
+import { TEMA } from "@/module/_global";
+import { useHookstate } from "@hookstate/core";
+import { Box, Flex, SimpleGrid, Stack, Text } from "@mantine/core";
+import { IoAddCircle } from "react-icons/io5";
+
+export default function DrawerDiscussionGeneral() {
+ const tema = useHookstate(TEMA)
+
+ return (
+
+
+
+ window.location.href = "/discussion/create"} justify={'center'} align={'center'} direction={'column'} >
+
+
+
+
+ Tambah Diskusi
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/module/discussion_general/ui/edit_discussion_general.tsx b/src/module/discussion_general/ui/edit_discussion_general.tsx
new file mode 100644
index 0000000..4bc1945
--- /dev/null
+++ b/src/module/discussion_general/ui/edit_discussion_general.tsx
@@ -0,0 +1,208 @@
+'use client'
+import { keyWibu, TEMA } from "@/module/_global"
+import LayoutModal from "@/module/_global/layout/layout_modal"
+import { useHookstate } from "@hookstate/core"
+import { Box, Button, rem, Skeleton, TextInput } 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"
+import { funEditDiscussionGeneral, funGetOneDiscussionGeneral } from "../lib/api_discussion_general"
+
+export default function FormEditDiscussionGeneral() {
+ const [isValModal, setValModal] = useState(false)
+ const [loadingModal, setLoadingModal] = useState(false)
+ const router = useRouter()
+ const param = useParams<{ id: string }>()
+ const [isDesc, setDesc] = useState("")
+ const [isTitle, setTitle] = useState("")
+ const [loading, setLoading] = useState(true)
+ const tema = useHookstate(TEMA)
+ const [touched, setTouched] = useState({
+ title: false,
+ desc: false,
+ });
+ const [dataRealTime, setDataRealtime] = useWibuRealtime({
+ WIBU_REALTIME_TOKEN: keyWibu,
+ project: "sdm"
+ })
+
+ async function fetchGetOneDiscussion() {
+ try {
+ setLoading(true)
+ const response = await funGetOneDiscussionGeneral(param.id, '?cat=detail')
+ setDesc(response.data.desc)
+ setTitle(response.data.title)
+ } catch (error) {
+ console.error(error);
+ toast.error("Gagal menampilkan diskusi, coba lagi nanti");
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ async function fetchEditDiscussion() {
+ try {
+ setLoadingModal(true)
+ const response = await funEditDiscussionGeneral(param.id, {
+ title: isTitle,
+ desc: isDesc
+ })
+ if (response.success) {
+ toast.success(response.message)
+ setValModal(false)
+ setDataRealtime([{
+ category: "discussion-general-detail",
+ id: param.id,
+ }])
+ router.push(`/discussion/${param.id}`)
+ } else {
+ toast.error(response.message)
+ }
+
+ } catch (error) {
+ console.error(error);
+ setValModal(false)
+ toast.error("Gagal mengubah diskusi umum, coba lagi nanti");
+ } finally {
+ setValModal(false)
+ setLoadingModal(false)
+ }
+ }
+
+ useShallowEffect(() => {
+ fetchGetOneDiscussion()
+ }, [])
+
+ function onValidation(kategori: string, val: string) {
+ if (kategori == 'title') {
+ setTitle(val)
+ if (val === "") {
+ setTouched({ ...touched, title: true })
+ } else {
+ setTouched({ ...touched, title: false })
+ }
+ } else if (kategori == 'diskusi') {
+ setDesc(val)
+ if (val === "") {
+ setTouched({ ...touched, desc: true })
+ } else {
+ setTouched({ ...touched, desc: false })
+ }
+ }
+ }
+
+
+ function onCheck() {
+ const cek = checkAll()
+ if (!cek)
+ return false
+
+ setValModal(true)
+ }
+
+ function checkAll() {
+ let nilai = true
+ if (isTitle === "") {
+ setTouched(touched => ({ ...touched, title: true }))
+ nilai = false
+ }
+ if (isDesc === "") {
+ setTouched(touched => ({ ...touched, desc: true }))
+ nilai = false
+ }
+ return nilai
+ }
+
+
+
+ return (
+
+
+
+ {loading ?
+ Array(2)
+ .fill(null)
+ .map((_, i) => (
+
+ ))
+ :
+
+ { onValidation('title', e.target.value) }}
+ error={
+ touched.title && (
+ isTitle == "" ? "Judul Tidak Boleh Kosong" : null
+ )
+ }
+ />
+ { onValidation('diskusi', e.target.value) }}
+ error={
+ touched.desc && (
+ isDesc == "" ? "Diskusi Tidak Boleh Kosong" : null
+ )
+ }
+ />
+
+ }
+
+
+
+ {loading ?
+
+ :
+
+ }
+
+
+ setValModal(false)}
+ description="Apakah Anda yakin ingin mengubah data?"
+ onYes={(val) => {
+ if (val) {
+ fetchEditDiscussion()
+ } else {
+ setValModal(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
new file mode 100644
index 0000000..a5d8b7a
--- /dev/null
+++ b/src/module/discussion_general/ui/list_discussion.tsx
@@ -0,0 +1,259 @@
+'use client'
+import { currentScroll, globalNotifPage, keyWibu, ReloadButtonTop, TEMA } from "@/module/_global";
+import { useHookstate } from "@hookstate/core";
+import { ActionIcon, Badge, Box, Divider, Flex, Grid, Group, Skeleton, Spoiler, Text, TextInput } from "@mantine/core";
+import { useShallowEffect } from "@mantine/hooks";
+import _ from "lodash";
+import { useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
+import toast from "react-hot-toast";
+import { BiSolidCommentDetail } from "react-icons/bi";
+import { GrChatOption } from "react-icons/gr";
+import { HiMagnifyingGlass } from "react-icons/hi2";
+import { useWibuRealtime } from "wibu-realtime";
+import { funGetAllDiscussionGeneral } from "../lib/api_discussion_general";
+
+
+export default function ListDiscussionGeneral() {
+ const [isData, setData] = useState([])
+ const [searchQuery, setSearchQuery] = useState('')
+ const [loading, setLoading] = useState(true)
+ const tema = useHookstate(TEMA)
+ const router = useRouter()
+ const { value: containerRef } = useHookstate(currentScroll);
+ const [isPage, setPage] = useState(1)
+ const notifLoadPage = useHookstate(globalNotifPage)
+ const [isRefresh, setRefresh] = useState(false)
+ const [dataRealTime, setDataRealtime] = useWibuRealtime({
+ WIBU_REALTIME_TOKEN: keyWibu,
+ project: "sdm"
+ })
+
+ const getData = async (loading: boolean) => {
+ try {
+ setLoading(loading)
+ const response = await funGetAllDiscussionGeneral('?search=' + searchQuery + '&page=' + isPage)
+ if (response.success) {
+ if (isPage == 1) {
+ setData(response.data)
+ } else {
+ setData([...isData, ...response.data])
+ }
+ } else {
+ toast.error(response.message)
+ }
+ } catch (error) {
+ console.error(error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useShallowEffect(() => {
+ getData(false)
+ }, [isPage])
+
+ useShallowEffect(() => {
+ setPage(1)
+ getData(true)
+ }, [searchQuery])
+
+ useEffect(() => {
+ const handleScroll = async () => {
+ if (containerRef && containerRef.current) {
+ const scrollTop = containerRef.current.scrollTop;
+ const containerHeight = containerRef.current.clientHeight;
+ const scrollHeight = containerRef.current.scrollHeight;
+
+ if (scrollTop + containerHeight >= scrollHeight) {
+ setPage(isPage + 1)
+ }
+ }
+ };
+
+ const container = containerRef?.current;
+ container?.addEventListener("scroll", handleScroll);
+
+ return () => {
+ container?.removeEventListener("scroll", handleScroll);
+ };
+ }, [containerRef, isPage]);
+
+
+
+ useShallowEffect(() => {
+ if (notifLoadPage.get().category == 'discussion-general' && notifLoadPage.get().load == true) {
+ setRefresh(true)
+ }
+ }, [notifLoadPage.get().load])
+
+ useShallowEffect(() => {
+ if (dataRealTime && dataRealTime.some((i: any) => i.category == 'discussion-general')) {
+ setRefresh(true)
+ }
+ }, [dataRealTime])
+
+ function onRefresh() {
+ notifLoadPage.set({
+ category: '',
+ load: false
+ })
+ setRefresh(false)
+ setPage(1)
+ setTimeout(() => {
+ getData(false)
+ }, 500)
+ }
+
+
+ return (
+
+ {
+ isRefresh &&
+ { onRefresh() }}
+ title='UPDATE'
+ />
+
+ }
+ }
+ placeholder="Pencarian"
+ value={searchQuery}
+ onChange={(e) => setSearchQuery(e.target.value)}
+ />
+ {loading ?
+ Array(3)
+ .fill(null)
+ .map((_, i) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))
+ :
+ _.isEmpty(isData)
+ ?
+
+ Tidak ada Diskusi
+
+ :
+ isData.map((v, i) => {
+ return (
+
+ {
+ router.push(`discussion/${v.id}`)
+ }}>
+
+
+
+
+
+
+
+
+ {v.title}
+
+ {v.status === 1 ? "BUKA" : "TUTUP"}
+
+
+
+ {v.createdAt}
+
+
+
+
+ {
+ router.push(`/discussion/${v.id}`)
+ }}
+ >
+ {v.desc}
+
+
+
+
+
+
+ Diskusikan
+
+
+ {v.total_komentar} Komentar
+
+
+
+ {isData.length <= 1 ? "" :
+
+ }
+
+
+ );
+ })
+ }
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/module/discussion_general/ui/member_discussion_general.tsx b/src/module/discussion_general/ui/member_discussion_general.tsx
new file mode 100644
index 0000000..1613acb
--- /dev/null
+++ b/src/module/discussion_general/ui/member_discussion_general.tsx
@@ -0,0 +1,177 @@
+"use client"
+import { globalRole, LayoutDrawer, LayoutNavbarNew, SkeletonList, TEMA } from '@/module/_global';
+import LayoutModal from '@/module/_global/layout/layout_modal';
+import { useHookstate } from '@hookstate/core';
+import { ActionIcon, Avatar, Box, Divider, Grid, Group, Text } from '@mantine/core';
+import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
+import { useParams, useRouter } from 'next/navigation';
+import { useState } from 'react';
+import toast from 'react-hot-toast';
+import { AiOutlineUserAdd } from 'react-icons/ai';
+import { FaUserTie } from 'react-icons/fa6';
+import { IoIosCloseCircle } from 'react-icons/io';
+import { funDelMemberDiscussionGeneral, funGetOneDiscussionGeneral } from '../lib/api_discussion_general';
+import { IFormMemberDisscussionGeneral } from '../lib/type_discussion_general';
+
+
+export default function MemberDiscussionGeneral() {
+ const router = useRouter()
+ const [openDrawer, setDrawer] = useState(false)
+ const param = useParams<{ id: string }>()
+ const [member, setMember] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [valChooseMemberId, setChooseMemberId] = useState("")
+ const [valChooseMemberName, setChooseMemberName] = useState("")
+ const [isOpenModal, setOpenModal] = useState(false)
+ const roleLogin = useHookstate(globalRole)
+ const isMobile = useMediaQuery('(max-width: 455px)')
+ const isMobile2 = useMediaQuery("(max-width: 438px)")
+ const tema = useHookstate(TEMA)
+ const [loadingDelete, setLoadingDelete] = useState(false)
+
+ async function getOneData(loading: boolean) {
+ try {
+ setLoading(loading)
+ const res = await funGetOneDiscussionGeneral(param.id, "?cat=anggota");
+ if (res.success) {
+ setMember(res.data)
+ } else {
+ toast.error(res.message);
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error("Gagal mendapatkan data, coba lagi nanti");
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useShallowEffect(() => {
+ getOneData(true);
+ }, [param.id])
+
+
+ async function onClickMember(id: string, name: string) {
+ setChooseMemberId(id)
+ setChooseMemberName(name)
+ setDrawer(true)
+ }
+
+
+ async function deleteMember() {
+ try {
+ setLoadingDelete(true)
+ const res = await funDelMemberDiscussionGeneral(param.id, { idUser: valChooseMemberId })
+ if (res.success) {
+ toast.success(res.message)
+ setDrawer(false)
+ getOneData(false)
+ } else {
+ toast.error(res.message)
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error("Gagal mendapatkan divisi, coba lagi nanti");
+ } finally {
+ setLoadingDelete(false)
+ setOpenModal(false)
+ }
+ }
+
+
+ return (
+
+ >} />
+
+
+
+ {member.length} Anggota
+
+
+
+ {!loading ?
+ roleLogin.get() != "user" && roleLogin.get() != "coadmin" ?
+ router.push('/discussion/' + param.id + '/add-member/')}>
+
+
+
+ Tambah Anggota
+
+ : <>>
+ :
+ <>>
+ }
+
+
+
+ {loading
+ ? Array(6)
+ .fill(null)
+ .map((_, i) => (
+
+
+
+ ))
+ : member.map((v, i) => {
+ return (
+
+ { onClickMember(v.idUser, v.name) }}
+ >
+
+
+
+
+
+ {v.name}
+
+
+
+
+
+
+
+ );
+ })
+ }
+
+
+
+
+
+
+ setDrawer(false)} title={valChooseMemberName}>
+
+ { router.push('/member/' + valChooseMemberId) }} >
+
+
+
+ Lihat Profil
+
+ {
+ (roleLogin.get() != "user" && roleLogin.get() != "coadmin") &&
+ setOpenModal(true)}>
+
+
+
+ Keluarkan dari diskusi
+
+ }
+
+
+
+ setOpenModal(false)}
+ description="Apakah Anda yakin ingin mengeluarkan anggota?"
+ onYes={(val) => {
+ if (!val) {
+ setOpenModal(false)
+ } else {
+ deleteMember()
+ }
+ }} />
+
+ );
+}
diff --git a/src/module/discussion_general/ui/navbar_discussion.tsx b/src/module/discussion_general/ui/navbar_discussion.tsx
new file mode 100644
index 0000000..42c1a77
--- /dev/null
+++ b/src/module/discussion_general/ui/navbar_discussion.tsx
@@ -0,0 +1,26 @@
+'use client'
+import { LayoutDrawer, LayoutNavbarNew, TEMA } from "@/module/_global";
+import { useHookstate } from "@hookstate/core";
+import { ActionIcon } from "@mantine/core";
+import { useState } from "react";
+import { HiMenu } from "react-icons/hi";
+import DrawerDiscussionGeneral from "./drawer_discussion";
+
+export default function NavbarDiscussionGeneral() {
+ const [openDrawer, setOpenDrawer] = useState(false)
+ const tema = useHookstate(TEMA)
+ return (
+ <>
+ setOpenDrawer(true)} bg={tema.get().bgIcon} size="lg" radius="lg" aria-label="Settings">
+
+
+ }
+ />
+ setOpenDrawer(false)}>
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/src/module/division_new/ui/create_division.tsx b/src/module/division_new/ui/create_division.tsx
index 22c9373..3996563 100644
--- a/src/module/division_new/ui/create_division.tsx
+++ b/src/module/division_new/ui/create_division.tsx
@@ -49,8 +49,11 @@ export default function CreateDivision() {
const cek = checkAll()
if (!cek)
return false
- if (member.length <= 1)
- return toast.error("Error! Silahkan pilih anggota lebih dari 1")
+
+ if (member.length == 0)
+ return toast.error("Error! Silahkan pilih anggota")
+ // if (member.length <= 1)
+ // return toast.error("Error! Silahkan pilih anggota lebih dari 1")
setChooseAdmin(true)
}
diff --git a/src/module/document/ui/drawer_menu_document_division.tsx b/src/module/document/ui/drawer_menu_document_division.tsx
index 7fefc09..f6de9db 100644
--- a/src/module/document/ui/drawer_menu_document_division.tsx
+++ b/src/module/document/ui/drawer_menu_document_division.tsx
@@ -149,12 +149,12 @@ export default function DrawerMenuDocumentDivision() {
}}
activateOnClick={false}
maxSize={3 * 1024 ** 2}
- accept={['text/csv', 'image/png', 'image/jpeg', 'image/heic', 'application/pdf']}
+ accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']}
onReject={(files) => {
refresh.set(true)
setOpenModal(false)
setOpenDrawerDocument(false)
- toast.error('File yang diizinkan: .csv, .png, .jpg, .heic, .pdf dengan ukuran maksimal 3 MB')
+ toast.error('File yang diizinkan: .csv, .png, .jpg, .heic, .pdf, .doc, .docx, .xls, .xlsx dengan ukuran maksimal 3 MB')
}}
>
openRef.current?.()}>
diff --git a/src/module/document/ui/navbar_document_division.tsx b/src/module/document/ui/navbar_document_division.tsx
index 80af889..a513250 100644
--- a/src/module/document/ui/navbar_document_division.tsx
+++ b/src/module/document/ui/navbar_document_division.tsx
@@ -34,6 +34,7 @@ export default function NavbarDocumentDivision() {
const [isOpenModalView, setOpenModalView] = useState(false);
const [isExtension, setExtension] = useState("");
const [idStorage, setIdStorage] = useState("");
+ const [nameFileFull, setNameFileFull] = useState("");
const [name, setName] = useState("");
const [isOpen, setOpen] = useState(false);
const [isDelete, setIsDelete] = useState(false);
@@ -253,9 +254,18 @@ export default function NavbarDocumentDivision() {
setRename(true);
}
- const onDownload = async () => {
+ const onDownload = async (kategori: string, file?: { idFile: string, nameFile: string }) => {
try {
- const fileUrl = `https://wibu-storage.wibudev.com/api/files/${selectedFiles[0].idStorage}`;
+ let idStorageDownload, nameFileDownload = ""
+ if (kategori == "selected") {
+ idStorageDownload = selectedFiles[0].idStorage
+ nameFileDownload = `${selectedFiles[0].name}.${selectedFiles[0].extension}`
+ } else if (kategori == "klik") {
+ idStorageDownload = file?.idFile
+ nameFileDownload = String(file?.nameFile)
+ }
+
+ const fileUrl = `https://wibu-storage.wibudev.com/api/files/${idStorageDownload}`;
const response = await fetch(fileUrl);
const blob = await response.blob();
@@ -263,7 +273,7 @@ export default function NavbarDocumentDivision() {
const link = document.createElement("a");
const url = window.URL.createObjectURL(blob);
link.href = url;
- link.download = `${selectedFiles[0].name}.${selectedFiles[0].extension}`; // Nama file yang akan diunduh
+ link.download = nameFileDownload; // Nama file yang akan diunduh
document.body.appendChild(link);
link.click();
@@ -341,7 +351,7 @@ export default function NavbarDocumentDivision() {
direction={"column"}
onClick={() => {
if (selectedFiles.length > 0 && copyAllowed) {
- onDownload();
+ onDownload("selected");
}
}}
>
@@ -663,9 +673,7 @@ export default function NavbarDocumentDivision() {
) : (
dataDocument.map((v, i) => {
- const isSelected = selectedFiles.some(
- (i: any) => i?.id == v.id
- );
+ const isSelected = selectedFiles.some((i: any) => i?.id == v.id);
return (
@@ -680,20 +688,17 @@ export default function NavbarDocumentDivision() {
xl: 1,
}}
onClick={() => {
- if (
- v.category == "FOLDER" &&
- selectedFiles.length == 0 &&
- !dariSelectAll
- ) {
+ if (v.category == "FOLDER" && selectedFiles.length == 0 && !dariSelectAll) {
router.push("?path=" + v.id);
- } else if (
- v.category == "FILE" &&
- selectedFiles.length == 0 &&
- !dariSelectAll
- ) {
+ } else if (v.category == "FILE" && selectedFiles.length == 0 && !dariSelectAll) {
setExtension(v.extension);
setIdStorage(v.idStorage);
- setOpenModalView(true);
+ setNameFileFull(v.name + "." + v.extension)
+ if (v.extension != "doc" && v.extension != "docx" && v.extension != "xls" && v.extension != "xlsx" && v.extension != "ppt" && v.extension != "pptx") {
+ setOpenModalView(true);
+ } else {
+ onDownload("klik", { idFile: v.idStorage, nameFile: v.name + "." + v.extension })
+ }
}
}}
>
@@ -712,7 +717,13 @@ export default function NavbarDocumentDivision() {
{v.category == "FOLDER" ? (
) : v.extension == "pdf" ||
- v.extension == "csv" ? (
+ v.extension == "csv" ||
+ v.extension == "doc" ||
+ v.extension == "docx" ||
+ v.extension == "xls" ||
+ v.extension == "xlsx" ||
+ v.extension == "ppt" ||
+ v.extension == "pptx" ? (
) : (
@@ -723,7 +734,13 @@ export default function NavbarDocumentDivision() {
{v.category == "FOLDER" ? (
) : v.extension == "pdf" ||
- v.extension == "csv" ? (
+ v.extension == "csv" ||
+ v.extension == "doc" ||
+ v.extension == "docx" ||
+ v.extension == "xls" ||
+ v.extension == "xlsx" ||
+ v.extension == "ppt" ||
+ v.extension == "pptx" ? (
) : (
@@ -747,20 +764,17 @@ export default function NavbarDocumentDivision() {
{
- if (
- v.category == "FOLDER" &&
- selectedFiles.length == 0 &&
- !dariSelectAll
- ) {
+ if (v.category == "FOLDER" && selectedFiles.length == 0 && !dariSelectAll) {
router.push("?path=" + v.id);
- } else if (
- v.category == "FILE" &&
- selectedFiles.length == 0 &&
- !dariSelectAll
- ) {
+ } else if (v.category == "FILE" && selectedFiles.length == 0 && !dariSelectAll) {
setExtension(v.extension);
setIdStorage(v.idStorage);
- setOpenModalView(true);
+ setNameFileFull(v.name + "." + v.extension)
+ if (v.extension != "doc" && v.extension != "docx" && v.extension != "xls" && v.extension != "xlsx" && v.extension != "ppt" && v.extension != "pptx") {
+ setOpenModalView(true);
+ } else {
+ onDownload("klik", { idFile: v.idStorage, nameFile: v.name + "." + v.extension })
+ }
}
}}
>
diff --git a/src/module/home/ui/carosole.tsx b/src/module/home/ui/carosole.tsx
index 91ee799..0d0c3ac 100644
--- a/src/module/home/ui/carosole.tsx
+++ b/src/module/home/ui/carosole.tsx
@@ -1,5 +1,5 @@
'use client'
-import { TEMA } from '@/module/_global';
+import { keyWibu, TEMA } from '@/module/_global';
import { funGetAllBanner, IDataBanner } from '@/module/banner';
import { useHookstate } from '@hookstate/core';
import { Carousel } from '@mantine/carousel';
@@ -9,11 +9,16 @@ import Autoplay from 'embla-carousel-autoplay';
import { useRef, useState } from 'react';
import toast from 'react-hot-toast';
import { funGetHome } from '../lib/api_home';
+import { useWibuRealtime } from 'wibu-realtime';
export default function Carosole() {
const autoplay = useRef(Autoplay({ delay: 5000 }));
const tema = useHookstate(TEMA)
const [isDesa, setDesa] = useState("")
const [isData, setData] = useState([])
+ const [data, setDataRealtime] = useWibuRealtime({
+ WIBU_REALTIME_TOKEN: keyWibu,
+ project: "sdm"
+ })
const fetchData = async () => {
@@ -36,9 +41,19 @@ export default function Carosole() {
}
};
+ async function cekData() {
+ try {
+ const response = await funGetHome('?cat=check-late-project')
+ setDataRealtime(response.data)
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
useShallowEffect(() => {
fetchData();
+ cekData();
}, []);
return (
diff --git a/src/module/home/ui/view_detail_feature.tsx b/src/module/home/ui/view_detail_feature.tsx
index 509fe98..d5cca9d 100644
--- a/src/module/home/ui/view_detail_feature.tsx
+++ b/src/module/home/ui/view_detail_feature.tsx
@@ -4,7 +4,7 @@ import { useHookstate } from '@hookstate/core';
import { ActionIcon, Box, Center, SimpleGrid, Text } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { useRouter } from 'next/navigation';
-import { FaUserTag, FaUserTie } from 'react-icons/fa6';
+import { FaComments, FaUserTag, FaUserTie } from 'react-icons/fa6';
import { HiMegaphone, HiMiniPresentationChartBar, HiMiniUserGroup } from "react-icons/hi2";
import { IoColorPalette } from 'react-icons/io5';
import { PiUsersFourFill } from "react-icons/pi";
@@ -72,6 +72,22 @@ export default function ViewDetailFeature() {
Pengumuman
+ router.push('/discussion')}>
+
+
+
+
+
+
+ Diskusi
+
+
router.push('/member')}>
Jabatan
+ {
+ roleLogin.get() == "cosupadmin" &&
+ router.push('/banner')}>
+
+
+
+
+
+
+ Banner
+
+
+ }
{
roleLogin.get() == "supadmin" &&
<>
diff --git a/src/module/project/ui/add_file_detail_project.tsx b/src/module/project/ui/add_file_detail_project.tsx
index c6ca3dd..b1ceb76 100644
--- a/src/module/project/ui/add_file_detail_project.tsx
+++ b/src/module/project/ui/add_file_detail_project.tsx
@@ -69,7 +69,6 @@ export default function AddFileDetailProject() {
}
const response = await funAddFileProject(param.id, fd)
- console.group(response)
if (response.success) {
setDataRealtime([{
category: "project-detail-file",
@@ -107,9 +106,9 @@ export default function AddFileDetailProject() {
}}
activateOnClick={false}
maxSize={3 * 1024 ** 2}
- accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf']}
+ accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']}
onReject={(files) => {
- return toast.error('File yang diizinkan: .png, .jpg, .heic, .pdf dengan ukuran maksimal 3 MB')
+ return toast.error('File yang diizinkan: .csv, .png, .jpg, .heic, .pdf, .doc, .docx, .xls, .xlsx dengan ukuran maksimal 3 MB')
}}
>
diff --git a/src/module/project/ui/create_project.tsx b/src/module/project/ui/create_project.tsx
index 75124c2..1a09ae0 100644
--- a/src/module/project/ui/create_project.tsx
+++ b/src/module/project/ui/create_project.tsx
@@ -200,7 +200,7 @@ export default function CreateProject() {
label="Grup"
size="md"
styles={{
- input: {
+ input: {
border: `1px solid ${"#D6D8F6"}`,
borderRadius: 10,
},
@@ -413,9 +413,9 @@ export default function CreateProject() {
}}
activateOnClick={false}
maxSize={3 * 1024 ** 2}
- accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf']}
+ accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']}
onReject={(files) => {
- return toast.error('File yang diizinkan: .png, .jpg, .heic, .pdf dengan ukuran maksimal 3 MB')
+ return toast.error('File yang diizinkan: .csv, .png, .jpg, .heic, .pdf, .doc, .docx, .xls, .xlsx dengan ukuran maksimal 3 MB')
}}
>
diff --git a/src/module/project/ui/list_file_detail_project.tsx b/src/module/project/ui/list_file_detail_project.tsx
index 9a65f43..c9fe0ad 100644
--- a/src/module/project/ui/list_file_detail_project.tsx
+++ b/src/module/project/ui/list_file_detail_project.tsx
@@ -7,8 +7,9 @@ import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import toast from 'react-hot-toast';
-import { BsFileTextFill, BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from 'react-icons/bs';
+import { BsFileTextFill, BsFiletypeCsv, BsFiletypeDocx, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng, BsFiletypePptx, BsFiletypeXlsx } from 'react-icons/bs';
import { FaTrash } from 'react-icons/fa6';
+import { ImDownload3 } from "react-icons/im";
import { useWibuRealtime } from 'wibu-realtime';
import { funDeleteFileProject, funGetOneProjectById } from '../lib/api_project';
import { IDataFileProject } from '../lib/type_project';
@@ -113,6 +114,28 @@ export default function ListFileDetailProject() {
}
}, [dataRealTime])
+ const onDownload = async () => {
+ try {
+ const fileUrl = `https://wibu-storage.wibudev.com/api/files/${idStorage}`;
+ const response = await fetch(fileUrl);
+ const blob = await response.blob();
+
+ // Create a link element, use Blob URL
+ const link = document.createElement("a");
+ const url = window.URL.createObjectURL(blob);
+ link.href = url;
+ link.download = `${nameData}`; // Nama file yang akan diunduh
+ document.body.appendChild(link);
+ link.click();
+
+ // Cleanup
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(link);
+ } catch (error) {
+ alert(error);
+ }
+ };
+
return (
<>
@@ -167,13 +190,12 @@ export default function ListFileDetailProject() {
}}>
{item.extension == "pdf" && }
{item.extension == "csv" && }
- {item.extension == "png" && }
- {item.extension == "jpg" && }
- {item.extension == "jpeg" && }
- {item.extension == "PNG" && }
- {item.extension == "JPG" && }
- {item.extension == "JPEG" && }
+ {(item.extension == "png" || item.extension == "PNG") && }
+ {(item.extension == "jpg" || item.extension == "jpeg" || item.extension == "JPG" || item.extension == "JPEG") && }
{item.extension == "heic" && }
+ {(item.extension == "doc" || item.extension == "docx") && }
+ {(item.extension == "xls" || item.extension == "xlsx") && }
+ {(item.extension == "ppt" || item.extension == "pptx") && }
{nameData}} onClose={() => setOpenDrawer(false)}>
-
- { setOpenModalView(true) }} justify={'center'} align={'center'} direction={'column'} >
+
+ {
+ (isExtension != "doc" && isExtension != "docx" && isExtension != "xls" && isExtension != "xlsx" && isExtension != "ppt" && isExtension != "pptx") &&
+ { setOpenModalView(true) }} justify={'center'} align={'center'} direction={'column'} >
+
+
+
+
+ Lihat file
+
+
+ }
+
+ { onDownload() }} justify={'center'} align={'center'} direction={'column'} >
-
+
- Lihat file
+ Download
@@ -227,7 +259,7 @@ export default function ListFileDetailProject() {
- Hapus file
+ Hapus
}
diff --git a/src/module/project/ui/results_file.tsx b/src/module/project/ui/results_file.tsx
index 86c1f4d..4709110 100644
--- a/src/module/project/ui/results_file.tsx
+++ b/src/module/project/ui/results_file.tsx
@@ -1,10 +1,38 @@
-
import { Box, Center, Grid, Group, Text } from '@mantine/core';
-import React from 'react';
-import { BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from 'react-icons/bs';
+import { useShallowEffect } from '@mantine/hooks';
+import { useState } from 'react';
+import { BsFiletypeCsv, BsFiletypeDocx, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng, BsFiletypePptx, BsFiletypeXlsx } from 'react-icons/bs';
import { IListFileTaskProject } from '../lib/type_project';
export default function ResultsFile({ name, extension }: IListFileTaskProject) {
+ const [fixed, setFixed] = useState(extension)
+
+ function cekExtension() {
+ let jadi = extension
+ if (extension == "msword") {
+ jadi = "doc"
+ } else if (extension == "vnd.openxmlformats-officedocument.wordprocessingml.document") {
+ jadi = "docx"
+ } else if (extension == "vnd.ms-excel") {
+ jadi = "xls"
+ } else if (extension == "vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
+ jadi = "xlsx"
+ } else if (extension == "vnd.ms-powerpoint") {
+ jadi = "ppt"
+ } else if (extension == "vnd.openxmlformats-officedocument.presentationml.presentation") {
+ jadi = "pptx"
+ } else {
+ jadi = extension
+ }
+
+ setFixed(jadi)
+ }
+
+
+ useShallowEffect(() => {
+ cekExtension()
+ }, []);
+
return (
- {extension == "pdf" && }
- {extension == "csv" && }
- {extension == "png" && }
- {extension == "jpg" || extension == "jpeg" && }
- {extension == "heic" && }
+ {fixed == "pdf" && }
+ {fixed == "csv" && }
+ {fixed == "png" && }
+ {fixed == "jpg" || fixed == "jpeg" && }
+ {fixed == "heic" && }
+ {fixed == "doc" || fixed == "docx" && }
+ {fixed == "xls" || fixed == "xlsx" && }
+ {fixed == "ppt" || fixed == "pptx" && }
diff --git a/src/module/task/ui/add_file_detail_task.tsx b/src/module/task/ui/add_file_detail_task.tsx
index 7bc6884..a992454 100644
--- a/src/module/task/ui/add_file_detail_task.tsx
+++ b/src/module/task/ui/add_file_detail_task.tsx
@@ -107,9 +107,9 @@ export default function AddFileDetailTask() {
}}
activateOnClick={false}
maxSize={3 * 1024 ** 2}
- accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf']}
+ accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']}
onReject={(files) => {
- return toast.error('File yang diizinkan: .png, .jpg, .heic, .pdf dengan ukuran maksimal 3 MB')
+ return toast.error('File yang diizinkan: .csv, .png, .jpg, .heic, .pdf, .doc, .docx, .xls, .xlsx dengan ukuran maksimal 3 MB')
}}
>
diff --git a/src/module/task/ui/create_task.tsx b/src/module/task/ui/create_task.tsx
index dc4a033..fc24332 100644
--- a/src/module/task/ui/create_task.tsx
+++ b/src/module/task/ui/create_task.tsx
@@ -336,9 +336,9 @@ export default function CreateTask() {
}}
activateOnClick={false}
maxSize={3 * 1024 ** 2}
- accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf']}
+ accept={['image/png', 'image/jpeg', 'image/heic', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']}
onReject={(files) => {
- return toast.error('File yang diizinkan: .png, .jpg, .heic, .pdf dengan ukuran maksimal 3 MB')
+ return toast.error('File yang diizinkan: .csv, .png, .jpg, .heic, .pdf, .doc, .docx, .xls, .xlsx dengan ukuran maksimal 3 MB')
}}
>
diff --git a/src/module/task/ui/detail_list_file_task.tsx b/src/module/task/ui/detail_list_file_task.tsx
index 50364d4..198bddc 100644
--- a/src/module/task/ui/detail_list_file_task.tsx
+++ b/src/module/task/ui/detail_list_file_task.tsx
@@ -1,18 +1,19 @@
'use client'
import { globalRole, keyWibu, LayoutDrawer, LayoutModalViewFile, TEMA } from "@/module/_global";
import LayoutModal from "@/module/_global/layout/layout_modal";
+import { globalIsMemberDivision } from "@/module/division_new";
import { useHookstate } from "@hookstate/core";
import { Box, Center, Flex, Grid, Group, SimpleGrid, Skeleton, Stack, Text } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { useParams } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
-import { BsFileTextFill, BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from "react-icons/bs";
+import { BsFileTextFill, BsFiletypeCsv, BsFiletypeDocx, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng, BsFiletypePptx, BsFiletypeXlsx } from "react-icons/bs";
import { FaTrash } from "react-icons/fa6";
+import { ImDownload3 } from "react-icons/im";
import { useWibuRealtime } from "wibu-realtime";
import { funDeleteFileTask, funGetTaskDivisionById } from "../lib/api_task";
import { IDataFileTaskDivision } from "../lib/type_task";
-import { globalIsMemberDivision } from "@/module/division_new";
export default function ListFileDetailTask() {
const roleLogin = useHookstate(globalRole)
@@ -111,6 +112,28 @@ export default function ListFileDetailTask() {
}
}, [dataRealTime])
+ const onDownload = async () => {
+ try {
+ const fileUrl = `https://wibu-storage.wibudev.com/api/files/${idDataStorage}`;
+ const response = await fetch(fileUrl);
+ const blob = await response.blob();
+
+ // Create a link element, use Blob URL
+ const link = document.createElement("a");
+ const url = window.URL.createObjectURL(blob);
+ link.href = url;
+ link.download = `${nameData}`; // Nama file yang akan diunduh
+ document.body.appendChild(link);
+ link.click();
+
+ // Cleanup
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(link);
+ } catch (error) {
+ alert(error);
+ }
+ };
+
return (
File
@@ -159,13 +182,12 @@ export default function ListFileDetailTask() {
{item.extension == "pdf" && }
{item.extension == "csv" && }
- {item.extension == "png" && }
- {item.extension == "jpg" && }
- {item.extension == "jpeg" && }
- {item.extension == "PNG" && }
- {item.extension == "JPG" && }
- {item.extension == "JPEG" && }
+ {(item.extension == "png" || item.extension == "PNG") && }
+ {(item.extension == "jpg" || item.extension == "jpeg" || item.extension == "JPG" || item.extension == "JPEG") && }
{item.extension == "heic" && }
+ {(item.extension == "doc" || item.extension == "docx") && }
+ {(item.extension == "xls" || item.extension == "xlsx") && }
+ {(item.extension == "ppt" || item.extension == "pptx") && }
@@ -185,15 +207,24 @@ export default function ListFileDetailTask() {
{nameData}} onClose={() => setOpenDrawer(false)}>
-
- { setOpenModalView(true) }} justify={'center'} align={'center'} direction={'column'} >
+
+ {
+ (isExtension != "doc" && isExtension != "docx" && isExtension != "xls" && isExtension != "xlsx" && isExtension != "ppt" && isExtension != "pptx") &&
+ { setOpenModalView(true) }} justify={'center'} align={'center'} direction={'column'} >
+
+
+
+
+ Lihat file
+
+
+ }
+ { onDownload() }} justify={'center'} align={'center'} direction={'column'} >
-
+
- Lihat file
+ Download
{
diff --git a/src/module/task/ui/results_file.tsx b/src/module/task/ui/results_file.tsx
index 4bc6f5c..ebfbc62 100644
--- a/src/module/task/ui/results_file.tsx
+++ b/src/module/task/ui/results_file.tsx
@@ -1,9 +1,38 @@
import { Box, Center, Grid, Group, Text } from '@mantine/core';
-import React from 'react';
-import { BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from 'react-icons/bs';
+import React, { useState } from 'react';
+import { BsFiletypeCsv, BsFiletypeDocx, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng, BsFiletypePptx, BsFiletypeXlsx } from 'react-icons/bs';
import { IListFileTask } from '../lib/type_task';
+import { useShallowEffect } from '@mantine/hooks';
export default function ResultsFile({ name, extension }: IListFileTask) {
+ const [fixed, setFixed] = useState(extension)
+
+ function cekExtension() {
+ let jadi = extension
+ if (extension == "msword") {
+ jadi = "doc"
+ } else if (extension == "vnd.openxmlformats-officedocument.wordprocessingml.document") {
+ jadi = "docx"
+ } else if (extension == "vnd.ms-excel") {
+ jadi = "xls"
+ } else if (extension == "vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
+ jadi = "xlsx"
+ } else if (extension == "vnd.ms-powerpoint") {
+ jadi = "ppt"
+ } else if (extension == "vnd.openxmlformats-officedocument.presentationml.presentation") {
+ jadi = "pptx"
+ } else {
+ jadi = extension
+ }
+
+ setFixed(jadi)
+ }
+
+
+ useShallowEffect(() => {
+ cekExtension()
+ }, []);
+
return (
- {extension == "pdf" && }
- {extension == "csv" && }
- {extension == "png" && }
- {extension == "jpg" && }
- {extension == "jpeg" && }
- {extension == "PNG" && }
- {extension == "JPG" && }
- {extension == "JPEG" && }
- {extension == "heic" && }
+ {fixed == "pdf" && }
+ {fixed == "csv" && }
+ {fixed == "png" && }
+ {fixed == "jpg" || fixed == "jpeg" && }
+ {fixed == "heic" && }
+ {fixed == "doc" || fixed == "docx" && }
+ {fixed == "xls" || fixed == "xlsx" && }
+ {fixed == "ppt" || fixed == "pptx" && }