From b075dae4b3cf228572d7a8563ba85c9549183a84 Mon Sep 17 00:00:00 2001 From: amal Date: Thu, 7 Aug 2025 17:37:13 +0800 Subject: [PATCH 01/14] upd: api mobile Deskripsi: - update api diskusi umum dan diskusi divisi> pada post komentar fdm push notifikasi No Issues --- src/app/api/mobile/discussion-general/[id]/comment/route.ts | 6 ++++-- src/app/api/mobile/discussion/[id]/comment/route.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/api/mobile/discussion-general/[id]/comment/route.ts b/src/app/api/mobile/discussion-general/[id]/comment/route.ts index da842a6..6df4622 100644 --- a/src/app/api/mobile/discussion-general/[id]/comment/route.ts +++ b/src/app/api/mobile/discussion-general/[id]/comment/route.ts @@ -70,7 +70,9 @@ export async function POST(request: Request, context: { params: { id: string } } } }) - const dataFCM = member.map((v: any) => ({ + const memberFilter = member.filter((v: any) => v.idUser != userMobile.id) + + const dataFCM = memberFilter.map((v: any) => ({ ..._.omit(v, ["idUser", "User", "Subscribe", "TokenDeviceUser"]), tokens: v.User.TokenDeviceUser.map((v: any) => v.token) })) @@ -106,7 +108,7 @@ export async function POST(request: Request, context: { params: { id: string } } token: tokenUnique, title: "Komentar Baru", body: `${userSent?.name}: ${data.comment}`, - data: { id: data.id, category: "discussion", content: id } + data: { id: data.id, category: "discussion-general", content: id } }) // create log user diff --git a/src/app/api/mobile/discussion/[id]/comment/route.ts b/src/app/api/mobile/discussion/[id]/comment/route.ts index 52216af..e1b0fbc 100644 --- a/src/app/api/mobile/discussion/[id]/comment/route.ts +++ b/src/app/api/mobile/discussion/[id]/comment/route.ts @@ -86,7 +86,9 @@ export async function POST(request: Request, context: { params: { id: string } } } }) - const dataFCM = member.map((v: any) => ({ + const memberFilter = member.filter((v: any) => v.idUser != userMobile.id) + + const dataFCM = memberFilter.map((v: any) => ({ ..._.omit(v, ["idUser", "User", "Subscribe", "TokenDeviceUser"]), tokens: v.User.TokenDeviceUser.map((v: any) => v.token) })) @@ -122,7 +124,7 @@ export async function POST(request: Request, context: { params: { id: string } } token: tokenUnique, title: "Komentar Baru", body: `${userSent?.name}: ${comment}`, - data: { id: data.id, category: `division/${dataDivision?.idDivision}/discussion/`, content: id } + data: { id: data.id, category: `division/${dataDivision?.idDivision}/discussion`, content: id } }) // create log user From b0dca49e04a21fb5aaef9f839d2084f766f3bb70 Mon Sep 17 00:00:00 2001 From: amal Date: Wed, 13 Aug 2025 16:27:38 +0800 Subject: [PATCH 02/14] upd: link upload Deskripsi: - update struktur db - api tambah link pada projet - api deelete link pada project - api get link pada project - api tambah link pada tugas divisi - api delete link pada tugas divisi - api get link pada tugas divisi - tampilan modal tambah link pada project dan tugas divisi - tampilan list link pada project dan tugas divisi - tampilan modal detail link pada project dan tugas divisi No Issues --- prisma/schema.prisma | 25 ++ .../(fitur-division)/task/[detail]/page.tsx | 3 +- src/app/(application)/project/[id]/page.tsx | 4 +- src/app/api/project/[id]/link/route.ts | 90 +++++++ src/app/api/project/[id]/route.ts | 11 + src/app/api/task/[id]/link/route.ts | 95 +++++++ src/app/api/task/[id]/route.ts | 12 + src/lib/urlCompleted.ts | 7 + src/module/project/index.ts | 4 +- src/module/project/lib/api_project.ts | 24 +- src/module/project/lib/type_project.ts | 10 +- .../project/ui/list_link_detail_project.tsx | 231 ++++++++++++++++++ .../project/ui/navbar_detail_project.tsx | 94 ++++++- src/module/task/index.ts | 4 +- src/module/task/lib/api_task.ts | 25 +- src/module/task/lib/type_task.ts | 7 +- src/module/task/ui/detail_list_link_task.tsx | 230 +++++++++++++++++ .../task/ui/navbar_detail_division_task.tsx | 97 +++++++- 18 files changed, 955 insertions(+), 18 deletions(-) create mode 100644 src/app/api/project/[id]/link/route.ts create mode 100644 src/app/api/task/[id]/link/route.ts create mode 100644 src/lib/urlCompleted.ts create mode 100644 src/module/project/ui/list_link_detail_project.tsx create mode 100644 src/module/task/ui/detail_list_link_task.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9421ad0..f111c5b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -202,6 +202,7 @@ model Project { ProjectFile ProjectFile[] ProjectComment ProjectComment[] ProjectTask ProjectTask[] + ProjectLink ProjectLink[] } model ProjectMember { @@ -228,6 +229,16 @@ model ProjectFile { updatedAt DateTime @updatedAt } +model ProjectLink { + id String @id @default(cuid()) + Project Project @relation(fields: [idProject], references: [id]) + idProject String + link String @db.Text + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + model ProjectTask { id String @id @default(cuid()) Project Project @relation(fields: [idProject], references: [id]) @@ -280,6 +291,7 @@ model Division { DivisionCalendar DivisionCalendar[] DivisionCalendarReminder DivisionCalendarReminder[] ContainerFileDivision ContainerFileDivision[] + DivisionProjectLink DivisionProjectLink[] } model DivisionMember { @@ -309,6 +321,19 @@ model DivisionProject { DivisionProjectTask DivisionProjectTask[] DivisionProjectMember DivisionProjectMember[] DivisionProjectFile DivisionProjectFile[] + DivisionProjectLink DivisionProjectLink[] +} + +model DivisionProjectLink { + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + DivisionProject DivisionProject @relation(fields: [idProject], references: [id]) + idProject String + link String @db.Text + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model DivisionProjectTask { diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx index 293102f..37e2386 100644 --- a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx @@ -1,4 +1,4 @@ -import { NavbarDetailDivisionTask, ProgressDetailTask, ListTugasDetailTask, ListFileDetailTask, ListAnggotaDetailTask } from "@/module/task" +import { ListAnggotaDetailTask, ListFileDetailTask, ListLinkDetailTask, ListTugasDetailTask, NavbarDetailDivisionTask, ProgressDetailTask } from "@/module/task" import { Box } from "@mantine/core" function Page() { @@ -9,6 +9,7 @@ function Page() { + diff --git a/src/app/(application)/project/[id]/page.tsx b/src/app/(application)/project/[id]/page.tsx index 26dfc03..c54659b 100644 --- a/src/app/(application)/project/[id]/page.tsx +++ b/src/app/(application)/project/[id]/page.tsx @@ -1,6 +1,5 @@ -import { ListAnggotaDetailProject, ListFileDetailProject, ListTugasDetailProject, NavbarDetailProject, ProgressDetailProject } from '@/module/project'; +import { ListAnggotaDetailProject, ListFileDetailProject, ListLinkDetailProject, ListTugasDetailProject, NavbarDetailProject, ProgressDetailProject } from '@/module/project'; import { Box } from '@mantine/core'; -import React from 'react'; function Page() { return ( @@ -10,6 +9,7 @@ function Page() { + diff --git a/src/app/api/project/[id]/link/route.ts b/src/app/api/project/[id]/link/route.ts new file mode 100644 index 0000000..a133801 --- /dev/null +++ b/src/app/api/project/[id]/link/route.ts @@ -0,0 +1,90 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import { NextResponse } from "next/server"; + +// ADD LINK 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 { link } = (await request.json()) + + const data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + + const insertLink = await prisma.projectLink.create({ + data: { + idProject: id, + link: link + } + }) + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambah link kegiatan', table: 'projectLink', data: insertLink.id }) + return NextResponse.json({ success: true, message: "Berhasil menambahkan link kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah link kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// DELETE LINK 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 { idLink } = (await request.json()) + + const data = await prisma.projectLink.count({ + where: { + id: idLink + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan link, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const deleteLink = await prisma.projectLink.delete({ + where: { + id: idLink + } + }) + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus link kegiatan', table: 'projectLink', data: String(idLink) }) + return NextResponse.json({ success: true, message: "Berhasil menghapus link kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus link kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} diff --git a/src/app/api/project/[id]/route.ts b/src/app/api/project/[id]/route.ts index 19bc836..ec851c0 100644 --- a/src/app/api/project/[id]/route.ts +++ b/src/app/api/project/[id]/route.ts @@ -133,6 +133,17 @@ export async function GET(request: Request, context: { params: { id: string } }) })) allData = fix + } else if (kategori == "link") { + const dataLink = await prisma.projectLink.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'asc' + } + }) + allData = dataLink } return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: allData, }, { status: 200 }); diff --git a/src/app/api/task/[id]/link/route.ts b/src/app/api/task/[id]/link/route.ts new file mode 100644 index 0000000..8f9146c --- /dev/null +++ b/src/app/api/task/[id]/link/route.ts @@ -0,0 +1,95 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import { NextResponse } from "next/server"; + +// ADD LINK 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 { link, idDivision } = (await request.json()); + + const data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah link tugas gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + + + const insertlink = await prisma.divisionProjectLink.create({ + data: { + idProject: id, + link, + idDivision, + } + }) + + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambahkan link tugas divisi', table: 'divisionProjectLink', data: insertlink.id }) + + + return NextResponse.json({ success: true, message: "Berhasil menambahkan link tugas", }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah link tugas, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// DELETE LINK TASK DIVISI +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 { idLink } = (await request.json()) + + const data = await prisma.divisionProjectLink.count({ + where: { + id: idLink + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan link, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const deleteLink = await prisma.divisionProjectLink.delete({ + where: { + id: idLink + } + }) + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus link tugas divisi', table: 'divisionProjectLink', data: String(idLink) }) + return NextResponse.json({ success: true, message: "Berhasil menghapus link tugas divisi" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus link tugas divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ 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 07e9dc2..dbc2ef1 100644 --- a/src/app/api/task/[id]/route.ts +++ b/src/app/api/task/[id]/route.ts @@ -150,6 +150,18 @@ export async function GET(request: Request, context: { params: { id: string } }) })) allData = fix + } else if (kategori == "link") { + const dataLink = await prisma.divisionProjectLink.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'asc' + } + }) + + allData = dataLink } return NextResponse.json({ success: true, message: "Berhasil mendapatkan tugas divisi", data: allData }, { status: 200 }); diff --git a/src/lib/urlCompleted.ts b/src/lib/urlCompleted.ts new file mode 100644 index 0000000..2af7aad --- /dev/null +++ b/src/lib/urlCompleted.ts @@ -0,0 +1,7 @@ +export function urlCompleted(url: string) { + if (url.startsWith("http://") || url.startsWith("https://")) { + return url + } else { + return `https://${url}` + } +} \ No newline at end of file diff --git a/src/module/project/index.ts b/src/module/project/index.ts index 1c33e04..e4b0d43 100644 --- a/src/module/project/index.ts +++ b/src/module/project/index.ts @@ -15,6 +15,7 @@ import AddMemberDetailProject from "./ui/add_member_detail_project"; import CreateProject from "./ui/create_project"; import AddFileDetailProject from "./ui/add_file_detail_project"; import WrapLayoutProject from "./ui/wrap_project"; +import ListLinkDetailProject from "./ui/list_link_detail_project"; export { ViewDateEndTask } export { CreateUsersProject } @@ -31,4 +32,5 @@ export { CancelProject } export { AddMemberDetailProject } export { CreateProject } export { AddFileDetailProject } -export { WrapLayoutProject } \ No newline at end of file +export { WrapLayoutProject } +export { ListLinkDetailProject } \ No newline at end of file diff --git a/src/module/project/lib/api_project.ts b/src/module/project/lib/api_project.ts index 255faae..b64563b 100644 --- a/src/module/project/lib/api_project.ts +++ b/src/module/project/lib/api_project.ts @@ -1,4 +1,4 @@ -import { IFormAddDetailproject, IFormAddMemberProject, IFormDateProject, NewIFormDateProject } from "./type_project"; +import { IFormAddDetailproject, IFormAddMemberProject, NewIFormDateProject } from "./type_project"; export const funGetAllProject = async (path?: string) => { @@ -149,6 +149,17 @@ export const funAddFileProject = async (path: string, data: FormData) => { return await response.json().catch(() => null); }; +export const funDeleteLinkProject = async (path: string, data: { idLink: string }) => { + const response = await fetch(`/api/project/${path}/link`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +}; + export const funDeleteProject = async (path: string) => { const response = await fetch(`/api/project/${path}/lainnya`, { method: "DELETE", @@ -159,3 +170,14 @@ export const funDeleteProject = async (path: string) => { return await response.json().catch(() => null); }; + +export const funAddLinkProject = async (path: string, data: {link: string}) => { + const response = await fetch(`/api/project/${path}/link`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +} diff --git a/src/module/project/lib/type_project.ts b/src/module/project/lib/type_project.ts index 8c1bb4c..4b26867 100644 --- a/src/module/project/lib/type_project.ts +++ b/src/module/project/lib/type_project.ts @@ -20,7 +20,13 @@ export interface IDataFileProject { id: string name: string extension: string - idStorage:string + idStorage: string +} + + +export interface IDataLinkProject { + id: string + link: string } export interface IDataMemberProject { @@ -46,7 +52,7 @@ export interface IFormDateProject { title: string, } -export interface NewIFormDateProject{ +export interface NewIFormDateProject { dateStart: string, dateEnd: string, title: string, diff --git a/src/module/project/ui/list_link_detail_project.tsx b/src/module/project/ui/list_link_detail_project.tsx new file mode 100644 index 0000000..eb2440b --- /dev/null +++ b/src/module/project/ui/list_link_detail_project.tsx @@ -0,0 +1,231 @@ +'use client' +import { urlCompleted } from '@/lib/urlCompleted'; +import { globalRole, keyWibu, LayoutDrawer, TEMA } from '@/module/_global'; +import LayoutModal from '@/module/_global/layout/layout_modal'; +import { useHookstate } from '@hookstate/core'; +import { Box, Flex, Grid, Group, SimpleGrid, Stack, Text } from '@mantine/core'; +import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; +import { useParams } from 'next/navigation'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { FaTrash } from 'react-icons/fa6'; +import { LuLink } from 'react-icons/lu'; +import { RiExternalLinkLine } from 'react-icons/ri'; +import { useWibuRealtime } from 'wibu-realtime'; +import { funDeleteLinkProject, funGetOneProjectById } from '../lib/api_project'; +import { IDataLinkProject } from '../lib/type_project'; +import { globalIsMemberProject } from '../lib/val_project'; + +export default function ListLinkDetailProject() { + const [isData, setData] = useState([]) + const param = useParams<{ id: string }>() + const [loading, setLoading] = useState(true) + const [loadingDelete, setLoadingDelete] = useState(false) + const [idData, setIdData] = useState('') + const [linkData, setLinkData] = useState('') + const [openDrawer, setOpenDrawer] = useState(false) + const [isOpenModal, setOpenModal] = useState(false) + const tema = useHookstate(TEMA) + const roleLogin = useHookstate(globalRole) + const memberProject = useHookstate(globalIsMemberProject) + const isMobile = useMediaQuery("(max-width: 350px)"); + const [reason, setReason] = useState("") + const [dataRealTime, setDataRealtime] = useWibuRealtime({ + WIBU_REALTIME_TOKEN: keyWibu, + project: "sdm" + }) + + async function getOneDataCancel() { + try { + const res = await funGetOneProjectById(param.id, 'data'); + if (res.success) { + setReason(res.data.reason); + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan data Kegiatan, coba lagi nanti"); + } + } + + useShallowEffect(() => { + getOneDataCancel(); + }, [param.id]) + + async function getOneData(loading: boolean) { + try { + setLoading(loading) + const res = await funGetOneProjectById(param.id, 'link'); + if (res.success) { + setData(res.data) + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan link kegiatan, coba lagi nanti"); + } finally { + setLoading(false) + } + } + + useShallowEffect(() => { + getOneData(true); + }, [param.id]) + + + async function onDelete() { + try { + setLoadingDelete(true) + const res = await funDeleteLinkProject(param.id, { idLink: idData }); + if (res.success) { + setDataRealtime([{ + category: "project-detail-link", + id: param.id, + }]) + toast.success(res.message) + getOneData(false) + setIdData("") + setOpenDrawer(false) + } else { + toast.error(res.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal menghapus link, coba lagi nanti"); + } finally { + setOpenModal(false) + setLoadingDelete(false) + } + + } + + useShallowEffect(() => { + if (dataRealTime && dataRealTime.some((i: any) => i.category == 'project-detail-link' && i.id == param.id)) { + getOneData(false) + } else if (dataRealTime && dataRealTime.some((i: any) => i.category == 'project-detail-status' && i.id == param.id)) { + getOneDataCancel() + } + }, [dataRealTime]) + + return ( + <> + { + isData.length > 0 + && + + Link + + { + isData.map((item, index) => { + return ( + { + setLinkData(item.link) + setIdData(item.id) + setOpenDrawer(true) + }} + > + + + + + + {item.link} + + + + + + ) + }) + } + + + + Menu} onClose={() => setOpenDrawer(false)}> + + + + { + { window.open(urlCompleted(linkData), '_blank', 'noopener,noreferrer') }} justify={'center'} align={'center'} direction={'column'} > + + + + + Buka Link + + + } + + { + (roleLogin.get() == "user" || roleLogin.get() == "coadmin") && !memberProject.get() ? <> + : + { + reason == null ? + setOpenModal(true) + : setOpenModal(false) + }} justify={'center'} align={'center'} direction={'column'} > + + + + + Hapus + + + } + + + + + + + setOpenModal(false)} + description="Apakah Anda yakin ingin menghapus link ini? Link yang dihapus tidak dapat dikembalikan" + onYes={(val) => { + if (val) { + onDelete() + } else { + setOpenModal(false) + } + }} /> + + + } + + ); +} + diff --git a/src/module/project/ui/navbar_detail_project.tsx b/src/module/project/ui/navbar_detail_project.tsx index f3ff1ea..bbe5d7b 100644 --- a/src/module/project/ui/navbar_detail_project.tsx +++ b/src/module/project/ui/navbar_detail_project.tsx @@ -3,7 +3,7 @@ import { globalRole, keyWibu, LayoutDrawer, LayoutNavbarNew, TEMA } from '@/modu import LayoutModal from '@/module/_global/layout/layout_modal'; import { funGetUserByCookies } from '@/module/auth'; import { useHookstate } from '@hookstate/core'; -import { ActionIcon, Box, Flex, SimpleGrid, Stack, Text } from '@mantine/core'; +import { ActionIcon, Box, Button, Flex, Grid, Modal, SimpleGrid, Stack, Text, TextInput } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -11,9 +11,10 @@ import toast from 'react-hot-toast'; import { FaFileCirclePlus, FaPencil, FaTrash, FaUsers } from 'react-icons/fa6'; import { HiMenu } from 'react-icons/hi'; import { IoAddCircle } from 'react-icons/io5'; +import { LuLink } from 'react-icons/lu'; import { MdCancel } from 'react-icons/md'; import { useWibuRealtime } from 'wibu-realtime'; -import { funDeleteProject, funGetOneProjectById } from '../lib/api_project'; +import { funAddLinkProject, funDeleteProject, funGetOneProjectById } from '../lib/api_project'; import { globalIsMemberProject } from '../lib/val_project'; export default function NavbarDetailProject() { @@ -28,7 +29,10 @@ export default function NavbarDetailProject() { const tema = useHookstate(TEMA) const [reason, setReason] = useState("") const [openModal, setOpenModal] = useState(false) + const [openNewLink, setOpenNewLink] = useState(false) const [loadingModal, setLoadingModal] = useState(false) + const [loadingLink, setLoadingLink] = useState(false) + const [valLink, setValLink] = useState("") const [dataRealTime, setDataRealtime] = useWibuRealtime({ WIBU_REALTIME_TOKEN: keyWibu, project: "sdm" @@ -52,6 +56,30 @@ export default function NavbarDetailProject() { } } + async function addLinkProject() { + try { + setLoadingLink(true) + const res = await funAddLinkProject(param.id, { link: valLink }); + if (res.success) { + setDataRealtime([{ + category: "project-detail-link", + id: param.id, + user: res.user + }]) + toast.success(res.message) + } else { + toast.error(res.message) + } + } catch (error) { + console.error(error); + toast.error("Gagal menambahkan link, coba lagi nanti"); + } finally { + setLoadingLink(false) + setOpenNewLink(false) + setValLink("") + } + } + async function deleteDataProject() { try { setLoadingModal(true) @@ -157,6 +185,27 @@ export default function NavbarDetailProject() { + { + if (reason == null) { + setOpen(false) + setOpenNewLink(true) + } else { + null + } + }} + > + + + + + Tambah link + + + { (roleLogin.get() != "user" && roleLogin.get() != "coadmin") && <> @@ -229,11 +278,50 @@ export default function NavbarDetailProject() { - + setOpenModal(false)} description="Apakah Anda yakin ingin menghapus kegiatan ini?" onYes={(val) => { val ? deleteDataProject() : setOpenModal(false) }} /> + + setOpenNewLink(false)} centered withCloseButton={false}> + + Tambah Link + + setValLink(e.target.value)} + /> + + + + + + + + + + + + ); } diff --git a/src/module/task/index.ts b/src/module/task/index.ts index fcaa02e..48a67fc 100644 --- a/src/module/task/index.ts +++ b/src/module/task/index.ts @@ -7,6 +7,7 @@ import CreateTask from "./ui/create_task"; import CreateUsersProject from "./ui/create_users_project"; import ListAnggotaDetailTask from "./ui/detail_list_anggota_task"; import ListFileDetailTask from "./ui/detail_list_file_task"; +import ListLinkDetailTask from "./ui/detail_list_link_task"; import ListTugasDetailTask from "./ui/detail_list_tugas_task"; import ProgressDetailTask from "./ui/detail_progress_task"; import EditDetailTask from "./ui/edit_detail_task"; @@ -32,4 +33,5 @@ export { AddDetailTask } export { AddMemberDetailTask } export { CancelTask } export { EditTask } -export { AddFileDetailTask } \ No newline at end of file +export { AddFileDetailTask } +export { ListLinkDetailTask } \ 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 077bd98..1c8cba3 100644 --- a/src/module/task/lib/api_task.ts +++ b/src/module/task/lib/api_task.ts @@ -154,4 +154,27 @@ export const funDeleteTask = async (path: string) => { }, }); return await response.json().catch(() => null); -}; \ No newline at end of file +}; + +export const funAddLinkTask = async (path: string, data: { link: string, idDivision: string }) => { + const response = await fetch(`/api/task/${path}/link`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +}; + + +export const funDeleteLinkTask = async (path: string, data: { idLink: string }) => { + const response = await fetch(`/api/task/${path}/link`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +}; diff --git a/src/module/task/lib/type_task.ts b/src/module/task/lib/type_task.ts index ed88fb0..96dab26 100644 --- a/src/module/task/lib/type_task.ts +++ b/src/module/task/lib/type_task.ts @@ -70,4 +70,9 @@ export interface IDataFileTaskDivision { extension: string, nameInStorage: string, idStorage: string -} \ No newline at end of file +} + +export interface IDataLinkTaskDivision { + id: string + link: string +} diff --git a/src/module/task/ui/detail_list_link_task.tsx b/src/module/task/ui/detail_list_link_task.tsx new file mode 100644 index 0000000..2e84f45 --- /dev/null +++ b/src/module/task/ui/detail_list_link_task.tsx @@ -0,0 +1,230 @@ +'use client' +import { urlCompleted } from '@/lib/urlCompleted'; +import { globalRole, keyWibu, LayoutDrawer, TEMA } from '@/module/_global'; +import LayoutModal from '@/module/_global/layout/layout_modal'; +import { globalIsMemberDivision } from '@/module/division_new'; +import { useHookstate } from '@hookstate/core'; +import { Box, Flex, Grid, Group, SimpleGrid, Stack, Text } from '@mantine/core'; +import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; +import { useParams } from 'next/navigation'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { FaTrash } from 'react-icons/fa6'; +import { LuLink } from 'react-icons/lu'; +import { RiExternalLinkLine } from 'react-icons/ri'; +import { useWibuRealtime } from 'wibu-realtime'; +import { funDeleteLinkTask, funGetTaskDivisionById } from '../lib/api_task'; +import { IDataLinkTaskDivision } from '../lib/type_task'; + +export default function ListLinkDetailTask() { + const isMobile = useMediaQuery("(max-width: 350px)"); + const roleLogin = useHookstate(globalRole) + const memberDivision = useHookstate(globalIsMemberDivision) + 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 [loadingModal, setLoadingModal] = useState(false) + const [linkData, setLinkData] = useState('') + const [idData, setIdData] = useState('') + const tema = useHookstate(TEMA) + const [reason, setReason] = useState("") + const [dataRealTime, setDataRealtime] = useWibuRealtime({ + WIBU_REALTIME_TOKEN: keyWibu, + project: "sdm" + }) + + + async function getOneDataCancel() { + try { + const res = await funGetTaskDivisionById(param.detail, 'data'); + if (res.success) { + setReason(res.data.reason); + } else { + toast.error(res.message); + } + + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan data tugas divisi, coba lagi nanti"); + } + } + + useShallowEffect(() => { + getOneDataCancel(); + }, [param.detail]) + + async function getOneData(loading: boolean) { + try { + setLoading(loading) + const res = await funGetTaskDivisionById(param.detail, 'link'); + if (res.success) { + setData(res.data) + } else { + toast.error(res.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan file tugas divisi, coba lagi nanti"); + } finally { + setLoading(false) + } + } + + useShallowEffect(() => { + getOneData(true); + }, [param.detail]) + + + async function onDelete() { + try { + setLoadingModal(true) + const res = await funDeleteLinkTask(param.detail, { idLink: idData }); + if (res.success) { + setDataRealtime([{ + category: "tugas-detail-link", + id: param.detail, + }]) + toast.success(res.message) + getOneData(false) + setIdData("") + setOpenDrawer(false) + } else { + toast.error(res.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal menghapus file, coba lagi nanti"); + } finally { + setLoadingModal(false) + setOpenModal(false) + } + } + + useShallowEffect(() => { + if (dataRealTime && dataRealTime.some((i: any) => i.category == 'tugas-detail-link' && i.id == param.detail)) { + getOneData(false) + } else if (dataRealTime && dataRealTime.some((i: any) => i.category == 'tugas-detail-status' && i.id == param.detail)) { + getOneDataCancel() + } + }, [dataRealTime]) + + return ( + <> + { + isData.length > 0 + && + + Link + + { + isData.map((item, index) => { + return ( + { + setLinkData(item.link) + setIdData(item.id) + setOpenDrawer(true) + }} + > + + + + + + {item.link} + + + + + + ) + }) + } + + + + Menu} onClose={() => setOpenDrawer(false)}> + + + + { + { window.open(urlCompleted(linkData), '_blank', 'noopener,noreferrer') }} justify={'center'} align={'center'} direction={'column'} > + + + + + Buka Link + + + } + + { + (roleLogin.get() == "user" || roleLogin.get() == "coadmin") && !memberDivision.get() ? <> + : + { + reason == null ? + setOpenModal(true) + : setOpenModal(false) + }} justify={'center'} align={'center'} direction={'column'} > + + + + + Hapus + + + } + + + + + + + setOpenModal(false)} + description="Apakah Anda yakin ingin menghapus link ini? Link yang dihapus tidak dapat dikembalikan" + onYes={(val) => { + if (val) { + onDelete() + } else { + 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 fb3131a..92f409d 100644 --- a/src/module/task/ui/navbar_detail_division_task.tsx +++ b/src/module/task/ui/navbar_detail_division_task.tsx @@ -1,8 +1,10 @@ 'use client' import { globalRole, keyWibu, LayoutDrawer, LayoutNavbarNew, TEMA } from "@/module/_global"; +import LayoutModal from "@/module/_global/layout/layout_modal"; +import { funGetUserByCookies } from "@/module/auth"; import { globalIsAdminDivision, globalIsMemberDivision } from "@/module/division_new"; import { useHookstate } from "@hookstate/core"; -import { ActionIcon, Box, Flex, SimpleGrid, Stack, Text } from "@mantine/core"; +import { ActionIcon, Box, Button, Flex, Grid, Modal, SimpleGrid, Stack, Text, TextInput } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; @@ -10,11 +12,10 @@ import toast from "react-hot-toast"; import { FaFileCirclePlus, FaPencil, FaTrash, FaUsers } from "react-icons/fa6"; import { HiMenu } from "react-icons/hi"; import { IoAddCircle } from "react-icons/io5"; +import { LuLink } from "react-icons/lu"; import { MdCancel } from "react-icons/md"; -import { funDeleteTask, funGetTaskDivisionById } from "../lib/api_task"; import { useWibuRealtime } from "wibu-realtime"; -import LayoutModal from "@/module/_global/layout/layout_modal"; -import { funGetUserByCookies } from "@/module/auth"; +import { funAddLinkTask, funDeleteTask, funGetTaskDivisionById } from "../lib/api_task"; export default function NavbarDetailDivisionTask() { const router = useRouter() @@ -29,6 +30,9 @@ export default function NavbarDetailDivisionTask() { const [isUser, setUser] = useState('') const [loadingModal, setLoadingModal] = useState(false) const [openModal, setOpenModal] = useState(false) + const [openNewLink, setOpenNewLink] = useState(false) + const [valLink, setValLink] = useState("") + const [loadingLink, setLoadingLink] = useState(false) const [dataRealTime, setDataRealtime] = useWibuRealtime({ WIBU_REALTIME_TOKEN: keyWibu, project: "sdm" @@ -80,6 +84,29 @@ export default function NavbarDetailDivisionTask() { getOneData(); }, [param.detail]) + async function addLinkProject() { + try { + setLoadingLink(true) + const res = await funAddLinkTask(param.detail, { link: valLink, idDivision: param.id }); + if (res.success) { + setDataRealtime([{ + category: "tugas-detail-link", + id: param.detail, + }]) + toast.success(res.message) + } else { + toast.error(res.message) + } + } catch (error) { + console.error(error); + toast.error("Gagal menambahkan link, coba lagi nanti"); + } finally { + setLoadingLink(false) + setOpenNewLink(false) + setValLink("") + } + } + useShallowEffect(() => { if (dataRealTime && dataRealTime.some((i: any) => (i.category == 'tugas-detail' || i.category == 'tugas-detail-status') && i.id == param.detail)) { @@ -158,6 +185,27 @@ export default function NavbarDetailDivisionTask() { + { + if (reason == null) { + setOpen(false) + setOpenNewLink(true) + } else { + null + } + }} + > + + + + + Tambah link + + + { (roleLogin.get() != "user" && roleLogin.get() != "coadmin") || adminLogin.get() ? <> @@ -239,7 +287,46 @@ export default function NavbarDetailDivisionTask() { setOpenModal(false)} description="Apakah Anda yakin ingin menghapus tugas divisi ini?" - onYes={(val) => { val ? deleteDataProject() : setOpenModal(false) }} /> + onYes={(val) => { val ? deleteDataProject() : setOpenModal(false) }} + /> + + setOpenNewLink(false)} centered withCloseButton={false}> + + Tambah Link + + setValLink(e.target.value)} + /> + + + + + + + + + + + ) } \ No newline at end of file From 2c98c2581d0ad095722e8fd6903ced29e292e073 Mon Sep 17 00:00:00 2001 From: amal Date: Thu, 14 Aug 2025 12:15:33 +0800 Subject: [PATCH 03/14] upd: tambah upload link Deskripsi: - api mobile tambah dan hapus link pada project - api mobile tambah dan hapus link pada tugas divisi No Issues --- src/app/api/mobile/project/[id]/link/route.ts | 95 ++++++++++++++++++ src/app/api/mobile/project/[id]/route.ts | 12 +++ src/app/api/mobile/task/[id]/link/route.ts | 98 +++++++++++++++++++ src/app/api/mobile/task/[id]/route.ts | 12 +++ src/app/api/project/[id]/link/route.ts | 5 +- src/app/api/task/[id]/link/route.ts | 5 +- 6 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 src/app/api/mobile/project/[id]/link/route.ts create mode 100644 src/app/api/mobile/task/[id]/link/route.ts diff --git a/src/app/api/mobile/project/[id]/link/route.ts b/src/app/api/mobile/project/[id]/link/route.ts new file mode 100644 index 0000000..c42ffde --- /dev/null +++ b/src/app/api/mobile/project/[id]/link/route.ts @@ -0,0 +1,95 @@ +import { prisma } from "@/module/_global"; +import { funGetUserById } from "@/module/auth"; +import { createLogUserMobile } from "@/module/user"; +import { NextResponse } from "next/server"; + +// ADD LINK PROJECT +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params + const { link, user } = (await request.json()) + + const userMobile = await funGetUserById({ id: String(user) }) + + if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 200 } + ); + } + + + const insertLink = await prisma.projectLink.create({ + data: { + idProject: id, + link: link + } + }) + + // create log user + const log = await createLogUserMobile({ act: 'CREATE', desc: 'User menambah link kegiatan', table: 'projectLink', data: insertLink.id, user: userMobile.id }) + return NextResponse.json({ success: true, message: "Berhasil menambahkan link kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah link kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// DELETE LINK PROJECT +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const { idLink, user } = (await request.json()) + + const userMobile = await funGetUserById({ id: String(user) }) + + if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const data = await prisma.projectLink.count({ + where: { + id: idLink + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan link, data tidak ditemukan", + }, + { status: 200 } + ); + } + + const deleteLink = await prisma.projectLink.update({ + where: { + id: idLink + }, + data: { + isActive: false + } + }) + + // create log user + const log = await createLogUserMobile({ act: 'DELETE', desc: 'User menghapus link kegiatan', table: 'projectLink', data: String(idLink), user: userMobile.id }) + return NextResponse.json({ success: true, message: "Berhasil menghapus link kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus link kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} diff --git a/src/app/api/mobile/project/[id]/route.ts b/src/app/api/mobile/project/[id]/route.ts index e261dae..36551cf 100644 --- a/src/app/api/mobile/project/[id]/route.ts +++ b/src/app/api/mobile/project/[id]/route.ts @@ -136,6 +136,18 @@ export async function GET(request: Request, context: { params: { id: string } }) })) allData = fix + } else if (kategori == "link") { + const dataLink = await prisma.projectLink.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'asc' + } + }) + + allData = dataLink } return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: allData, }, { status: 200 }); diff --git a/src/app/api/mobile/task/[id]/link/route.ts b/src/app/api/mobile/task/[id]/link/route.ts new file mode 100644 index 0000000..067394a --- /dev/null +++ b/src/app/api/mobile/task/[id]/link/route.ts @@ -0,0 +1,98 @@ +import { prisma } from "@/module/_global"; +import { funGetUserById } from "@/module/auth"; +import { createLogUserMobile } from "@/module/user"; +import { NextResponse } from "next/server"; + +// ADD LINK TASK DIVISI +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { link, idDivision, user } = (await request.json()); + + const userMobile = await funGetUserById({ id: String(user) }) + if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah link tugas gagal, data tugas tidak ditemukan", + }, + { status: 200 } + ); + } + + + + const insertlink = await prisma.divisionProjectLink.create({ + data: { + idProject: id, + link, + idDivision, + } + }) + + + // create log user + const log = await createLogUserMobile({ act: 'CREATE', desc: 'User menambahkan link tugas divisi', table: 'divisionProjectLink', data: insertlink.id, user: userMobile.id }) + + + return NextResponse.json({ success: true, message: "Berhasil menambahkan link tugas", }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah link tugas, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// DELETE LINK TASK DIVISI +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const { idLink, user } = (await request.json()) + + const userMobile = await funGetUserById({ id: String(user) }) + if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 }); + } + + const data = await prisma.divisionProjectLink.count({ + where: { + id: idLink + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan link, data tidak ditemukan", + }, + { status: 200 } + ); + } + + const deleteLink = await prisma.divisionProjectLink.update({ + where: { + id: idLink + }, + data: { + isActive: false + } + }) + + // create log user + const log = await createLogUserMobile({ act: 'DELETE', desc: 'User menghapus link tugas divisi', table: 'divisionProjectLink', data: String(idLink), user: userMobile.id }) + return NextResponse.json({ success: true, message: "Berhasil menghapus link tugas divisi" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus link tugas divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/mobile/task/[id]/route.ts b/src/app/api/mobile/task/[id]/route.ts index 8328320..312945e 100644 --- a/src/app/api/mobile/task/[id]/route.ts +++ b/src/app/api/mobile/task/[id]/route.ts @@ -151,6 +151,18 @@ export async function GET(request: Request, context: { params: { id: string } }) })) allData = fix + } else if (kategori == "link") { + const dataLink = await prisma.divisionProjectLink.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'asc' + } + }) + + allData = dataLink } return NextResponse.json({ success: true, message: "Berhasil mendapatkan tugas divisi", data: allData }, { status: 200 }); diff --git a/src/app/api/project/[id]/link/route.ts b/src/app/api/project/[id]/link/route.ts index a133801..eb642e4 100644 --- a/src/app/api/project/[id]/link/route.ts +++ b/src/app/api/project/[id]/link/route.ts @@ -73,9 +73,12 @@ export async function DELETE(request: Request, context: { params: { id: string } ); } - const deleteLink = await prisma.projectLink.delete({ + const deleteLink = await prisma.projectLink.update({ where: { id: idLink + }, + data: { + isActive: false } }) diff --git a/src/app/api/task/[id]/link/route.ts b/src/app/api/task/[id]/link/route.ts index 8f9146c..25b927b 100644 --- a/src/app/api/task/[id]/link/route.ts +++ b/src/app/api/task/[id]/link/route.ts @@ -78,9 +78,12 @@ export async function DELETE(request: Request, context: { params: { id: string } ); } - const deleteLink = await prisma.divisionProjectLink.delete({ + const deleteLink = await prisma.divisionProjectLink.update({ where: { id: idLink + }, + data: { + isActive: false } }) From 1f856ad3abd31c628a7be156b1766c89f42e94bf Mon Sep 17 00:00:00 2001 From: amal Date: Thu, 14 Aug 2025 16:50:02 +0800 Subject: [PATCH 04/14] upd: laporan kegiatan Deskripsi: - update struktur database - api update laporan kegiatan project - tampilan laporan kegiatan project - form update laporan kegiatan project - integrasi api update laporan kegiatan project - api update laporan kegiatan tugas divisi - tampilan laporan kegiatan tugas divisi - form update laporan kegiatan tugas divisi - integrasi api update laporan kegiatan tugas divisi No Issues --- prisma/schema.prisma | 52 +++--- .../(fitur-division)/task/[detail]/page.tsx | 3 +- .../task/[detail]/report/page.tsx | 8 + src/app/(application)/project/[id]/page.tsx | 3 +- .../project/[id]/report/page.tsx | 9 + src/app/api/project/[id]/lainnya/route.ts | 46 +++++ src/app/api/task/[id]/lainnya/route.ts | 48 ++++++ src/module/project/index.ts | 7 +- src/module/project/lib/api_project.ts | 11 ++ src/module/project/ui/add_report_project.tsx | 157 ++++++++++++++++++ src/module/project/ui/list_report_project.tsx | 82 +++++++++ .../project/ui/navbar_detail_project.tsx | 23 ++- src/module/task/index.ts | 6 +- src/module/task/lib/api_task.ts | 11 ++ src/module/task/ui/add_report_detail_task.tsx | 156 +++++++++++++++++ .../task/ui/detail_list_report_task.tsx | 83 +++++++++ .../task/ui/navbar_detail_division_task.tsx | 23 ++- 17 files changed, 689 insertions(+), 39 deletions(-) create mode 100644 src/app/(application)/division/[id]/(fitur-division)/task/[detail]/report/page.tsx create mode 100644 src/app/(application)/project/[id]/report/page.tsx create mode 100644 src/module/project/ui/add_report_project.tsx create mode 100644 src/module/project/ui/list_report_project.tsx create mode 100644 src/module/task/ui/add_report_detail_task.tsx create mode 100644 src/module/task/ui/detail_list_report_task.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f111c5b..452fb0c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -113,7 +113,6 @@ model User { Announcement Announcement[] Project Project[] ProjectMember ProjectMember[] - ProjectComment ProjectComment[] UserLog UserLog[] Division Division[] DivisionMember DivisionMember[] @@ -184,25 +183,25 @@ model AnnouncementMember { } model Project { - id String @id @default(cuid()) - Village Village @relation(fields: [idVillage], references: [id]) - idVillage String - Group Group @relation(fields: [idGroup], references: [id]) - idGroup String - title String - status Int @default(0) // 0 = pending, 1 = ongoing, 2 = done, 3 = cancelled - desc String? @db.Text - reason String? @db.Text - isActive Boolean @default(true) - User User @relation(fields: [createdBy], references: [id]) - createdBy String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - ProjectMember ProjectMember[] - ProjectFile ProjectFile[] - ProjectComment ProjectComment[] - ProjectTask ProjectTask[] - ProjectLink ProjectLink[] + id String @id @default(cuid()) + Village Village @relation(fields: [idVillage], references: [id]) + idVillage String + Group Group @relation(fields: [idGroup], references: [id]) + idGroup String + title String + status Int @default(0) // 0 = pending, 1 = ongoing, 2 = done, 3 = cancelled + desc String? @db.Text + reason String? @db.Text + report String? @db.Text + isActive Boolean @default(true) + User User @relation(fields: [createdBy], references: [id]) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + ProjectMember ProjectMember[] + ProjectFile ProjectFile[] + ProjectTask ProjectTask[] + ProjectLink ProjectLink[] } model ProjectMember { @@ -254,18 +253,6 @@ model ProjectTask { updatedAt DateTime @updatedAt } -model ProjectComment { - id String @id @default(cuid()) - Project Project @relation(fields: [idProject], references: [id]) - idProject String - User User @relation(fields: [createdBy], references: [id]) - createdBy String - comment String @db.Text - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - model Division { id String @id @default(cuid()) Village Village @relation(fields: [idVillage], references: [id]) @@ -314,6 +301,7 @@ model DivisionProject { title String desc String? @db.Text reason String? @db.Text + report String? @db.Text status Int @default(0) // 0 = pending, 1 = ongoing, 2 = done, 3 = cancelled isActive Boolean @default(true) createdAt DateTime @default(now()) diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx index 37e2386..f964f3b 100644 --- a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx @@ -1,4 +1,4 @@ -import { ListAnggotaDetailTask, ListFileDetailTask, ListLinkDetailTask, ListTugasDetailTask, NavbarDetailDivisionTask, ProgressDetailTask } from "@/module/task" +import { ListAnggotaDetailTask, ListFileDetailTask, ListLinkDetailTask, ListReportDetailTask, ListTugasDetailTask, NavbarDetailDivisionTask, ProgressDetailTask } from "@/module/task" import { Box } from "@mantine/core" function Page() { @@ -7,6 +7,7 @@ function Page() { + diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/report/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/report/page.tsx new file mode 100644 index 0000000..9da9b27 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/report/page.tsx @@ -0,0 +1,8 @@ +import { AddReportTask } from "@/module/task" + +function Page() { + return ( + + ) +} +export default Page diff --git a/src/app/(application)/project/[id]/page.tsx b/src/app/(application)/project/[id]/page.tsx index c54659b..715f53a 100644 --- a/src/app/(application)/project/[id]/page.tsx +++ b/src/app/(application)/project/[id]/page.tsx @@ -1,4 +1,4 @@ -import { ListAnggotaDetailProject, ListFileDetailProject, ListLinkDetailProject, ListTugasDetailProject, NavbarDetailProject, ProgressDetailProject } from '@/module/project'; +import { ListAnggotaDetailProject, ListFileDetailProject, ListLinkDetailProject, ListReportDetailProject, ListTugasDetailProject, NavbarDetailProject, ProgressDetailProject } from '@/module/project'; import { Box } from '@mantine/core'; function Page() { @@ -7,6 +7,7 @@ function Page() { + diff --git a/src/app/(application)/project/[id]/report/page.tsx b/src/app/(application)/project/[id]/report/page.tsx new file mode 100644 index 0000000..c3b9021 --- /dev/null +++ b/src/app/(application)/project/[id]/report/page.tsx @@ -0,0 +1,9 @@ +import { AddReportProject } from "@/module/project"; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/api/project/[id]/lainnya/route.ts b/src/app/api/project/[id]/lainnya/route.ts index bf3f042..1e28df9 100644 --- a/src/app/api/project/[id]/lainnya/route.ts +++ b/src/app/api/project/[id]/lainnya/route.ts @@ -45,4 +45,50 @@ export async function DELETE(request: Request, context: { params: { id: string } console.error(error); return NextResponse.json({ success: false, message: "Gagal menghapus kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); } +} + +// EDIT PROJECT REPORT +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 { report } = await request.json() + + const data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.project.update({ + where: { + id + }, + data: { + report: report + } + }) + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate laporan kegiatan', table: 'project', data: String(id) }) + + return NextResponse.json({ success: true, message: "Laporan kegiatan berhasil diupdate" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } } \ No newline at end of file diff --git a/src/app/api/task/[id]/lainnya/route.ts b/src/app/api/task/[id]/lainnya/route.ts index cc67eeb..585a0ac 100644 --- a/src/app/api/task/[id]/lainnya/route.ts +++ b/src/app/api/task/[id]/lainnya/route.ts @@ -45,4 +45,52 @@ export async function DELETE(request: Request, context: { params: { id: string } console.error(error); return NextResponse.json({ success: false, message: "Gagal menghapus tugas, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); } +} + + +// EDIT TASK REPORT +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 { report } = await request.json() + + + const data = await prisma.divisionProject.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.divisionProject.update({ + where: { + id + }, + data: { + report: report + } + }) + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate laporan tugas divisi', table: 'divisionProject', data: String(id) }) + + return NextResponse.json({ success: true, message: "Laporan tugas divisi berhasil diupdate" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate laporan tugas divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } } \ No newline at end of file diff --git a/src/module/project/index.ts b/src/module/project/index.ts index e4b0d43..35107a1 100644 --- a/src/module/project/index.ts +++ b/src/module/project/index.ts @@ -5,7 +5,6 @@ import NavbarDetailProject from "./ui/navbar_detail_project"; import ProgressDetailProject from "./ui/progress_detail_project"; import ListTugasDetailProject from "./ui/list_tugas_detail_project"; import ListFileDetailProject from "./ui/list_file_detail_project"; -import LiatAnggotaDetailProject from "./ui/list_anggota_detail_project"; import EditTaskProject from "./ui/edit_task_project"; import EditDetailTaskProject from "./ui/edit_detail_task_project"; import ListAnggotaDetailProject from "./ui/list_anggota_detail_project"; @@ -16,6 +15,8 @@ import CreateProject from "./ui/create_project"; import AddFileDetailProject from "./ui/add_file_detail_project"; import WrapLayoutProject from "./ui/wrap_project"; import ListLinkDetailProject from "./ui/list_link_detail_project"; +import AddReportProject from "./ui/add_report_project"; +import ListReportDetailProject from "./ui/list_report_project"; export { ViewDateEndTask } export { CreateUsersProject } @@ -33,4 +34,6 @@ export { AddMemberDetailProject } export { CreateProject } export { AddFileDetailProject } export { WrapLayoutProject } -export { ListLinkDetailProject } \ No newline at end of file +export { ListLinkDetailProject } +export { AddReportProject } +export { ListReportDetailProject } \ No newline at end of file diff --git a/src/module/project/lib/api_project.ts b/src/module/project/lib/api_project.ts index b64563b..ab349ef 100644 --- a/src/module/project/lib/api_project.ts +++ b/src/module/project/lib/api_project.ts @@ -122,6 +122,17 @@ export const funEditProject = async (path: string, data: { name: string }) => { return await response.json().catch(() => null); } +export const funEditReportProject = async (path: string, data: { report: string }) => { + const response = await fetch(`/api/project/${path}/lainnya`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +} + export const funDeleteFileProject = async (path: string) => { const response = await fetch(`/api/project/file/${path}`, { diff --git a/src/module/project/ui/add_report_project.tsx b/src/module/project/ui/add_report_project.tsx new file mode 100644 index 0000000..593e4b0 --- /dev/null +++ b/src/module/project/ui/add_report_project.tsx @@ -0,0 +1,157 @@ +"use client" +import { keyWibu, LayoutNavbarNew, TEMA } from '@/module/_global'; +import LayoutModal from '@/module/_global/layout/layout_modal'; +import { useHookstate } from '@hookstate/core'; +import { Box, Button, rem, Skeleton, Stack, Textarea } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { useParams, useRouter } from 'next/navigation'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { useWibuRealtime } from 'wibu-realtime'; +import { funEditReportProject, funGetOneProjectById } from '../lib/api_project'; + +export default function AddReportProject() { + const router = useRouter() + const [report, setReport] = useState("") + const [openModal, setOpenModal] = useState(false) + const param = useParams<{ id: string }>() + const [loading, setLoading] = useState(true) + const [loadingSubmit, setLoadingSubmit] = useState(false) + const tema = useHookstate(TEMA) + const [touched, setTouched] = useState({ + report: false, + }); + const [dataRealTime, setDataRealtime] = useWibuRealtime({ + WIBU_REALTIME_TOKEN: keyWibu, + project: "sdm" + }) + + async function onSubmit() { + try { + setLoadingSubmit(true) + const res = await funEditReportProject(param.id, { report }) + if (res.success) { + setDataRealtime([{ + category: "project-report", + id: param.id, + }]) + toast.success(res.message) + router.push("./") + } else { + toast.error(res.message) + } + } catch (error) { + console.error(error) + toast.error("Gagal mengedit Laporan Kegiatan, coba lagi nanti") + } finally { + setLoadingSubmit(false) + setOpenModal(false) + } + } + + function onCheck() { + if (report == "") { + setTouched({ ...touched, report: true }) + return false + } + setOpenModal(true) + } + + + + function onValidation(kategori: string, val: string) { + if (kategori == 'report') { + setReport(val) + if (val === "") { + setTouched({ ...touched, report: true }) + } else { + setTouched({ ...touched, report: false }) + } + } + } + + async function getOneData() { + try { + setLoading(true) + const res = await funGetOneProjectById(param.id, 'data'); + if (res.success) { + setReport(res.data.report); + } else { + toast.error(res.message); + } + setLoading(false); + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan data Kegiatan, coba lagi nanti"); + } finally { + setLoading(false); + } + } + + useShallowEffect(() => { + getOneData(); + }, [param.id]) + + + return ( + + + + + {loading ? + + : +