From e8fcf445581b1855c79cda5236f8ce99f5b47eaa Mon Sep 17 00:00:00 2001 From: amel Date: Mon, 19 Aug 2024 13:22:02 +0800 Subject: [PATCH] upd: task divisi Deskripsi: - update task divisi - delete detail task - update status task - edit detail task No Issues --- .../task/edit/[detail]/page.tsx | 9 + src/app/api/task/[id]/route.ts | 5 +- src/app/api/task/detail/[id]/route.ts | 187 ++++++++++++++++++ src/app/api/task/route.ts | 9 +- src/module/task/index.ts | 4 +- src/module/task/lib/api_task.ts | 45 ++++- src/module/task/lib/val_task.ts | 14 +- src/module/task/ui/detail_list_tugas_task.tsx | 154 ++++++++++++++- src/module/task/ui/detail_progress_task.tsx | 21 +- src/module/task/ui/edit_detail_task.tsx | 162 +++++++++++++++ .../task/ui/navbar_detail_division_task.tsx | 2 +- 11 files changed, 592 insertions(+), 20 deletions(-) create mode 100644 src/app/(application)/division/[id]/(fitur-division)/task/edit/[detail]/page.tsx create mode 100644 src/app/api/task/detail/[id]/route.ts create mode 100644 src/module/task/ui/edit_detail_task.tsx diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/edit/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/edit/[detail]/page.tsx new file mode 100644 index 0000000..fe6054e --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/edit/[detail]/page.tsx @@ -0,0 +1,9 @@ +import { EditDetailTask } from "@/module/task" + +function Page() { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/api/task/[id]/route.ts b/src/app/api/task/[id]/route.ts index 24409ed..0817ebe 100644 --- a/src/app/api/task/[id]/route.ts +++ b/src/app/api/task/[id]/route.ts @@ -36,7 +36,7 @@ export async function GET(request: Request, context: { params: { id: string } }) isActive: true, idProject: String(id) }, - orderBy:{ + orderBy: { updatedAt: 'desc' } }) @@ -61,6 +61,9 @@ export async function GET(request: Request, context: { params: { id: string } }) status: true, dateStart: true, dateEnd: true, + }, + orderBy: { + status: 'desc' } }) diff --git a/src/app/api/task/detail/[id]/route.ts b/src/app/api/task/detail/[id]/route.ts new file mode 100644 index 0000000..1fb2f37 --- /dev/null +++ b/src/app/api/task/detail/[id]/route.ts @@ -0,0 +1,187 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// HAPUS DETAIL TASK +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.divisionProjectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus tugas gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProjectTask.update({ + where: { + id: id, + }, + data: { + isActive: false, + }, + }); + + return NextResponse.json( + { + success: true, + message: "Tugas berhasil dihapus", + data, + }, + { status: 200 } + ); + } catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Gagal menghapus tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// EDIT STATUS DETAIL TASK +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 { status } = (await request.json()); + const data = await prisma.divisionProjectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Update status detail tugas gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProjectTask.update({ + where: { + id: id, + }, + data: { + status: status, + }, + }); + + return NextResponse.json( + { + success: true, + message: "Status detail tugas berhasil diupdate", + data, + }, + { status: 200 } + ); + } catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate status detail tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// GET ONE DETAIL TASK +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + 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.divisionProjectTask.findUnique({ + where: { + id: String(id), + isActive: true + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan detail tugas, data tidak ditemukan", }, { status: 404 }); + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan detail tugas divisi", data }, { status: 200 }); + + } + catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan detail tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// EDIT DETAIL TASK +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 { title, dateStart, dateEnd } = (await request.json()); + const data = await prisma.divisionProjectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Edit detail tugas gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProjectTask.update({ + where: { + id: id, + }, + data: { + title, + 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 tugas berhasil diedit", + data, + }, + { status: 200 } + ); + } catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Gagal mengedit detail tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/task/route.ts b/src/app/api/task/route.ts index e5939da..558cabb 100644 --- a/src/app/api/task/route.ts +++ b/src/app/api/task/route.ts @@ -1,6 +1,7 @@ import { funUploadFile, prisma } from "@/module/_global"; import { funGetUserByCookies } from "@/module/auth"; import _, { ceil } from "lodash"; +import moment from "moment"; import { NextResponse } from "next/server"; @@ -115,8 +116,8 @@ export async function POST(request: Request) { idDivision: idDivision, idProject: data.id, title: v.title, - dateStart: new Date(v.dateStart), - dateEnd: new Date(v.dateEnd), + dateStart: new Date(moment(v.dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(v.dateEnd).format('YYYY-MM-DD')), })) const insertTask = await prisma.divisionProjectTask.createMany({ @@ -139,7 +140,6 @@ export async function POST(request: Request) { let fileFix: any[] = [] - console.log("amalia",file) if (file.length > 0) { file.map((v: any, index: any) => { @@ -158,7 +158,6 @@ export async function POST(request: Request) { fileFix.push(dataFile) }) - console.log(fileFix) const insertFile = await prisma.divisionProjectFile.createMany({ data: fileFix }) @@ -170,6 +169,6 @@ export async function POST(request: Request) { } catch (error) { console.log(error); - return NextResponse.json({ success: false, message: "Gagal membuat tugas divisiii, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + return NextResponse.json({ success: false, message: "Gagal membuat tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); } } \ No newline at end of file diff --git a/src/module/task/index.ts b/src/module/task/index.ts index 5a6dd34..afbcfd7 100644 --- a/src/module/task/index.ts +++ b/src/module/task/index.ts @@ -5,6 +5,7 @@ import ListAnggotaDetailTask from "./ui/detail_list_anggota_task"; import ListFileDetailTask from "./ui/detail_list_file_task"; import ListTugasDetailTask from "./ui/detail_list_tugas_task"; import ProgressDetailTask from "./ui/detail_progress_task"; +import EditDetailTask from "./ui/edit_detail_task"; import FileSave from "./ui/file_save"; import NavbarDetailDivisionTask from "./ui/navbar_detail_division_task"; import NavbarDivisionTask from "./ui/navbar_division_task"; @@ -20,4 +21,5 @@ export { NavbarDetailDivisionTask } export { ListTugasDetailTask } export { ProgressDetailTask } export { ListFileDetailTask } -export { ListAnggotaDetailTask } \ No newline at end of file +export { ListAnggotaDetailTask } +export { EditDetailTask } \ No newline at end of file diff --git a/src/module/task/lib/api_task.ts b/src/module/task/lib/api_task.ts index 7fe36fd..5aec7e2 100644 --- a/src/module/task/lib/api_task.ts +++ b/src/module/task/lib/api_task.ts @@ -1,4 +1,4 @@ -import { IFormTaskDivision } from "./type_task"; +import { IFormDateTask, IFormTaskDivision } from "./type_task"; export const funGetAllTask = async (path?: string) => { const response = await fetch(`/api/task${(path) ? path : ''}`, { next: { tags: ['task'] } }); @@ -23,4 +23,45 @@ export const funCreateTask = async (data: IFormTaskDivision) => { export const funGetTaskDivisionById = async (path: string, kategori: string) => { const response = await fetch(`/api/task/${path}?cat=${kategori}`); return await response.json().catch(() => null); -} \ No newline at end of file +} + + +export const funDeleteDetailTask = async (path: string) => { + const response = await fetch(`/api/task/detail/${path}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + return await response.json().catch(() => null); +}; + + +export const funUpdateStatusDetailTask = async (path: string, data: { status: number }) => { + const response = await fetch(`/api/task/detail/${path}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +}; + + +export const funGetDetailTask = async (path: string) => { + const response = await fetch(`/api/task/detail/${path}`); + return await response.json().catch(() => null); +} + + +export const funEditDetailTask = async (path: string, data: IFormDateTask) => { + const response = await fetch(`/api/task/detail/${path}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +}; \ No newline at end of file diff --git a/src/module/task/lib/val_task.ts b/src/module/task/lib/val_task.ts index eaea013..0412ddc 100644 --- a/src/module/task/lib/val_task.ts +++ b/src/module/task/lib/val_task.ts @@ -1,4 +1,16 @@ import { hookstate } from "@hookstate/core"; import { IFormMemberTask } from "./type_task"; -export const globalMemberTask = hookstate([]); \ No newline at end of file +export const globalMemberTask = hookstate([]); +export const globalRefreshTask = hookstate(false); + +export const valStatusDetailTask = [ + { + name: "Belum Dikerjakan", + value: 0 + }, + { + name: "Selesai", + value: 1 + } +] \ No newline at end of file diff --git a/src/module/task/ui/detail_list_tugas_task.tsx b/src/module/task/ui/detail_list_tugas_task.tsx index 7218595..4170bbe 100644 --- a/src/module/task/ui/detail_list_tugas_task.tsx +++ b/src/module/task/ui/detail_list_tugas_task.tsx @@ -1,18 +1,29 @@ 'use client' -import { WARNA } from "@/module/_global" -import { Box, Grid, Center, Checkbox, Group, SimpleGrid, Text } from "@mantine/core" +import { LayoutDrawer, WARNA } from "@/module/_global" +import { Box, Grid, Center, Checkbox, Group, SimpleGrid, Text, Stack, Flex, Divider } from "@mantine/core" import { useShallowEffect } from "@mantine/hooks" -import { useParams } from "next/navigation" +import { useParams, useRouter } from "next/navigation" import toast from "react-hot-toast" -import { AiOutlineFileSync } from "react-icons/ai" -import { funGetTaskDivisionById } from "../lib/api_task" +import { AiOutlineFileDone, AiOutlineFileSync } from "react-icons/ai" +import { funDeleteDetailTask, funGetTaskDivisionById, funUpdateStatusDetailTask } from "../lib/api_task" import { useState } from "react" import { IDataListTaskDivision } from "../lib/type_task" +import { FaCheck, FaPencil, FaTrash } from "react-icons/fa6" +import LayoutModal from "@/module/_global/layout/layout_modal" +import { globalRefreshTask, valStatusDetailTask } from "../lib/val_task" +import { useHookstate } from "@hookstate/core" export default function ListTugasDetailTask() { + const [openDrawer, setOpenDrawer] = useState(false) + const [openDrawerStatus, setOpenDrawerStatus] = useState(false) + const [isOpenModal, setOpenModal] = useState(false) const [isData, setData] = useState([]) const [loading, setLoading] = useState(true) const param = useParams<{ id: string, detail: string }>() + const [idData, setIdData] = useState('') + const [statusData, setStatusData] = useState(0) + const router = useRouter() + const refresh = useHookstate(globalRefreshTask) async function getOneData() { try { setLoading(true) @@ -35,6 +46,45 @@ export default function ListTugasDetailTask() { getOneData(); }, [param.detail]) + + async function onDelete() { + try { + const res = await funDeleteDetailTask(idData); + if (res.success) { + toast.success(res.message); + refresh.set(true) + getOneData(); + setIdData("") + setOpenDrawer(false) + } else { + toast.error(res.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal menghapus tugas divisi, coba lagi nanti"); + } + + } + + async function onUpdateStatus(val: number) { + try { + const res = await funUpdateStatusDetailTask(idData, { status: val }); + if (res.success) { + toast.success(res.message); + refresh.set(true) + getOneData(); + setIdData("") + setOpenDrawerStatus(false) + setOpenDrawer(false) + } else { + toast.error(res.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal mengubah status tugas divisi, coba lagi nanti"); + } + } + return ( @@ -53,7 +103,13 @@ export default function ListTugasDetailTask() { isData.length === 0 ? Tidak ada tugas : isData.map((item, index) => { return ( - + { + setIdData(item.id) + setStatusData(item.status) + setOpenDrawer(true) + }} + >
@@ -109,8 +165,92 @@ export default function ListTugasDetailTask() { ) }) } - + + setOpenDrawer(false)}> + + + + { setOpenDrawerStatus(true) }} justify={'center'} align={'center'} direction={'column'} > + + + + + Update status + + + + { router.push('edit/' + idData) }} justify={'center'} align={'center'} direction={'column'} > + + + + + Edit tugas + + + + { setOpenModal(true) }} justify={'center'} align={'center'} direction={'column'} > + + + + + Hapus tugas + + + + + + + + setOpenModal(false)} + description="Apakah Anda yakin ingin menghapus tugas ini?" + onYes={(val) => { + if (val) { + onDelete() + } + setOpenModal(false) + }} /> + + + setOpenDrawerStatus(false)}> + + + { + valStatusDetailTask.map((item, index) => { + return ( + { onUpdateStatus(item.value) }}> + + + + {item.name} + + + + {statusData === item.value ? : ""} + + + + + ) + }) + } + + + + ) } \ No newline at end of file diff --git a/src/module/task/ui/detail_progress_task.tsx b/src/module/task/ui/detail_progress_task.tsx index 51fb1b6..af14cbb 100644 --- a/src/module/task/ui/detail_progress_task.tsx +++ b/src/module/task/ui/detail_progress_task.tsx @@ -6,12 +6,16 @@ import { useParams } from "next/navigation"; import toast from "react-hot-toast"; import { HiMiniPresentationChartBar } from "react-icons/hi2"; import { funGetTaskDivisionById } from "../lib/api_task"; -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { globalRefreshTask } from "../lib/val_task"; +import { useHookstate } from "@hookstate/core"; export default function ProgressDetailTask() { const [valProgress, setValProgress] = useState(0) const [valLastUpdate, setValLastUpdate] = useState('') const param = useParams<{ id: string, detail: string }>() + const refresh = useHookstate(globalRefreshTask) + async function getOneData() { try { const res = await funGetTaskDivisionById(param.detail, 'progress'); @@ -28,6 +32,19 @@ export default function ProgressDetailTask() { } } + function onRefresh() { + if (refresh.get()) { + getOneData() + refresh.set(false) + } + } + + + + useEffect(() => { + onRefresh() + }, [refresh.get()]) + useShallowEffect(() => { getOneData(); }, [param.detail]) @@ -66,7 +83,7 @@ export default function ProgressDetailTask() { size="xl" value={valProgress} /> - {valLastUpdate} + {/* Update terakhir : {valLastUpdate} */} diff --git a/src/module/task/ui/edit_detail_task.tsx b/src/module/task/ui/edit_detail_task.tsx new file mode 100644 index 0000000..23717f6 --- /dev/null +++ b/src/module/task/ui/edit_detail_task.tsx @@ -0,0 +1,162 @@ +"use client"; +import { LayoutNavbarNew, WARNA } from "@/module/_global"; +import { + Avatar, + Box, + Button, + Flex, + Group, + Input, + SimpleGrid, + Stack, + Text, +} from "@mantine/core"; +import React, { useState } from "react"; +import { DatePicker } from "@mantine/dates"; +import { useParams, useRouter } from "next/navigation"; +import toast from "react-hot-toast"; +import moment from "moment"; +import { funEditDetailTask, funGetDetailTask } from "../lib/api_task"; +import { useShallowEffect } from "@mantine/hooks"; +import LayoutModal from "@/module/_global/layout/layout_modal"; + + +export default function EditDetailTask() { + const [value, setValue] = useState<[Date | null, Date | null]>([null, null]); + const router = useRouter() + const [title, setTitle] = useState("") + const param = useParams<{ id: string, detail: string }>() + const [openModal, setOpenModal] = useState(false) + + async function onSubmit() { + if (value[0] == null || value[1] == null) + return toast.error("Error! harus memilih tanggal") + + if (title == "") + return toast.error("Error! harus memasukkan judul tugas") + + try { + const res = await funEditDetailTask(param.detail, { + title: title, + dateStart: value[0], + dateEnd: value[1], + }) + + if (res.success) { + toast.success(res.message); + } else { + toast.error(res.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal edit detail tugas divisi, coba lagi nanti"); + } + + } + + async function getOneData() { + try { + const res = await funGetDetailTask(param.detail); + if (res.success) { + setTitle(res.data.title) + setValue([ + new Date(moment(res.data.dateStart).format('YYYY-MM-DD')), + new Date(moment(res.data.dateEnd).format('YYYY-MM-DD')), + ]) + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan detail tugas divisi, coba lagi nanti"); + } + } + + useShallowEffect(() => { + getOneData(); + }, [param.detail]) + + + return ( + + + + + + + + + Tanggal Mulai + + {value[0] ? `${moment(value[0]).format('DD-MM-YYYY')}` : ""} + + + + Tanggal Berakhir + + {value[1] ? `${moment(value[1]).format('DD-MM-YYYY')}` : ""} + + + + + { setTitle(e.target.value) }} + /> + + + + + + + setOpenModal(false)} + description="Apakah Anda yakin ingin mengubah data?" + onYes={(val) => { + if (val) { + onSubmit() + } + setOpenModal(false) + }} /> + + ); +} diff --git a/src/module/task/ui/navbar_detail_division_task.tsx b/src/module/task/ui/navbar_detail_division_task.tsx index e06b78c..512d4da 100644 --- a/src/module/task/ui/navbar_detail_division_task.tsx +++ b/src/module/task/ui/navbar_detail_division_task.tsx @@ -41,7 +41,7 @@ export default function NavbarDetailDivisionTask() { size="lg" radius="lg" aria-label="Settings" - onClick={() => router.push("/task/update/1")} + onClick={() => router.push("update/clzwclyjc00072sqq4sbr5iz4")} >