diff --git a/src/app/(application)/layout.tsx b/src/app/(application)/layout.tsx index d2839d0..8eb45f9 100644 --- a/src/app/(application)/layout.tsx +++ b/src/app/(application)/layout.tsx @@ -1,4 +1,4 @@ -import { ScrollProvider, WrapLayout } from "@/module/_global" +import { WrapLayout } from "@/module/_global" import { funDetectCookies, funGetUserByCookies } from "@/module/auth" import _ from "lodash" import { redirect } from "next/navigation" @@ -11,9 +11,7 @@ export default async function Layout({ children }: { children: React.ReactNode } return ( <> - - {children} - + {children} ); diff --git a/src/app/api/discussion/route.ts b/src/app/api/discussion/route.ts index c00992d..7d2cf7a 100644 --- a/src/app/api/discussion/route.ts +++ b/src/app/api/discussion/route.ts @@ -18,6 +18,8 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const idDivision = searchParams.get("division"); const name = searchParams.get('search'); + const page = searchParams.get('page'); + const dataSkip = Number(page) * 10 - 10; if (idDivision != "null" && idDivision != null && idDivision != undefined) { @@ -33,6 +35,8 @@ export async function GET(request: Request) { } const data = await prisma.divisionDisscussion.findMany({ + skip: dataSkip, + take: 10, where: { isActive: true, idDivision: idDivision, diff --git a/src/app/api/project/route.ts b/src/app/api/project/route.ts index feec0b7..0e81b2a 100644 --- a/src/app/api/project/route.ts +++ b/src/app/api/project/route.ts @@ -22,6 +22,8 @@ export async function GET(request: Request) { const name = searchParams.get('search'); const status = searchParams.get('status'); const idGroup = searchParams.get("group"); + const page = searchParams.get('page'); + const dataSkip = Number(page) * 10 - 10; const villageId = user.idVillage const userId = user.id @@ -73,6 +75,8 @@ export async function GET(request: Request) { const data = await prisma.project.findMany({ + skip: dataSkip, + take: 10, where: kondisi, select: { id: true, @@ -95,6 +99,11 @@ export async function GET(request: Request) { member: v.ProjectMember.length })) + + const totalData = await prisma.project.count({ + where: kondisi + }) + const filter = await prisma.group.findUnique({ where: { id: grup @@ -106,7 +115,7 @@ export async function GET(request: Request) { }) - return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: omitData, filter }, { status: 200 }); + return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: omitData, filter, total: totalData }, { status: 200 }); } catch (error) { console.error(error); diff --git a/src/app/api/task/route.ts b/src/app/api/task/route.ts index 3093cf6..679881d 100644 --- a/src/app/api/task/route.ts +++ b/src/app/api/task/route.ts @@ -20,6 +20,8 @@ export async function GET(request: Request) { const name = searchParams.get('search'); const divisi = searchParams.get('division'); const status = searchParams.get('status'); + const page = searchParams.get('page'); + const dataSkip = Number(page) * 10 - 10; const cek = await prisma.division.count({ where: { @@ -33,6 +35,8 @@ export async function GET(request: Request) { } const data = await prisma.divisionProject.findMany({ + skip: dataSkip, + take: 10, where: { isActive: true, idDivision: String(divisi), @@ -73,7 +77,19 @@ export async function GET(request: Request) { member: v.DivisionProjectMember.length })) - return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: formatData, }, { status: 200 }); + const totalData = await prisma.divisionProject.count({ + where: { + isActive: true, + idDivision: String(divisi), + status: (status == "0" || status == "1" || status == "2" || status == "3") ? Number(status) : 0, + title: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + } + } + }) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: formatData, total: totalData }, { status: 200 }); } catch (error) { console.error(error); diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 75772ea..d424396 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -15,7 +15,7 @@ export async function GET(request: Request) { const idGroup = searchParams.get("group"); const active = searchParams.get("active"); const page = searchParams.get('page'); - const dataSkip = Number(page) * 5 - 5; + const dataSkip = Number(page) * 10 - 10; const user = await funGetUserByCookies() if (user.id == undefined) { return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); @@ -37,46 +37,89 @@ export async function GET(request: Request) { }) - const users = await prisma.user.findMany({ - skip: dataSkip, - take: 5, - where: { - isActive: active == 'false' ? false : true, - idGroup: String(fixGroup), - name: { - contains: (name == undefined || name == null) ? "" : name, - mode: "insensitive", - } - }, - select: { - id: true, - isActive: true, - nik: true, - name: true, - phone: true, - email: true, - gender: true, - img: true, - Position: { - select: { - name: true, + if (page != undefined) { + const users = await prisma.user.findMany({ + skip: dataSkip, + take: 10, + where: { + isActive: active == 'false' ? false : true, + idGroup: String(fixGroup), + name: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive", + } + }, + select: { + id: true, + isActive: true, + nik: true, + name: true, + phone: true, + email: true, + gender: true, + img: true, + Position: { + select: { + name: true, + }, + }, + Group: { + select: { + name: true, + }, }, }, - Group: { - select: { - name: true, + }); + + const allData = users.map((v: any) => ({ + ..._.omit(v, ["Group", "Position"]), + group: v.Group.name, + position: v.Position.name + })) + + return NextResponse.json({ success: true, message: "Berhasil member", data: allData, filter }, { status: 200 }); + } else { + const users = await prisma.user.findMany({ + where: { + isActive: active == 'false' ? false : true, + idGroup: String(fixGroup), + name: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive", + } + }, + select: { + id: true, + isActive: true, + nik: true, + name: true, + phone: true, + email: true, + gender: true, + img: true, + Position: { + select: { + name: true, + }, + }, + Group: { + select: { + name: true, + }, }, }, - }, - }); + }); + + const allData = users.map((v: any) => ({ + ..._.omit(v, ["Group", "Position"]), + group: v.Group.name, + position: v.Position.name + })) + + return NextResponse.json({ success: true, message: "Berhasil member", data: allData, filter }, { status: 200 }); + } - const allData = users.map((v: any) => ({ - ..._.omit(v, ["Group", "Position"]), - group: v.Group.name, - position: v.Position.name - })) - return NextResponse.json({ success: true, message: "Berhasil member", data: allData, filter }, { status: 200 }); } catch (error) { console.error(error); return NextResponse.json({ success: false, message: "Gagal mendapatkan member, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2daadaa..8751385 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,7 +6,7 @@ import { MantineProvider, rem, } from "@mantine/core"; -import { WARNA } from "@/module/_global"; +import { ScrollProvider, WARNA } from "@/module/_global"; import { Lato } from "next/font/google"; import '@mantine/carousel/styles.css'; import { Toaster } from 'react-hot-toast'; @@ -33,20 +33,23 @@ export default function RootLayout({ return ( - + - + - - - {children} - + + + + {children} + + + diff --git a/src/module/announcement/ui/list_announcement.tsx b/src/module/announcement/ui/list_announcement.tsx index ce57010..966d95e 100644 --- a/src/module/announcement/ui/list_announcement.tsx +++ b/src/module/announcement/ui/list_announcement.tsx @@ -31,13 +31,11 @@ export default function ListAnnouncement() { setLoading(true) const response = await funGetAllAnnouncement('?search=' + searchQuery + '&page=' + isPage) if (response.success) { - // if (response.data.length > 0) { if (isPage == 1) { setIsData(response?.data) } else { setIsData([...isData, ...response?.data]) } - // } } else { toast.error(response.message); } @@ -79,7 +77,6 @@ export default function ListAnnouncement() { const scrollTop = containerRef.current.scrollTop; const containerHeight = containerRef.current.clientHeight; const scrollHeight = containerRef.current.scrollHeight; - if (scrollTop + containerHeight >= scrollHeight) { setPage(isPage + 1) } diff --git a/src/module/discussion/ui/list_discussion.tsx b/src/module/discussion/ui/list_discussion.tsx index 1c789cc..43b9c47 100644 --- a/src/module/discussion/ui/list_discussion.tsx +++ b/src/module/discussion/ui/list_discussion.tsx @@ -1,8 +1,8 @@ 'use client' -import { TEMA } from "@/module/_global"; +import { currentScroll, TEMA } from "@/module/_global"; import { Avatar, Badge, Box, Divider, Flex, Grid, Group, Skeleton, Spoiler, Text, TextInput } from "@mantine/core"; import { useParams, useRouter, useSearchParams } from "next/navigation"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { GrChatOption } from "react-icons/gr"; import { HiMagnifyingGlass } from "react-icons/hi2"; import { funGetAllDiscussion } from "../lib/api_discussion"; @@ -18,15 +18,21 @@ export default function ListDiscussion({ id }: { id: string }) { const param = useParams<{ id: string }>() const [loading, setLoading] = useState(true) const tema = useHookstate(TEMA) + const router = useRouter() + const { value: containerRef } = useHookstate(currentScroll); + const [isPage, setPage] = useState(1) - const getData = async () => { + const getData = async (loading: boolean) => { try { - setLoading(true) - const response = await funGetAllDiscussion('?division=' + id + '&search=' + searchQuery) - if ( - response.success - ) { - setData(response.data) + if (loading) + setLoading(true) + const response = await funGetAllDiscussion('?division=' + id + '&search=' + searchQuery + '&page=' + isPage) + if (response.success) { + if (isPage == 1) { + setData(response.data) + } else { + setData([...isData, ...response.data]) + } } else { toast.error(response.message) } @@ -39,10 +45,36 @@ export default function ListDiscussion({ id }: { id: string }) { } useShallowEffect(() => { - getData() + setPage(1) + getData(true) }, [searchQuery]) - const router = useRouter() + useShallowEffect(() => { + getData(false) + }, [isPage]) + + useEffect(() => { + const handleScroll = async () => { + if (containerRef && containerRef.current) { + const scrollTop = containerRef.current.scrollTop; + const containerHeight = containerRef.current.clientHeight; + const scrollHeight = containerRef.current.scrollHeight; + + if (scrollTop + containerHeight >= scrollHeight) { + setPage(isPage + 1) + } + } + }; + + const container = containerRef?.current; + container?.addEventListener("scroll", handleScroll); + + return () => { + container?.removeEventListener("scroll", handleScroll); + }; + }, [containerRef, isPage]); + + return ( { + const handleList = () => { + setIsList(!isList) + } + + const fetchData = async (loading: boolean) => { try { - setLoading(true) - const response = await funGetAllProject('?status=' + status + '&search=' + searchQuery + '&group=' + group) + if (loading) + setLoading(true) + const response = await funGetAllProject('?status=' + status + '&search=' + searchQuery + '&group=' + group + '&page=' + isPage) if (response.success) { - setData(response?.data) setNameGroup(response.filter.name) + setTotalData(response.total) + if (isPage == 1) { + setData(response.data) + } else { + setData([...isData, ...response.data]) + } } else { toast.error(response.message); } @@ -47,15 +62,35 @@ export default function ListProject() { useShallowEffect(() => { - fetchData(); + setPage(1) + fetchData(true); }, [status, searchQuery]); - const handleList = () => { - setIsList(!isList) - } - const isMobile = useMediaQuery('(max-width: 369px)'); - const paddingLift = useMediaQuery('(max-width: 505px)') + useShallowEffect(() => { + fetchData(false) + }, [isPage]) + + useEffect(() => { + const handleScroll = async () => { + if (containerRef && containerRef.current) { + const scrollTop = containerRef.current.scrollTop; + const containerHeight = containerRef.current.clientHeight; + const scrollHeight = containerRef.current.scrollHeight; + + if (scrollTop + containerHeight + 1 >= scrollHeight) { + setPage(isPage + 1) + } + } + }; + + const container = containerRef?.current; + container?.addEventListener("scroll", handleScroll); + + return () => { + container?.removeEventListener("scroll", handleScroll); + }; + }, [containerRef, isPage]); return ( @@ -97,7 +132,7 @@ export default function ListProject() { Total Kegiatan - {isData.length} + {totalData} } diff --git a/src/module/task/ui/list_division_task.tsx b/src/module/task/ui/list_division_task.tsx index 698b42b..99546ad 100644 --- a/src/module/task/ui/list_division_task.tsx +++ b/src/module/task/ui/list_division_task.tsx @@ -1,7 +1,7 @@ -import { TEMA } from "@/module/_global"; +import { currentScroll, TEMA } from "@/module/_global"; import { ActionIcon, Avatar, Box, Card, Center, Divider, Flex, Grid, Group, Progress, Skeleton, Text, TextInput, Title } from "@mantine/core"; import { useParams, useRouter, useSearchParams } from "next/navigation"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { HiMagnifyingGlass, HiMiniPresentationChartBar, HiOutlineListBullet, HiSquares2X2 } from "react-icons/hi2"; import { MdAccountCircle } from "react-icons/md"; import { IDataTask } from "../lib/type_task"; @@ -22,20 +22,26 @@ export default function ListDivisionTask() { const [loading, setLoading] = useState(true); const tema = useHookstate(TEMA) const paddingLift = useMediaQuery('(max-width: 505px)') + const { value: containerRef } = useHookstate(currentScroll) + const [isPage, setPage] = useState(1) + const [totalData, setTotalData] = useState(0) const handleList = () => { setIsList(!isList) } - const fetchData = async () => { + const fetchData = async (loading: boolean) => { try { - setData([]); - setLoading(true); - - const response = await funGetAllTask('?division=' + param.id + '&status=' + status + '&search=' + searchQuery) - + if (loading) + setLoading(true) + const response = await funGetAllTask('?division=' + param.id + '&status=' + status + '&search=' + searchQuery + '&page=' + isPage) if (response.success) { - setData(response?.data) + setTotalData(response.total) + if (isPage == 1) { + setData(response?.data) + } else { + setData([...isData, ...response.data]) + } } else { toast.error(response.message); } @@ -51,9 +57,36 @@ export default function ListDivisionTask() { useShallowEffect(() => { - fetchData(); + setPage(1) + fetchData(true); }, [status, searchQuery]); + + useShallowEffect(() => { + fetchData(false) + }, [isPage]) + + useEffect(() => { + const handleScroll = async () => { + if (containerRef && containerRef.current) { + const scrollTop = containerRef.current.scrollTop; + const containerHeight = containerRef.current.clientHeight; + const scrollHeight = containerRef.current.scrollHeight; + + if (scrollTop + containerHeight >= scrollHeight) { + setPage(isPage + 1) + } + } + }; + + const container = containerRef?.current; + container?.addEventListener("scroll", handleScroll); + + return () => { + container?.removeEventListener("scroll", handleScroll); + }; + }, [containerRef, isPage]); + return ( @@ -92,7 +125,7 @@ export default function ListDivisionTask() { Total Kegiatan - {isData.length} + {totalData} } diff --git a/src/module/user/member/ui/tab_list_member.tsx b/src/module/user/member/ui/tab_list_member.tsx index 2c9f273..4a664cb 100644 --- a/src/module/user/member/ui/tab_list_member.tsx +++ b/src/module/user/member/ui/tab_list_member.tsx @@ -1,5 +1,4 @@ - -import { currentScroll, globalRole, SkeletonSingle, TEMA, WARNA } from "@/module/_global" +import { currentScroll, globalRole, SkeletonSingle, TEMA } from "@/module/_global" import { Box, Text, TextInput, Divider, Avatar, Grid } from "@mantine/core" import { useShallowEffect } from "@mantine/hooks" import { useRouter, useSearchParams } from "next/navigation" @@ -7,7 +6,6 @@ import { useEffect, useState } from "react" import { HiMagnifyingGlass } from "react-icons/hi2" import { IListMember } from "../lib/type_member" import { funGetAllmember } from "../lib/api_member" -import { funGetAllGroup, IDataGroup } from "@/module/group" import toast from "react-hot-toast" import _ from "lodash" import { useHookstate } from "@hookstate/core" @@ -24,48 +22,42 @@ export default function TabListMember() { const roleLogin = useHookstate(globalRole) const [nameGroup, setNameGroup] = useState('') const tema = useHookstate(TEMA) - - //scroll const { value: containerRef } = useHookstate(currentScroll); const [isPage, setPage] = useState(1) async function getAllUser(loading: boolean) { try { - setLoading(true) + if (loading) + setLoading(true) const res = await funGetAllmember('?active=' + status + '&group=' + group + '&search=' + searchQuery + '&page=' + isPage) if (res.success) { - if (isPage == 1) { - setDataMember(res.data) - setNameGroup(res.filter.name) - } else { - setDataMember([...dataMember, ...res.data]) - setNameGroup(res.filter.name) - } - + setNameGroup(res.filter.name) + if (isPage == 1) { + setDataMember(res.data) + } else { + setDataMember([...dataMember, ...res.data]) + } + } else { toast.error(res.message) } } catch (error) { console.error(error) - throw new Error("Error") + toast.error("Gagal memuat data, coba lagi nanti") } finally { setLoading(false) } } - function onSearch(val:string){ - setSearchQuery(val) - setPage(1) - } - useShallowEffect(() => { + setPage(1) getAllUser(true) }, [status, searchQuery]) useShallowEffect(() => { getAllUser(false) - }, [status, isPage]) + }, [isPage]) useEffect(() => { const handleScroll = async () => { @@ -103,7 +95,7 @@ export default function TabListMember() { radius={30} leftSection={} placeholder="Pencarian" - onChange={(e) => onSearch(e.target.value)} + onChange={(e) => setSearchQuery(e.target.value)} my={10} /> {loading