From ed12cc087a04d8f7ff04cc53a07a709d96790b54 Mon Sep 17 00:00:00 2001 From: amel Date: Fri, 16 Aug 2024 15:55:09 +0800 Subject: [PATCH] upd: tambah task divisi Deskripsi: - tambah dropzone - update struktur database - create task No Issuess --- package.json | 2 +- prisma/schema.prisma | 34 ++-- public/assets/{ => file}/.gitkeep | 0 src/app/api/task/route.ts | 103 ++++++++++- src/module/_global/fun/upload-file.ts | 6 + src/module/_global/index.ts | 4 +- src/module/task/lib/api_task.ts | 19 +- src/module/task/lib/type_task.ts | 14 ++ src/module/task/ui/create_task.tsx | 204 +++++++++++++++++++--- src/module/task/ui/list_division_task.tsx | 9 +- src/module/task/ui/results_file.tsx | 46 ++--- yarn.lock | 8 +- 12 files changed, 368 insertions(+), 81 deletions(-) rename public/assets/{ => file}/.gitkeep (100%) create mode 100644 src/module/_global/fun/upload-file.ts diff --git a/package.json b/package.json index 89d446e..c053950 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@mantine/code-highlight": "^7.11.0", "@mantine/core": "^7.11.0", "@mantine/dates": "^7.11.1", - "@mantine/dropzone": "^7.11.0", + "@mantine/dropzone": "^7.12.1", "@mantine/form": "^7.11.0", "@mantine/hooks": "^7.11.0", "@mantine/modals": "^7.11.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index af6264d..3ccc229 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -298,18 +298,18 @@ model DivisionProjectMember { } model DivisionProjectFile { - id String @id @default(cuid()) - Division Division @relation(fields: [idDivision], references: [id]) - idDivision String - DivisionProject DivisionProject @relation(fields: [idProject], references: [id]) - idProject String - name String - extension String - isActive Boolean @default(true) - User User @relation(fields: [createdBy], references: [id]) - createdBy String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + DivisionProject DivisionProject @relation(fields: [idProject], references: [id]) + idProject String + ContainerFileDivision ContainerFileDivision @relation(fields: [idFile], references: [id]) + idFile String + isActive Boolean @default(true) + User User @relation(fields: [createdBy], references: [id]) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model DivisionDisscussion { @@ -424,3 +424,13 @@ model ContainerImage { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model ContainerFileDivision { + id String @id @default(cuid()) + name String + extension String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + DivisionProjectFile DivisionProjectFile[] +} diff --git a/public/assets/.gitkeep b/public/assets/file/.gitkeep similarity index 100% rename from public/assets/.gitkeep rename to public/assets/file/.gitkeep diff --git a/src/app/api/task/route.ts b/src/app/api/task/route.ts index 28da9ee..e5939da 100644 --- a/src/app/api/task/route.ts +++ b/src/app/api/task/route.ts @@ -1,8 +1,9 @@ -import { prisma } from "@/module/_global"; +import { funUploadFile, prisma } from "@/module/_global"; import { funGetUserByCookies } from "@/module/auth"; import _, { ceil } from "lodash"; import { NextResponse } from "next/server"; + // GET ALL DATA TUGAS DIVISI export async function GET(request: Request) { try { @@ -67,10 +68,108 @@ export async function GET(request: Request) { member: v.DivisionProjectMember.length })) - return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data, }, { status: 200 }); + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: formatData, }, { status: 200 }); } catch (error) { console.log(error); return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); } +} + + +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 { title, task, member, file, idDivision } = (await request.json()); + + const cek = await prisma.division.count({ + where: { + isActive: true, + id: idDivision + } + }) + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal, data divisi tidak ditemukan", }, { status: 404 }); + } + + const data = await prisma.divisionProject.create({ + data: { + idDivision: idDivision, + title: title, + desc: '' + }, + select: { + id: true + } + }) + + + if (task.length > 0) { + const dataTask = task.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd", "title"]), + idDivision: idDivision, + idProject: data.id, + title: v.title, + dateStart: new Date(v.dateStart), + dateEnd: new Date(v.dateEnd), + })) + + const insertTask = await prisma.divisionProjectTask.createMany({ + data: dataTask + }) + } + + if (member.length > 0) { + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name"]), + idDivision: idDivision, + idProject: data.id, + idUser: v.idUser, + })) + + const insertMember = await prisma.divisionProjectMember.createMany({ + data: dataMember + }) + } + + let fileFix: any[] = [] + + console.log("amalia",file) + + 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() + // funUploadFile(fName, f) + + const dataFile = { + name: fName, + extension: fExt, + idDivision: idDivision, + idProject: data.id, + } + + fileFix.push(dataFile) + }) + + console.log(fileFix) + 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 divisiii, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } } \ No newline at end of file diff --git a/src/module/_global/fun/upload-file.ts b/src/module/_global/fun/upload-file.ts new file mode 100644 index 0000000..96035fb --- /dev/null +++ b/src/module/_global/fun/upload-file.ts @@ -0,0 +1,6 @@ +'use server' +import fs from 'fs' +export async function funUploadFile(fName: any, f: any) { + const filenya = Buffer.from(await f.arrayBuffer()) + fs.writeFileSync(`./public/assets/file/${fName}`, filenya) +} \ No newline at end of file diff --git a/src/module/_global/index.ts b/src/module/_global/index.ts index 1786a69..16554ed 100644 --- a/src/module/_global/index.ts +++ b/src/module/_global/index.ts @@ -3,6 +3,7 @@ import { pwd_key_config } from "./bin/val_global"; import SkeletonDetailDiscussionComment from "./components/skeleton_detail_discussion_comment"; import SkeletonDetailDiscussionMember from "./components/skeleton_detail_discussion_member"; import SkeletonSingle from "./components/skeleton_single"; +import { funUploadFile } from "./fun/upload-file"; import { WARNA } from "./fun/WARNA"; import LayoutDrawer from "./layout/layout_drawer"; import LayoutIconBack from "./layout/layout_icon_back"; @@ -24,4 +25,5 @@ export { prisma }; export { pwd_key_config }; export { SkeletonSingle } export { SkeletonDetailDiscussionComment } -export { SkeletonDetailDiscussionMember } \ No newline at end of file +export { SkeletonDetailDiscussionMember } +export { funUploadFile } \ 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 59e21ca..d2995ce 100644 --- a/src/module/task/lib/api_task.ts +++ b/src/module/task/lib/api_task.ts @@ -1,4 +1,21 @@ +import { IFormTaskDivision } from "./type_task"; + export const funGetAllTask = async (path?: string) => { const response = await fetch(`/api/task${(path) ? path : ''}`, { next: { tags: ['task'] } }); return await response.json().catch(() => null); -} \ No newline at end of file +} + + +export const funCreateTask = async (data: IFormTaskDivision) => { + if (data.title.length < 3) + return { success: false, message: 'Nama proyek minimal 3 karakter' } + + const response = await fetch("/api/task", { + 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/type_task.ts b/src/module/task/lib/type_task.ts index 4521724..998fb18 100644 --- a/src/module/task/lib/type_task.ts +++ b/src/module/task/lib/type_task.ts @@ -17,4 +17,18 @@ export interface IFormDateTask { dateStart: Date, dateEnd: Date, title: string +} + + +export interface IFormTaskDivision { + idDivision: string + title: string + task: IFormDateTask[] | [] + member: IFormMemberTask[] | [] + file: FormData[] | [] +} + +export interface IListFileTask { + name: string, + extension: string } \ 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 1e30cff..dd20f0b 100644 --- a/src/module/task/ui/create_task.tsx +++ b/src/module/task/ui/create_task.tsx @@ -1,8 +1,8 @@ "use client"; import { LayoutDrawer, LayoutNavbarNew, WARNA } from "@/module/_global"; -import { Avatar, Box, Button, Center, Flex, Group, Input, Stack, Text } from "@mantine/core"; -import { useRouter } from "next/navigation"; -import React, { useState } from "react"; +import { Avatar, Box, Button, Center, Flex, Group, Input, SimpleGrid, Stack, Text } from "@mantine/core"; +import { useParams, useRouter } from "next/navigation"; +import React, { useRef, useState } from "react"; import { IoIosArrowDropright } from "react-icons/io"; import { BsFiletypeCsv } from "react-icons/bs"; import CreateUsersProject from "./create_users_project"; @@ -11,21 +11,72 @@ import ResultsFile from "./results_file"; import { useHookstate } from "@hookstate/core"; import { globalMemberTask } from "../lib/val_task"; import ViewDateEndTask from "./create_date_end_task"; -import { IFormDateTask } from "../lib/type_task"; +import { IFormDateTask, IFormMemberTask, IListFileTask } from "../lib/type_task"; +import { Dropzone } from '@mantine/dropzone'; +import toast from "react-hot-toast"; +import _ from "lodash"; +import { FaTrash } from "react-icons/fa6"; +import LayoutModal from "@/module/_global/layout/layout_modal"; +import { funCreateTask } from "../lib/api_task"; export default function CreateTask() { - const router = useRouter(); + const router = useRouter() + const param = useParams<{ id: string }>() const [openDrawer, setOpenDrawer] = useState(false) + const [openDrawerFile, setOpenDrawerFile] = useState(false) + const [openDrawerTask, setOpenDrawerTask] = useState(false) const [openMember, setOpenMember] = useState(false) const [openTugas, setOpenTugas] = useState(false) + const [openModal, setOpenModal] = useState(false) const member = useHookstate(globalMemberTask) + const memberValue = member.get() as IFormMemberTask[] const [dataTask, setDataTask] = useState([]) + const openRef = useRef<() => void>(null) + const [fileForm, setFileForm] = useState([]) + const [listFile, setListFile] = useState([]) + const [indexDelFile, setIndexDelFile] = useState(0) + const [indexDelTask, setIndexDelTask] = useState(0) + const [title, setTitle] = useState("") + + function deleteFile(index: number) { + setListFile([...listFile.filter((val, i) => i !== index)]) + setFileForm([...fileForm.filter((val, i) => i !== index)]) + setOpenDrawerFile(false) + } + + function deleteTask(index: number) { + setDataTask([...dataTask.filter((val, i) => i !== index)]) + setOpenDrawerTask(false) + } + + + async function onSubmit() { + try { + console.log("kirim",fileForm) + const response = await funCreateTask({ idDivision: param.id, title, task: dataTask, file: fileForm, member: memberValue }) + + if (response.success) { + toast.success(response.message) + setTitle("") + member.set([]) + setFileForm([]) + setListFile([]) + setDataTask([]) + } else { + toast.error(response.message) + } + } catch (error) { + console.log(error) + toast.error("Gagal menambahkan tugas divisi, coba lagi nanti"); + } + } if (openTugas) return { setDataTask([...dataTask, val]) setOpenTugas(false) }} />; + if (openMember) return setOpenMember(false)} /> @@ -43,6 +94,8 @@ export default function CreateTask() { }} placeholder="Nama Proyek" size="md" + value={title} + onChange={(e) => setTitle(e.target.value)} /> { setOpenTugas(true) }}> { return ( - + { + setIndexDelTask(i) + setOpenDrawerTask(true) + }}> ) @@ -99,7 +155,31 @@ export default function CreateTask() { } - {/* */} + { + listFile.length > 0 && + + File + + { + listFile.map((v, i) => { + return ( + { + setIndexDelFile(i) + setOpenDrawerFile(true) + }}> + + + ) + }) + } + + + } + { member.length > 0 && @@ -148,7 +228,7 @@ export default function CreateTask() { - @@ -156,30 +236,49 @@ export default function CreateTask() { + {/* Drawer pilih file */} setOpenDrawer(false)} title={"Pilih File"} > - ""}> - -
- -
+ { + if (!files || _.isEmpty(files)) + return toast.error('Tidak ada file yang dipilih') + const fd = new FormData(); + fd.append("file", files[0]); + setFileForm([...fileForm, fd]) + setListFile([...listFile, { name: files[0].name, extension: files[0].type.split("/")[1] }]) + }} + 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?.()}> + +
+ +
+
+ + Pilih file + + diperangkat
- - Pilih file - - diperangkat -
+ router.push("/task/create?page=file-save")}>
+ + + + {/* Drawer hapus file */} + setOpenDrawerFile(false)} + title={""} + > + + + deleteFile(indexDelFile)}> + + + + + Hapus File + + + + + + + + {/* Drawer hapus tugas */} + setOpenDrawerTask(false)} + title={""} + > + + + deleteTask(indexDelTask)}> + + + + + Hapus Tugas + + + + + + + + + + setOpenModal(false)} + description="Apakah Anda yakin ingin menambahkan data?" + onYes={(val) => { + if (val) { + onSubmit() + } + setOpenModal(false) + }} />
); } diff --git a/src/module/task/ui/list_division_task.tsx b/src/module/task/ui/list_division_task.tsx index c5dd0be..19eeedc 100644 --- a/src/module/task/ui/list_division_task.tsx +++ b/src/module/task/ui/list_division_task.tsx @@ -166,7 +166,7 @@ export default function ListDivisionTask() { {isData.map((v: any, i: any) => { return ( - router.push(`/task/${v.id}`)}> + router.push(`task/${v.id}`)}> @@ -176,18 +176,17 @@ export default function ListDivisionTask() { - - {(status == 'segera') ? 0 : (status == 'dikerjakan') ? 42 : (status == 'selesai') ? 100 : 0}% + + {v.status}% {v.desc} - {status} - +5 + +{v.member-1} diff --git a/src/module/task/ui/results_file.tsx b/src/module/task/ui/results_file.tsx index 1a3c0d3..2d024a1 100644 --- a/src/module/task/ui/results_file.tsx +++ b/src/module/task/ui/results_file.tsx @@ -1,38 +1,24 @@ import { WARNA } from '@/module/_global'; import { Box, Group, Text } from '@mantine/core'; import React from 'react'; -import { BsFiletypeCsv } from 'react-icons/bs'; +import { BsFiletypeCsv, BsFiletypeHeic, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from 'react-icons/bs'; +import { IListFileTask } from '../lib/type_task'; -export default function ResultsFile() { +export default function ResultsFile({ name, extension }: IListFileTask) { return ( - - File - - - - - Proyek Laporan Permasyarakatan - - - - - - Proyek Laporan Permasyarakatan - - - + + + {extension == "pdf" && } + {extension == "csv" && } + {extension == "png" && } + {extension == "jpg" || extension == "jpeg" && } + {extension == "heic" && } + {name} + ); } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b8fcbd8..f58af9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -207,10 +207,10 @@ dependencies: clsx "^2.1.1" -"@mantine/dropzone@^7.11.0": - version "7.11.1" - resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-7.11.1.tgz#cfe10acf0f9761f2e852107b2bb5dbbe48336b39" - integrity sha512-Jvn+ikhT1GtQq3EMAYWK+1lk6CVDeB1Ykok7oUjTWPZDYAcQkLDNfPpFLqrbPPYSMav0u67zzaq9fUVTYVVhew== +"@mantine/dropzone@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-7.12.1.tgz#bd1c3b365261d3e2eb265e7a7bd165ef951271d4" + integrity sha512-IuAdCnl6PDtkDnGp4vQlHgxr9z3R7s0685khVKpxy/3f+XfdoswUBBY3X7XyirpDXMIjMD4SLpkIzwuUXgZsag== dependencies: react-dropzone-esm "15.0.1"