From be0cd94d8df4845d5a0dd2af2de299ca53ecb0b2 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Wed, 6 May 2026 12:32:34 +0800 Subject: [PATCH] feat: tambah API lampiran file pada tugas kegiatan dan tugas divisi --- src/app/api/mobile/project/[id]/route.ts | 20 +- .../mobile/project/task/file/[id]/route.ts | 198 +++++++++++++++++ src/app/api/mobile/task/[id]/route.ts | 21 +- .../api/mobile/task/tugas/file/[id]/route.ts | 210 ++++++++++++++++++ 4 files changed, 445 insertions(+), 4 deletions(-) create mode 100644 src/app/api/mobile/project/task/file/[id]/route.ts create mode 100644 src/app/api/mobile/task/tugas/file/[id]/route.ts diff --git a/src/app/api/mobile/project/[id]/route.ts b/src/app/api/mobile/project/[id]/route.ts index 9f0152a..c082437 100644 --- a/src/app/api/mobile/project/[id]/route.ts +++ b/src/app/api/mobile/project/[id]/route.ts @@ -68,7 +68,18 @@ export async function GET(request: Request, context: { params: { id: string } }) status: true, dateStart: true, dateEnd: true, - createdAt: true + createdAt: true, + ProjectTaskFile: { + where: { isActive: true }, + select: { + ProjectFile: { + select: { + name: true, + extension: true + } + } + } + } }, orderBy: { dateStart: 'asc' @@ -76,12 +87,15 @@ export async function GET(request: Request, context: { params: { id: string } }) }) const formatData = dataProgress.map((v: any) => ({ - ..._.omit(v, ["dateStart", "dateEnd", "createdAt"]), + ..._.omit(v, ["dateStart", "dateEnd", "createdAt", "ProjectTaskFile"]), dateStart: moment(v.dateStart).format("DD-MM-YYYY"), dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"), createdAt: moment(v.createdAt).format("DD-MM-YYYY HH:mm"), + files: v.ProjectTaskFile.map((tf: any) => ({ + name: tf.ProjectFile.name, + extension: tf.ProjectFile.extension + })) })) - // const dataFix = _.orderBy(formatData, 'createdAt', 'asc') allData = formatData } else if (kategori == "file") { diff --git a/src/app/api/mobile/project/task/file/[id]/route.ts b/src/app/api/mobile/project/task/file/[id]/route.ts new file mode 100644 index 0000000..b8b79a7 --- /dev/null +++ b/src/app/api/mobile/project/task/file/[id]/route.ts @@ -0,0 +1,198 @@ +import { DIR, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserById } from "@/module/auth"; +import { createLogUserMobile } from "@/module/user"; +import { NextResponse } from "next/server"; + +// GET: daftar file yang terlampir pada ProjectTask +// [id] = ProjectTask.id +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { searchParams } = new URL(request.url); + const userMobile = searchParams.get("user"); + + const user = await funGetUserById({ id: String(userMobile) }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const data = await prisma.projectTaskFile.findMany({ + where: { + idTask: id, + isActive: true, + }, + select: { + id: true, + ProjectFile: { + select: { + id: true, + name: true, + extension: true, + idStorage: true, + }, + }, + }, + orderBy: { createdAt: "asc" }, + }); + + const result = data.map((v) => ({ + id: v.id, // ProjectTaskFile.id — dipakai untuk DELETE + idFile: v.ProjectFile.id, // ProjectFile.id — dipakai untuk filter duplikat di picker + name: v.ProjectFile.name, + extension: v.ProjectFile.extension, + idStorage: v.ProjectFile.idStorage, + })); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan file tugas", data: result }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan file tugas (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +} + +// POST: upload file baru ke ProjectTask +// Membuat ProjectFile baru lalu membuat ProjectTaskFile (junction) +// [id] = ProjectTask.id +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const body = await request.formData(); + const data = JSON.parse(body.get("data") as string); + + const user = await funGetUserById({ id: data.user }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const task = await prisma.projectTask.findUnique({ + where: { id }, + select: { id: true, idProject: true }, + }); + + if (!task) { + return NextResponse.json({ success: false, message: "Tugas tidak ditemukan" }, { status: 200 }); + } + + const hasCekFile = body.has("file0"); + if (!hasCekFile) { + return NextResponse.json({ success: false, message: "Tidak ada file yang dikirim" }, { status: 200 }); + } + + body.delete("data"); + for (const [key] of body.entries()) { + if (!key.startsWith("file")) continue; + + const file = body.get(key) as File; + const fExt = file.name.split(".").pop(); + const fName = file.name.replace("." + fExt, ""); + + const upload = await funUploadFile({ file, dirId: DIR.project }); + if (!upload.success) continue; + + const projectFile = await prisma.projectFile.create({ + data: { + idProject: task.idProject, + name: fName, + extension: String(fExt), + idStorage: upload.data.id, + }, + select: { id: true }, + }); + + await prisma.projectTaskFile.create({ + data: { + idTask: id, + idFile: projectFile.id, + }, + }); + } + + await createLogUserMobile({ act: "CREATE", desc: "User menambah file pada tugas kegiatan", table: "projectTask", data: id, user: user.id }); + + return NextResponse.json({ success: true, message: "Berhasil menambahkan file" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan file (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +} + +// PATCH: link ProjectFile yang sudah ada ke ProjectTask +// Body: { user, idFile } — idFile = ProjectFile.id +// [id] = ProjectTask.id +export async function PATCH(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { user: userId, idFile } = await request.json(); + + const user = await funGetUserById({ id: String(userId) }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const task = await prisma.projectTask.findUnique({ + where: { id }, + select: { id: true }, + }); + if (!task) { + return NextResponse.json({ success: false, message: "Tugas tidak ditemukan" }, { status: 200 }); + } + + const file = await prisma.projectFile.findUnique({ + where: { id: idFile }, + select: { id: true }, + }); + if (!file) { + return NextResponse.json({ success: false, message: "File tidak ditemukan" }, { status: 200 }); + } + + // cek apakah sudah pernah di-link + const existing = await prisma.projectTaskFile.findFirst({ + where: { idTask: id, idFile, isActive: true }, + }); + if (existing) { + return NextResponse.json({ success: false, message: "File sudah terlampir pada tugas ini" }, { status: 200 }); + } + + await prisma.projectTaskFile.create({ + data: { idTask: id, idFile }, + }); + + await createLogUserMobile({ act: "CREATE", desc: "User melampirkan file kegiatan ke tugas", table: "projectTask", data: id, user: user.id }); + + return NextResponse.json({ success: true, message: "Berhasil melampirkan file" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal melampirkan file (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +} + +// DELETE: hapus lampiran file dari ProjectTask (hapus junction record saja) +// [id] = ProjectTaskFile.id +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { user: userId } = await request.json(); + + const user = await funGetUserById({ id: String(userId) }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const junction = await prisma.projectTaskFile.findUnique({ + where: { id }, + select: { id: true, idTask: true }, + }); + if (!junction) { + return NextResponse.json({ success: false, message: "Data tidak ditemukan" }, { status: 200 }); + } + + await prisma.projectTaskFile.delete({ where: { id } }); + + await createLogUserMobile({ act: "DELETE", desc: "User menghapus lampiran file dari tugas kegiatan", table: "projectTask", data: junction.idTask, user: user.id }); + + return NextResponse.json({ success: true, message: "Berhasil menghapus lampiran file" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus lampiran file (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +} diff --git a/src/app/api/mobile/task/[id]/route.ts b/src/app/api/mobile/task/[id]/route.ts index 7f285c6..60456ed 100644 --- a/src/app/api/mobile/task/[id]/route.ts +++ b/src/app/api/mobile/task/[id]/route.ts @@ -74,6 +74,21 @@ export async function GET(request: Request, context: { params: { id: string } }) status: true, dateStart: true, dateEnd: true, + DivisionProjectTaskFile: { + where: { isActive: true }, + select: { + DivisionProjectFile: { + select: { + ContainerFileDivision: { + select: { + name: true, + extension: true, + }, + }, + }, + }, + }, + }, }, orderBy: { dateStart: 'asc' @@ -81,9 +96,13 @@ export async function GET(request: Request, context: { params: { id: string } }) }) const fix = dataProgress.map((v: any) => ({ - ..._.omit(v, ["dateStart", "dateEnd"]), + ..._.omit(v, ["dateStart", "dateEnd", "DivisionProjectTaskFile"]), dateStart: moment(v.dateStart).format("DD-MM-YYYY"), dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"), + files: v.DivisionProjectTaskFile.map((tf: any) => ({ + name: tf.DivisionProjectFile.ContainerFileDivision.name, + extension: tf.DivisionProjectFile.ContainerFileDivision.extension, + })), })) allData = fix diff --git a/src/app/api/mobile/task/tugas/file/[id]/route.ts b/src/app/api/mobile/task/tugas/file/[id]/route.ts new file mode 100644 index 0000000..b9f7365 --- /dev/null +++ b/src/app/api/mobile/task/tugas/file/[id]/route.ts @@ -0,0 +1,210 @@ +import { DIR, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserById } from "@/module/auth"; +import { createLogUserMobile } from "@/module/user"; +import { NextResponse } from "next/server"; + +// GET: daftar file yang terlampir pada DivisionProjectTask +// [id] = DivisionProjectTask.id +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { searchParams } = new URL(request.url); + const userMobile = searchParams.get("user"); + + const user = await funGetUserById({ id: String(userMobile) }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const data = await prisma.divisionProjectTaskFile.findMany({ + where: { + idTask: id, + isActive: true, + }, + select: { + id: true, + DivisionProjectFile: { + select: { + id: true, + ContainerFileDivision: { + select: { + name: true, + extension: true, + idStorage: true, + }, + }, + }, + }, + }, + orderBy: { createdAt: "asc" }, + }); + + const result = data.map((v) => ({ + id: v.id, + idFile: v.DivisionProjectFile.id, + name: v.DivisionProjectFile.ContainerFileDivision.name, + extension: v.DivisionProjectFile.ContainerFileDivision.extension, + idStorage: v.DivisionProjectFile.ContainerFileDivision.idStorage, + })); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan file tugas", data: result }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan file tugas (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +} + +// POST: upload file baru ke DivisionProjectTask +// [id] = DivisionProjectTask.id +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const body = await request.formData(); + const data = JSON.parse(body.get("data") as string); + + const user = await funGetUserById({ id: data.user }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const task = await prisma.divisionProjectTask.findUnique({ + where: { id }, + select: { id: true, idProject: true, idDivision: true }, + }); + + if (!task) { + return NextResponse.json({ success: false, message: "Tugas tidak ditemukan" }, { status: 200 }); + } + + const hasCekFile = body.has("file0"); + if (!hasCekFile) { + return NextResponse.json({ success: false, message: "Tidak ada file yang dikirim" }, { status: 200 }); + } + + body.delete("data"); + for (const [key] of body.entries()) { + if (!key.startsWith("file")) continue; + + const file = body.get(key) as File; + const fExt = file.name.split(".").pop(); + const fName = file.name.replace("." + fExt, ""); + + const upload = await funUploadFile({ file, dirId: DIR.task }); + if (!upload.success) continue; + + const container = await prisma.containerFileDivision.create({ + data: { + idDivision: task.idDivision, + name: fName, + extension: String(fExt), + idStorage: upload.data.id, + }, + select: { id: true }, + }); + + const divFile = await prisma.divisionProjectFile.create({ + data: { + idProject: task.idProject, + idDivision: task.idDivision, + idFile: container.id, + createdBy: user.id, + }, + select: { id: true }, + }); + + await prisma.divisionProjectTaskFile.create({ + data: { + idTask: id, + idFile: divFile.id, + }, + }); + } + + await createLogUserMobile({ act: "CREATE", desc: "User menambah file pada tugas divisi", table: "divisionProjectTask", data: id, user: user.id }); + + return NextResponse.json({ success: true, message: "Berhasil menambahkan file" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan file (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +} + +// PATCH: link DivisionProjectFile yang sudah ada ke DivisionProjectTask +// Body: { user, idFile } — idFile = DivisionProjectFile.id +// [id] = DivisionProjectTask.id +export async function PATCH(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { user: userId, idFile } = await request.json(); + + const user = await funGetUserById({ id: String(userId) }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const task = await prisma.divisionProjectTask.findUnique({ + where: { id }, + select: { id: true }, + }); + if (!task) { + return NextResponse.json({ success: false, message: "Tugas tidak ditemukan" }, { status: 200 }); + } + + const file = await prisma.divisionProjectFile.findUnique({ + where: { id: idFile }, + select: { id: true }, + }); + if (!file) { + return NextResponse.json({ success: false, message: "File tidak ditemukan" }, { status: 200 }); + } + + const existing = await prisma.divisionProjectTaskFile.findFirst({ + where: { idTask: id, idFile, isActive: true }, + }); + if (existing) { + return NextResponse.json({ success: false, message: "File sudah terlampir pada tugas ini" }, { status: 200 }); + } + + await prisma.divisionProjectTaskFile.create({ + data: { idTask: id, idFile }, + }); + + await createLogUserMobile({ act: "CREATE", desc: "User melampirkan file divisi ke tugas", table: "divisionProjectTask", data: id, user: user.id }); + + return NextResponse.json({ success: true, message: "Berhasil melampirkan file" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal melampirkan file (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +} + +// DELETE: hapus lampiran file dari DivisionProjectTask (hapus junction record saja) +// [id] = DivisionProjectTaskFile.id +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { user: userId } = await request.json(); + + const user = await funGetUserById({ id: String(userId) }); + if (!user.id || user.id === "null") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const junction = await prisma.divisionProjectTaskFile.findUnique({ + where: { id }, + select: { id: true, idTask: true }, + }); + if (!junction) { + return NextResponse.json({ success: false, message: "Data tidak ditemukan" }, { status: 200 }); + } + + await prisma.divisionProjectTaskFile.delete({ where: { id } }); + + await createLogUserMobile({ act: "DELETE", desc: "User menghapus lampiran file dari tugas divisi", table: "divisionProjectTask", data: junction.idTask, user: user.id }); + + return NextResponse.json({ success: true, message: "Berhasil menghapus lampiran file" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus lampiran file (error: 500)", reason: (error as Error).message }, { status: 500 }); + } +}