diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0062780..0748a9d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -168,7 +168,8 @@ model Project { idGroup String name String status Int @default(0) // 0 = pending, 1 = ongoing, 2 = done, 3 = cancelled - desc String @db.Text + desc String? @db.Text + reason String? @db.Text isActive Boolean @default(true) User User @relation(fields: [createdBy], references: [id]) createdBy String @@ -177,6 +178,7 @@ model Project { ProjectMember ProjectMember[] ProjectFile ProjectFile[] ProjectComment ProjectComment[] + ProjectTask ProjectTask[] } model ProjectMember { @@ -202,6 +204,20 @@ model ProjectFile { updatedAt DateTime @updatedAt } +model ProjectTask { + id String @id @default(cuid()) + Project Project @relation(fields: [idProject], references: [id]) + idProject String + name String + desc String? + status Int @default(0) // 0 = pending, 1 = ongoing + dateStart DateTime @db.Date + dateEnd DateTime @db.Date + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + model ProjectComment { id String @id @default(cuid()) Project Project @relation(fields: [idProject], references: [id]) diff --git a/src/app/api/project/[id]/member/route.ts b/src/app/api/project/[id]/member/route.ts new file mode 100644 index 0000000..71bd20d --- /dev/null +++ b/src/app/api/project/[id]/member/route.ts @@ -0,0 +1,94 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// ADD MEMBER PROJECT +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 data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan project, data tidak ditemukan", + }, + { status: 404 } + ); + } + + if (member.length > 0) { + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name"]), + idProject: id, + idUser: v.idUser + })) + + const insertMember = await prisma.projectMember.createMany({ + data: dataMember + }) + } + + return NextResponse.json({ success: true, message: "Berhasil menambahkan anggota project" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah anggota project, coba lagi nanti", reason: (error as Error).message, }, { 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 data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const deleteMember = await prisma.projectMember.deleteMany({ + where: { + idProject: id, + idUser: idUser + } + }) + + return NextResponse.json({ success: true, message: "Berhasil mengeluarkan anggota project" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengeluarkan anggota project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/project/[id]/route.ts b/src/app/api/project/[id]/route.ts index 875b6e0..840b9a1 100644 --- a/src/app/api/project/[id]/route.ts +++ b/src/app/api/project/[id]/route.ts @@ -1,41 +1,263 @@ import { prisma } from "@/module/_global"; import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import moment from "moment"; import { NextResponse } from "next/server"; -// GET ONE PROJECT +// GET DETAIL PROJECT / GET ONE PROJECT export async function GET(request: Request, context: { params: { id: string } }) { try { + let allData const { id } = context.params; + const user = await funGetUserByCookies() + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.project.findUnique({ + where: { + id: String(id), + isActive: true + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan project, data tidak ditemukan", }, { status: 404 }); + } + + + if (kategori == "data") { + allData = data + } else if (kategori == "progress") { + const dataProgress = await prisma.projectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + updatedAt: 'desc' + } + }) + + console.log(dataProgress) + const semua = dataProgress.length + const selesai = _.filter(dataProgress, { status: 1 }).length + const progress = Math.ceil((selesai / semua) * 100) + + allData = { + progress: progress, + lastUpdate: moment(dataProgress[0].updatedAt).format("DD MMMM YYYY"), + } + } else if (kategori == "task") { + const dataProgress = await prisma.projectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + name: true, + desc: true, + status: true, + dateStart: true, + dateEnd: true, + }, + orderBy: { + status: 'desc' + } + }) + + const formatData = dataProgress.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd"]), + dateStart: moment(v.dateStart).format("DD MMMM YYYY"), + dateEnd: moment(v.dateEnd).format("DD MMMM YYYY"), + })) + + allData = formatData + + } else if (kategori == "file") { + const dataFile = await prisma.projectFile.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'desc' + }, + select: { + id: true, + name: true, + extension: true + } + }) + + allData = dataFile + } else if (kategori == "member") { + + const dataMember = await prisma.projectMember.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + idUser: true, + User: { + select: { + name: true, + email: true + } + } + } + }) + + const fix = dataMember.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + })) + + allData = fix + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan project", data: allData, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan project, coba lagi nantiiiiii", reason: (error as Error).message, }, { status: 500 }); + } +} + +//CREATE NEW DETAIL TASK PROJECT +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 data = await prisma.project.findFirst({ + const { id } = context.params + const { name, dateStart, dateEnd, } = await request.json() + + const data = await prisma.project.count({ where: { - id: id - }, - select: { - id: true, - name: true, - desc: true, - status: true, - ProjectMember: { - where: { - isActive: true - }, - select: { - idUser: true - } - } + id: String(id) } }) - return NextResponse.json({ success: true, message: "Berhasil mendapatkan project", data: data, }, { status: 200 }); + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan project, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.projectTask.create({ + data: { + name, + idProject: id, + dateStart: new Date(moment(dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(dateEnd).format('YYYY-MM-DD')), + } + }) + + return NextResponse.json({ success: true, message: "Detail project berhasil ditambahkan", data: dataCreate, }, { status: 200 }); } catch (error) { console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + return NextResponse.json({ success: false, message: "Gagal tambah detail project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// PEMBATALAN TASK PROJECT +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 { idTask } = await request.json() + const data = await prisma.projectTask.count({ + where: { + id: String(idTask) + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan project, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.project.update({ + where: { + id + }, + data: { + status: 3, + reason: idTask + } + }) + + return NextResponse.json({ success: true, message: "Project berhasil dibatalkan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membatalkan project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +// EDIT PROJECT +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 { name } = await request.json() + + const data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan project, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.project.update({ + where: { + id + }, + data: { + name + } + }) + + return NextResponse.json({ success: true, message: "Project berhasil diubah" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengubah project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); } } \ No newline at end of file diff --git a/src/app/api/project/detail/[id]/route.ts b/src/app/api/project/detail/[id]/route.ts new file mode 100644 index 0000000..25b8b0d --- /dev/null +++ b/src/app/api/project/detail/[id]/route.ts @@ -0,0 +1,54 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { NextResponse } from "next/server"; + + +// HAPUS DETAIL PROJECT +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 data = await prisma.projectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus project gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.projectTask.update({ + where: { + id: id, + }, + data: { + isActive: false, + }, + }); + + return NextResponse.json( + { + success: true, + message: "Project berhasil dihapus", + data, + }, + { status: 200 } + ); + } catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Gagal menghapus project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// EDIT STATUS DETAIL PROJECT diff --git a/src/module/project/lib/api_project.ts b/src/module/project/lib/api_project.ts index 0761698..8716ead 100644 --- a/src/module/project/lib/api_project.ts +++ b/src/module/project/lib/api_project.ts @@ -3,4 +3,9 @@ export const funGetAllProject = async (path?: string) => { const response = await fetch(`/api/project${(path) ? path : ''}`, { next: { tags: ['project'] } }); return await response.json().catch(() => null); +} + +export const funGetOneProjectById = async (path: string, kategori: string) => { + const response = await fetch(`/api/project/${path}?cat=${kategori}`); + return await response.json().catch(() => null); } \ No newline at end of file diff --git a/src/module/project/lib/type_project.ts b/src/module/project/lib/type_project.ts index b5e40ab..bad9475 100644 --- a/src/module/project/lib/type_project.ts +++ b/src/module/project/lib/type_project.ts @@ -4,4 +4,26 @@ export interface IDataProject { desc: string status: number member: number - } \ No newline at end of file +} + +export interface IDataListTaskProject { + id: string + name: string + desc: string + status: number + dateStart: string + dateEnd: string +} + +export interface IDataFileProject { + id: string + name: string + extension: string +} + +export interface IDataMemberProject { + id: string + idUser: string + name: string + email: string +} \ No newline at end of file diff --git a/src/module/project/lib/val_project.ts b/src/module/project/lib/val_project.ts new file mode 100644 index 0000000..bda7411 --- /dev/null +++ b/src/module/project/lib/val_project.ts @@ -0,0 +1,3 @@ +import { hookstate } from "@hookstate/core"; + +export const globalRefreshProject = hookstate(false) \ No newline at end of file diff --git a/src/module/project/ui/liat_anggota_detail_project.tsx b/src/module/project/ui/liat_anggota_detail_project.tsx index 5b4b5a9..80abb5c 100644 --- a/src/module/project/ui/liat_anggota_detail_project.tsx +++ b/src/module/project/ui/liat_anggota_detail_project.tsx @@ -1,47 +1,46 @@ 'use client' import { WARNA } from '@/module/_global'; import { Avatar, Box, Flex, Group, Text } from '@mantine/core'; -import React from 'react'; +import React, { useState } from 'react'; +import { funGetOneProjectById } from '../lib/api_project'; +import toast from 'react-hot-toast'; +import { useParams } from 'next/navigation'; +import { useShallowEffect } from '@mantine/hooks'; +import { IDataMemberProject } from '../lib/type_project'; -const dataTugas = [ - { - id: 1, - name: "Iqbal Ramadan", - image: "https://i.pravatar.cc/1000?img=5", - email: "iqbal.ramadan@gmail.com", - }, - { - id: 2, - name: "Doni Setiawan", - image: "https://i.pravatar.cc/1000?img=10", - email: "doni.setiawan@gmail.com", - }, - { - id: 3, - name: "Rangga Agung", - image: "https://i.pravatar.cc/1000?img=51", - email: "rangga.agung@gmail.com", - }, - { - id: 4, - name: "Ramadan Sananta", - image: "https://i.pravatar.cc/1000?img=15", - email: "ramadan@gmail.com", - }, - { - id: 5, - name: "Imam Baroni", - image: "https://i.pravatar.cc/1000?img=22", - email: "imam.baroni@gmail.com", - }, -]; export default function LiatAnggotaDetailProject() { + const [isData, setData] = useState([]) + const param = useParams<{ id: string }>() + const [loading, setLoading] = useState(true) + + async function getOneData() { + try { + setLoading(true) + const res = await funGetOneProjectById(param.id, 'member'); + if (res.success) { + setData(res.data) + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan member proyek, coba lagi nanti"); + } finally { + setLoading(false) + } + } + + useShallowEffect(() => { + getOneData(); + }, [param.id]) + return ( Anggota Terpilih - Total 10 Anggota + Total {isData.length} Anggota @@ -53,34 +52,38 @@ export default function LiatAnggotaDetailProject() { px={20} py={10} > - - Divisi Kerohanian - - {dataTugas.map((v, i) => { - return ( - - - - - - {v.name} - - - {v.email} - - - - - Anggota - - - ); - })} + { + loading ? loading : + isData.length === 0 ? Tidak ada anggota : + isData.map((v, i) => { + return ( + { + // setDataChoose({ id: v.idUser, name: v.name }) + // setOpenDrawer(true) + }} + > + + + + + {v.name} + + + {v.email} + + + + + Anggota + + + ); + })} diff --git a/src/module/project/ui/list_file_detail_project.tsx b/src/module/project/ui/list_file_detail_project.tsx index 54fc7b6..579e25f 100644 --- a/src/module/project/ui/list_file_detail_project.tsx +++ b/src/module/project/ui/list_file_detail_project.tsx @@ -1,9 +1,41 @@ 'use client' import { WARNA } from '@/module/_global'; -import { Box, Text } from '@mantine/core'; -import React from 'react'; +import { Box, Group, Text } from '@mantine/core'; +import React, { useState } from 'react'; +import toast from 'react-hot-toast'; +import { funGetOneProjectById } from '../lib/api_project'; +import { useParams } from 'next/navigation'; +import { useShallowEffect } from '@mantine/hooks'; +import { IDataFileProject } from '../lib/type_project'; +import { BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from 'react-icons/bs'; export default function ListFileDetailProject() { + const [isData, setData] = useState([]) + const param = useParams<{ id: string }>() + const [loading, setLoading] = useState(true) + + async function getOneData() { + try { + setLoading(true) + const res = await funGetOneProjectById(param.id, 'file'); + if (res.success) { + setData(res.data) + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan file proyek, coba lagi nanti"); + } finally { + setLoading(false) + } + } + + useShallowEffect(() => { + getOneData(); + }, [param.id]) + return ( <> @@ -13,7 +45,33 @@ export default function ListFileDetailProject() { border: `1px solid ${"#D6D8F6"}`, padding: 20 }}> - Tidak ada file + { + + loading ? loading : + isData.length === 0 ? Tidak ada file : + isData.map((item, index) => { + return ( + + + {item.extension == "pdf" && } + {item.extension == "csv" && } + {item.extension == "png" && } + {item.extension == "jpg" || item.extension == "jpeg" && } + {item.extension == "heic" && } + {item.name} + + + ) + }) + } diff --git a/src/module/project/ui/list_tugas_detail_project.tsx b/src/module/project/ui/list_tugas_detail_project.tsx index 36f566a..f3c4c0e 100644 --- a/src/module/project/ui/list_tugas_detail_project.tsx +++ b/src/module/project/ui/list_tugas_detail_project.tsx @@ -1,10 +1,41 @@ 'use client' import { WARNA } from '@/module/_global'; -import { Box, Center, Checkbox, Grid, Group, SimpleGrid, Text } from '@mantine/core'; -import React from 'react'; +import { Box, Center, Checkbox, Divider, Grid, Group, SimpleGrid, Text } from '@mantine/core'; +import React, { useState } from 'react'; +import toast from 'react-hot-toast'; import { AiOutlineFileSync } from 'react-icons/ai'; +import { funGetOneProjectById } from '../lib/api_project'; +import { useParams } from 'next/navigation'; +import { useShallowEffect } from '@mantine/hooks'; +import { IDataListTaskProject } from '../lib/type_project'; export default function ListTugasDetailProject() { + const [isData, setData] = useState([]) + const [loading, setLoading] = useState(true) + const param = useParams<{ id: string }>() + + async function getOneData() { + try { + setLoading(true) + const res = await funGetOneProjectById(param.id, 'task'); + if (res.success) { + setData(res.data) + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan list tugas proyek, coba lagi nanti"); + } finally { + setLoading(false) + } + } + + useShallowEffect(() => { + getOneData(); + }, [param.id]) + return ( <> @@ -19,59 +50,76 @@ export default function ListTugasDetailProject() { padding: 20, }} > - - -
- -
-
- - - - - Laporan Permasyarakatan - - - - - - Tanggal Mulai - - 16 Juni 2024 - - - - Tanggal Berakhir - - 20 Juni 2024 - - - - - -
+ { + loading ? loading : + isData.length === 0 ? Tidak ada tugas : + isData.map((item, index) => { + return ( + + { + // setIdData(item.id) + // setStatusData(item.status) + // setOpenDrawer(true) + }} + > + +
+ +
+
+ + + + + {item.name} + + + + + + Tanggal Mulai + + {item.dateStart} + + + + Tanggal Berakhir + + {item.dateEnd} + + + + + +
+ +
+ ) + }) + }
diff --git a/src/module/project/ui/progress_detail_project.tsx b/src/module/project/ui/progress_detail_project.tsx index e2a5551..fd08e2d 100644 --- a/src/module/project/ui/progress_detail_project.tsx +++ b/src/module/project/ui/progress_detail_project.tsx @@ -1,10 +1,53 @@ 'use client' import { WARNA } from '@/module/_global'; +import { useHookstate } from '@hookstate/core'; import { ActionIcon, Box, Grid, Progress, Text } from '@mantine/core'; -import React from 'react'; +import { useParams } from 'next/navigation'; +import React, { useEffect, useState } from 'react'; import { HiMiniPresentationChartBar } from 'react-icons/hi2'; +import { globalRefreshProject } from '../lib/val_project'; +import toast from 'react-hot-toast'; +import { funGetOneProjectById } from '../lib/api_project'; +import { useShallowEffect } from '@mantine/hooks'; export default function ProgressDetailProject() { + const [valProgress, setValProgress] = useState(0) + const [valLastUpdate, setValLastUpdate] = useState('') + const param = useParams<{ id: string }>() + const refresh = useHookstate(globalRefreshProject) + + async function getOneData() { + try { + const res = await funGetOneProjectById(param.id, 'progress'); + console.log("data",res) + if (res.success) { + setValProgress(res.data.progress); + setValLastUpdate(res.data.lastUpdate); + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan progress proyek, coba lagi nanti"); + } + } + + function onRefresh() { + if (refresh.get()) { + getOneData() + refresh.set(false) + } + } + + useEffect(() => { + onRefresh() + }, [refresh.get()]) + + useShallowEffect(() => { + getOneData(); + }, [param.id]) + return ( <> @@ -29,7 +72,7 @@ export default function ProgressDetailProject() { - Kemajuan Proyek 60% + Kemajuan Proyek {valProgress}% diff --git a/src/module/task/ui/detail_list_tugas_task.tsx b/src/module/task/ui/detail_list_tugas_task.tsx index eb17ff1..5582e23 100644 --- a/src/module/task/ui/detail_list_tugas_task.tsx +++ b/src/module/task/ui/detail_list_tugas_task.tsx @@ -103,65 +103,68 @@ export default function ListTugasDetailTask() { isData.length === 0 ? Tidak ada tugas : isData.map((item, index) => { return ( - { - setIdData(item.id) - setStatusData(item.status) - setOpenDrawer(true) - }} - > - -
- -
-
- - - - - {item.title} - - - - - - Tanggal Mulai - - {item.dateStart} - - - - Tanggal Berakhir - - {item.dateEnd} - - - - - -
+ + { + setIdData(item.id) + setStatusData(item.status) + setOpenDrawer(true) + }} + > + +
+ +
+
+ + + + + {item.title} + + + + + + Tanggal Mulai + + {item.dateStart} + + + + Tanggal Berakhir + + {item.dateEnd} + + + + + +
+ +
) }) }