From c45a55628d18cf05caef3274d1f264021fe8b6e8 Mon Sep 17 00:00:00 2001 From: amel Date: Mon, 2 Sep 2024 10:38:25 +0800 Subject: [PATCH 1/3] upd: create task divisi Deskripsi: - tambah file No Issues --- .gitignore | 3 +- .../(fitur-division)/task/create/page.tsx | 4 +- src/app/api/task/route.ts | 92 +++++++++---------- src/module/task/ui/create_task.tsx | 14 +-- 4 files changed, 52 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index ab67372..cdedd88 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ yarn-error.log* next-env.d.ts # folder foto kandidat -/public/image/ \ No newline at end of file +/public/image/ +/public/file/ \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/create/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/create/page.tsx index e6ce908..543ecc5 100644 --- a/src/app/(application)/division/[id]/(fitur-division)/task/create/page.tsx +++ b/src/app/(application)/division/[id]/(fitur-division)/task/create/page.tsx @@ -2,8 +2,8 @@ import { CreateTask, FileSave } from "@/module/task"; function Page({ searchParams }: { searchParams: any }) { - if (searchParams.page == "file-save") - return + // if (searchParams.page == "file-save") + // return return } diff --git a/src/app/api/task/route.ts b/src/app/api/task/route.ts index 5439f42..af82503 100644 --- a/src/app/api/task/route.ts +++ b/src/app/api/task/route.ts @@ -136,7 +136,7 @@ export async function POST(request: Request) { if (member.length > 0) { const dataMember = member.map((v: any) => ({ - ..._.omit(v, ["idUser", "name"]), + ..._.omit(v, ["idUser", "name", "img"]), idDivision: idDivision, idProject: data.id, idUser: v.idUser, @@ -147,66 +147,56 @@ export async function POST(request: Request) { }) } + + let fileFix: any[] = [] - // if (cekFile) { - // let a = 0 - // const root = path.join(process.cwd(), "./public/image/user/"); - // for (var pair of body.entries()) { - // if (String(pair[0]) == "file" + a) { - // const file = body.get(pair[0]) as File - // const fName = file.name - // const fExt = fName.split(".").pop() - - // console.log(file, file.name) - - // const insertToContainer = await prisma.containerFileDivision.create({ - // data:{ - // idDivision: idDivision, - // name: fName, - // extension: String(fExt) - // } - // }) - - // const dataFile = { - // name: fName, - // extension: fExt, - // idDivision: idDivision, - // } + if (cekFile) { + let a = 0 + const root = path.join(process.cwd(), "./public/file/task/"); + for (var pair of body.entries()) { + if (String(pair[0]) == "file" + a) { + const file = body.get(pair[0]) as File + const fName = file.name.split(".")[0] + const fExt = file.name.split(".").pop() - // fileFix.push(dataFile) - // } - // a++ - // } + const insertToContainer = await prisma.containerFileDivision.create({ + data: { + idDivision: idDivision, + name: fName, + extension: String(fExt) + }, + select: { + id: true + } + }) - // const insertFile = await prisma.divisionProjectFile.createMany({ - // data: fileFix - // }) - // } + const nameFix = insertToContainer.id + '.' + fExt + const filePath = path.join(root, nameFix) + // Konversi ArrayBuffer ke Buffer + const buffer = Buffer.from(await file.arrayBuffer()); + // Tulis file ke sistem + fs.writeFileSync(filePath, buffer); - // if (file.length > 0) { - // file.map((v: any, index: any) => { - // const f: any = file[index].get('file') - // const fName = f.name - // const fExt = fName.split(".").pop() + const dataFile = { + idProject: data.id, + idDivision: idDivision, + idFile: insertToContainer.id, + createdBy: user.id, + } - // const dataFile = { - // name: fName, - // extension: fExt, - // idDivision: idDivision, - // idProject: data.id, - // } - // fileFix.push(dataFile) - // }) + fileFix.push(dataFile) + } + a++ + } - // const insertFile = await prisma.divisionProjectFile.createMany({ - // data: fileFix - // }) - - // } + const insertFile = await prisma.divisionProjectFile.createMany({ + data: fileFix + }) + } return NextResponse.json({ success: true, message: "Berhasil membuat tugas divisi" }, { status: 200 }); diff --git a/src/module/task/ui/create_task.tsx b/src/module/task/ui/create_task.tsx index 058aa47..4bc237a 100644 --- a/src/module/task/ui/create_task.tsx +++ b/src/module/task/ui/create_task.tsx @@ -74,11 +74,11 @@ export default function CreateTask() { if (response.success) { toast.success(response.message) - // setTitle("") - // member.set([]) - // setFileForm([]) - // setListFile([]) - // setDataTask([]) + setTitle("") + member.set([]) + setFileForm([]) + setListFile([]) + setDataTask([]) } else { toast.error(response.message) } @@ -322,7 +322,7 @@ export default function CreateTask() { diperangkat - router.push("/task/create?page=file-save")}> + {/* router.push("/task/create?page=file-save")}> sudah ada - + */} From d8d0911afaa74caea39b59f1044202dccf5f0db4 Mon Sep 17 00:00:00 2001 From: amel Date: Mon, 2 Sep 2024 11:20:42 +0800 Subject: [PATCH 2/3] upd: task file Deskripsi: - hapus file task divisi No Issues --- src/app/api/task/file/[id]/route.ts | 71 +++++++++++++++++ src/module/task/lib/api_task.ts | 10 +++ src/module/task/ui/create_task.tsx | 2 +- src/module/task/ui/detail_list_file_task.tsx | 81 ++++++++++++++++++-- 4 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 src/app/api/task/file/[id]/route.ts diff --git a/src/app/api/task/file/[id]/route.ts b/src/app/api/task/file/[id]/route.ts new file mode 100644 index 0000000..f61d76b --- /dev/null +++ b/src/app/api/task/file/[id]/route.ts @@ -0,0 +1,71 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; +import fs from "fs"; + +// HAPUS DETAIL FILE, HAPUS FILE DI ASSETS DAN DATABASE (BUKAN PAKE ISACTIVE) +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.divisionProjectFile.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus file gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataRelasi = await prisma.divisionProjectFile.findUnique({ + where: { + id: id, + } + }) + + const dataFile = await prisma.containerFileDivision.findUnique({ + where: { + id: dataRelasi?.idFile + } + }) + + fs.unlink(`./public/file/task/${dataFile?.id}.${dataFile?.extension}`, (err) => { }) + + const deleteRelasi = await prisma.divisionProjectFile.delete({ + where: { + id: id, + }, + }); + + const deleteFile = await prisma.containerFileDivision.delete({ + where: { + id: dataRelasi?.idFile, + }, + }); + + + return NextResponse.json( + { + success: true, + message: "File berhasil dihapus", + data, + }, + { status: 200 } + ); + + } catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Gagal menghapus file, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ 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 089ee99..fa5bafd 100644 --- a/src/module/task/lib/api_task.ts +++ b/src/module/task/lib/api_task.ts @@ -119,4 +119,14 @@ export const funEditTask = async (path: string, data: { title: string }) => { body: JSON.stringify(data), }); return await response.json().catch(() => null); +}; + +export const funDeleteFileTask = async (path: string) => { + const response = await fetch(`/api/task/file/${path}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + return await response.json().catch(() => null); }; \ No newline at end of file diff --git a/src/module/task/ui/create_task.tsx b/src/module/task/ui/create_task.tsx index 4bc237a..c69c8c2 100644 --- a/src/module/task/ui/create_task.tsx +++ b/src/module/task/ui/create_task.tsx @@ -287,7 +287,7 @@ export default function CreateTask() { onClose={() => setOpenDrawer(false)} title={"Pilih File"} > - + { diff --git a/src/module/task/ui/detail_list_file_task.tsx b/src/module/task/ui/detail_list_file_task.tsx index 937d0f3..92d68d8 100644 --- a/src/module/task/ui/detail_list_file_task.tsx +++ b/src/module/task/ui/detail_list_file_task.tsx @@ -1,18 +1,24 @@ 'use client' -import { SkeletonDetailListTugasTask, WARNA } from "@/module/_global"; -import { Box, Group, Skeleton, Text } from "@mantine/core"; +import { LayoutDrawer, SkeletonDetailListTugasTask, WARNA } from "@/module/_global"; +import { Box, Flex, 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 { BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from "react-icons/bs"; -import { funGetTaskDivisionById } from "../lib/api_task"; +import { BsFileTextFill, BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from "react-icons/bs"; +import { funDeleteFileTask, funGetTaskDivisionById } from "../lib/api_task"; import { IDataFileTaskDivision } from "../lib/type_task"; +import { FaTrash } from "react-icons/fa6"; +import LayoutModal from "@/module/_global/layout/layout_modal"; export default function ListFileDetailTask() { const [isData, setData] = useState([]) const [loading, setLoading] = useState(true) const param = useParams<{ id: string, detail: string }>() + const [openDrawer, setOpenDrawer] = useState(false) + const [isOpenModal, setOpenModal] = useState(false) + const [idData, setIdData] = useState('') + const [nameData, setNameData] = useState('') async function getOneData() { try { setLoading(true) @@ -34,6 +40,25 @@ export default function ListFileDetailTask() { getOneData(); }, [param.detail]) + + async function onDelete() { + try { + const res = await funDeleteFileTask(idData); + if (res.success) { + toast.success(res.message) + getOneData() + setIdData("") + setOpenDrawer(false) + } else { + toast.error(res.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal menghapus file, coba lagi nanti"); + } + + } + return ( File @@ -67,6 +92,12 @@ export default function ListFileDetailTask() { padding: 10 }} mb={10} + + onClick={() => { + setNameData(item.name + '.' + item.extension) + setIdData(item.id) + setOpenDrawer(true) + }} > {item.extension == "pdf" && } @@ -74,13 +105,53 @@ export default function ListFileDetailTask() { {item.extension == "png" && } {item.extension == "jpg" || item.extension == "jpeg" && } {item.extension == "heic" && } - {item.name} + {item.name + '.' + item.extension} ) }) } + + + + setOpenDrawer(false)}> + + + + { }} justify={'center'} align={'center'} direction={'column'} > + + + + + Lihat file + + + + { setOpenModal(true) }} justify={'center'} align={'center'} direction={'column'} > + + + + + Hapus file + + + + + + + + + setOpenModal(false)} + description="Apakah Anda yakin ingin menghapus file ini? File yang dihapus tidak dapat dikembalikan" + onYes={(val) => { + if (val) { + onDelete() + } + setOpenModal(false) + }} /> ) } \ No newline at end of file From 0d389e417d2c85063a66464aa470dfc9475f4580 Mon Sep 17 00:00:00 2001 From: amel Date: Mon, 2 Sep 2024 13:37:32 +0800 Subject: [PATCH 3/3] upd : task divisi Deskripsi: - update task detail divisi No Issues --- .../task/[detail]/add-file/page.tsx | 7 + src/app/api/task/file/[id]/route.ts | 164 +++++++++++++++ src/module/task/index.ts | 4 +- src/module/task/lib/api_task.ts | 17 +- src/module/task/ui/add_file_detail_task.tsx | 189 ++++++++++++++++++ .../task/ui/navbar_detail_division_task.tsx | 25 ++- 6 files changed, 400 insertions(+), 6 deletions(-) create mode 100644 src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-file/page.tsx create mode 100644 src/module/task/ui/add_file_detail_task.tsx diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-file/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-file/page.tsx new file mode 100644 index 0000000..a18e3be --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-file/page.tsx @@ -0,0 +1,7 @@ +import { AddFileDetailTask } from "@/module/task"; + +export default function Page() { + return ( + + ) +} \ No newline at end of file diff --git a/src/app/api/task/file/[id]/route.ts b/src/app/api/task/file/[id]/route.ts index f61d76b..73eebbd 100644 --- a/src/app/api/task/file/[id]/route.ts +++ b/src/app/api/task/file/[id]/route.ts @@ -3,6 +3,7 @@ import { funGetUserByCookies } from "@/module/auth"; import _ from "lodash"; import { NextResponse } from "next/server"; import fs from "fs"; +import path from "path"; // HAPUS DETAIL FILE, HAPUS FILE DI ASSETS DAN DATABASE (BUKAN PAKE ISACTIVE) export async function DELETE(request: Request, context: { params: { id: string } }) { @@ -68,4 +69,167 @@ export async function DELETE(request: Request, context: { params: { id: string } console.log(error); return NextResponse.json({ success: false, message: "Gagal menghapus file, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); } +} + + +// TAMBAH FILE TASK DIVISI +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 body = await request.formData() + const cekFile = body.has("file0") + + const dataCek = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (dataCek == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah file tugas gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataProject = await prisma.divisionProject.findUnique({ + where: { + id: id, + }, + }); + + + let fileFix: any[] = [] + + if (cekFile) { + let a = 0 + const root = path.join(process.cwd(), "./public/file/task/"); + for (var pair of body.entries()) { + if (String(pair[0]) == "file" + a) { + const file = body.get(pair[0]) as File + const fName = file.name.split(".")[0] + const fExt = file.name.split(".").pop() + + + const insertToContainer = await prisma.containerFileDivision.create({ + data: { + idDivision: String(dataProject?.idDivision), + name: fName, + extension: String(fExt) + }, + select: { + id: true + } + }) + + const nameFix = insertToContainer.id + '.' + fExt + const filePath = path.join(root, nameFix) + // Konversi ArrayBuffer ke Buffer + const buffer = Buffer.from(await file.arrayBuffer()); + // Tulis file ke sistem + fs.writeFileSync(filePath, buffer); + + + const dataFile = { + idProject: id, + idDivision: dataProject?.idDivision, + idFile: insertToContainer.id, + createdBy: user.id, + } + + + fileFix.push(dataFile) + } + a++ + } + + const insertFile = await prisma.divisionProjectFile.createMany({ + data: fileFix + }) + } + + + return NextResponse.json({ success: true, message: "Berhasil membuat tugas divisi" }, { status: 200 }); + + } catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Gagal membuat tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// CEK FILE TASK DIVISI APAKAH PERNAH DIUPLOAD PADA TASK YG SAMA +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 body = await request.formData() + const file = body.get("file") as File + const fileName = file.name + + const dataCek = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (dataCek == 0) { + return NextResponse.json( + { + success: false, + message: "Upload file gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataTaskFile = await prisma.divisionProjectFile.findMany({ + where: { + idProject: id, + isActive: true + }, + select: { + ContainerFileDivision: { + select: { + name: true, + extension: true + } + } + } + }) + + const dataOmit = dataTaskFile.map((v: any) => ({ + ..._.omit(v, ["ContainerFileDivision"]), + file: v.ContainerFileDivision.name + '.' + v.ContainerFileDivision.extension, + })) + + + const cek = dataOmit.some((i: any) => i.file == fileName) + + + if (cek) { + return NextResponse.json({ success: true, message: "Cek berhasil" }, { status: 200 }); + } else { + return NextResponse.json({ success: false, message: "File sudah pernah diupload" }, { status: 400 }); + } + + } catch (error) { + console.log(error); + return NextResponse.json({ success: false, message: "Upload file gagal, 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 159e6b9..fcaa02e 100644 --- a/src/module/task/index.ts +++ b/src/module/task/index.ts @@ -1,4 +1,5 @@ import AddDetailTask from "./ui/add_detail_task"; +import AddFileDetailTask from "./ui/add_file_detail_task"; import AddMemberDetailTask from "./ui/add_member_detail_task"; import CancelTask from "./ui/cancel_task"; import ViewDateEndTask from "./ui/create_date_end_task"; @@ -30,4 +31,5 @@ export { EditDetailTask } export { AddDetailTask } export { AddMemberDetailTask } export { CancelTask } -export { EditTask } \ No newline at end of file +export { EditTask } +export { AddFileDetailTask } \ 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 fa5bafd..afe2fac 100644 --- a/src/module/task/lib/api_task.ts +++ b/src/module/task/lib/api_task.ts @@ -7,7 +7,6 @@ export const funGetAllTask = async (path?: string) => { export const funCreateTask = async (data: FormData) => { - const response = await fetch("/api/task", { method: "POST", body: data, @@ -129,4 +128,20 @@ export const funDeleteFileTask = async (path: string) => { }, }); return await response.json().catch(() => null); +}; + +export const funCekNamFileUploadTask = async (path: string, data: FormData) => { + const response = await fetch(`/api/task/file/${path}`, { + method: "PUT", + body: data, + }); + return await response.json().catch(() => null); +}; + +export const funAddFileTask = async (path: string, data: FormData) => { + const response = await fetch(`/api/task/file/${path}`, { + method: "POST", + body: data, + }); + return await response.json().catch(() => null); }; \ No newline at end of file diff --git a/src/module/task/ui/add_file_detail_task.tsx b/src/module/task/ui/add_file_detail_task.tsx new file mode 100644 index 0000000..bbbe383 --- /dev/null +++ b/src/module/task/ui/add_file_detail_task.tsx @@ -0,0 +1,189 @@ +"use client"; +import { LayoutDrawer, LayoutNavbarNew, WARNA } from "@/module/_global"; +import { + Box, + Button, + Flex, + Group, + rem, + SimpleGrid, + Stack, + Text, +} from "@mantine/core"; +import React, { useRef, useState } from "react"; +import { useParams, useRouter } from "next/navigation"; +import toast from "react-hot-toast"; +import { IoIosArrowDropright } from "react-icons/io"; +import { Dropzone } from "@mantine/dropzone"; +import _ from "lodash"; +import { IListFileTask } from "../lib/type_task"; +import ResultsFile from "./results_file"; +import { FaTrash } from "react-icons/fa6"; +import { funAddFileTask, funCekNamFileUploadTask } from "../lib/api_task"; + + +export default function AddFileDetailTask() { + const router = useRouter() + const [title, setTitle] = useState("") + const [openModal, setOpenModal] = useState(false) + const [fileForm, setFileForm] = useState([]) + const [listFile, setListFile] = useState([]) + const param = useParams<{ id: string, detail: string }>() + const [indexDelFile, setIndexDelFile] = useState(0) + const [openDrawerFile, setOpenDrawerFile] = useState(false) + const openRef = useRef<() => void>(null) + + function deleteFile(index: number) { + setListFile([...listFile.filter((val, i) => i !== index)]) + setFileForm([...fileForm.filter((val, i) => i !== index)]) + setOpenDrawerFile(false) + } + + + async function cekFileName(data: any) { + try { + const fd = new FormData(); + fd.append(`file`, data); + const res = await funCekNamFileUploadTask(param.detail, fd) + if (res.success) { + setFileForm([...fileForm, data]) + setListFile([...listFile, { name: data.name, extension: data.type.split("/")[1] }]) + } else { + toast.error(res.message) + } + } catch (error) { + console.log(error) + toast.error("Gagal menambahkan file, coba lagi nanti") + } + } + + async function onSubmit() { + try { + const fd = new FormData(); + for (let i = 0; i < fileForm.length; i++) { + fd.append(`file${i}`, fileForm[i]); + } + + const response = await funAddFileTask(param.detail, fd) + if (response.success) { + toast.success(response.message) + setFileForm([]) + setListFile([]) + router.push(`/division/${param.id}/task/${param.detail}`) + } else { + toast.error(response.message) + } + } catch (error) { + console.log(error) + toast.error("Gagal menambahkan tugas divisi, coba lagi nanti"); + } + } + + + + return ( + + + + + + { + if (!files || _.isEmpty(files)) + return toast.error('Tidak ada file yang dipilih') + cekFileName(files[0]) + }} + activateOnClick={false} + maxSize={3 * 1024 ** 2} + accept={['text/csv', 'image/png', 'image/jpeg', 'image/heic', 'application/pdf']} + onReject={(files) => { + return toast.error('File yang diizinkan: .csv, .png, .jpg, .heic, .pdf dengan ukuran maksimal 3 MB') + }} + > + + openRef.current?.()} + > + Upload File + + + + { + listFile.length > 0 && + + File + + { + listFile.map((v, i) => { + return ( + { + setIndexDelFile(i) + setOpenDrawerFile(true) + }}> + + + ) + }) + } + + + } + + + + + + + + setOpenDrawerFile(false)} + title={""} + > + + + deleteFile(indexDelFile)}> + + + + + Hapus File + + + + + + + + ); +} diff --git a/src/module/task/ui/navbar_detail_division_task.tsx b/src/module/task/ui/navbar_detail_division_task.tsx index 438f1ee..f7f2356 100644 --- a/src/module/task/ui/navbar_detail_division_task.tsx +++ b/src/module/task/ui/navbar_detail_division_task.tsx @@ -8,7 +8,7 @@ import { funGetTaskDivisionById } from "../lib/api_task"; import { useShallowEffect } from "@mantine/hooks"; import { HiMenu } from "react-icons/hi"; import { IoAddCircle } from "react-icons/io5"; -import { FaPencil, FaUsers } from "react-icons/fa6"; +import { FaFileCirclePlus, FaPencil, FaUsers } from "react-icons/fa6"; import { MdCancel } from "react-icons/md"; export default function NavbarDetailDivisionTask() { @@ -95,13 +95,15 @@ export default function NavbarDetailDivisionTask() { style={{ cursor: 'pointer' }} - onClick={() => { router.push(param.detail + '/cancel') }} + onClick={() => { + router.push(param.detail + '/add-file') + }} > - + - Batal + Tambah file @@ -118,6 +120,21 @@ export default function NavbarDetailDivisionTask() { Edit + + { router.push(param.detail + '/cancel') }} + > + + + + + Batal + + +