diff --git a/app/(application)/_layout.tsx b/app/(application)/_layout.tsx index f056034..7725fed 100644 --- a/app/(application)/_layout.tsx +++ b/app/(application)/_layout.tsx @@ -1,3 +1,4 @@ +import HeaderRightAnnouncementList from "@/components/announcement/headerAnnouncementList"; import ButtonBackHeader from "@/components/buttonBackHeader"; import HeaderDiscussionGeneral from "@/components/discussion_general/headerDiscussionGeneral"; import HeaderRightDivisionList from "@/components/division/headerDivisionList"; @@ -69,6 +70,14 @@ export default function RootLayout() { headerTitleAlign: 'center', headerRight: () => }} /> + { router.back() }} />, + headerTitle: 'Pengumuman', + headerTitleAlign: 'center', + headerRight: () => + }} + /> diff --git a/app/(application)/announcement/index.tsx b/app/(application)/announcement/index.tsx index 2ce1d3a..ed84780 100644 --- a/app/(application)/announcement/index.tsx +++ b/app/(application)/announcement/index.tsx @@ -1,6 +1,4 @@ -import HeaderRightAnnouncementList from "@/components/announcement/headerAnnouncementList"; import BorderBottomItem from "@/components/borderBottomItem"; -import ButtonBackHeader from "@/components/buttonBackHeader"; import InputSearch from "@/components/inputSearch"; import SkeletonContent from "@/components/skeletonContent"; import { ColorsStatus } from "@/constants/ColorsStatus"; @@ -8,9 +6,9 @@ import Styles from "@/constants/Styles"; import { apiGetAnnouncement } from "@/lib/api"; import { useAuthSession } from "@/providers/AuthProvider"; import { MaterialIcons } from "@expo/vector-icons"; -import { router, Stack } from "expo-router"; +import { router } from "expo-router"; import { useEffect, useState } from "react"; -import { SafeAreaView, ScrollView, Text, View } from "react-native"; +import { Text, View, VirtualizedList } from "react-native"; import { useSelector } from "react-redux"; type Props = { @@ -25,57 +23,77 @@ export default function Announcement() { const { token, decryptToken } = useAuthSession() const [data, setData] = useState([]) const [search, setSearch] = useState('') - const entityUser = useSelector((state: any) => state.user) const update = useSelector((state: any) => state.announcementUpdate) const [loading, setLoading] = useState(true) const arrSkeleton = Array.from({ length: 5 }, (_, index) => index) + const [page, setPage] = useState(1) + const [waiting, setWaiting] = useState(false) - async function handleLoad(loading: boolean) { + async function handleLoad(loading: boolean, thisPage: number) { try { + setWaiting(true) setLoading(loading) + setPage(thisPage) const hasil = await decryptToken(String(token?.current)) - const response = await apiGetAnnouncement({ user: hasil, search: search }) - setData(response.data) + const response = await apiGetAnnouncement({ user: hasil, search: search, page: thisPage }) + if (thisPage == 1) { + setData(response.data) + } else if (thisPage > 1 && response.data.length > 0) { + setData([...data, ...response.data]) + } else { + return; + } } catch (error) { console.error(error) } finally { setLoading(false) + setWaiting(false) } } useEffect(() => { - handleLoad(false) + handleLoad(false, 1) }, [update]) useEffect(() => { - handleLoad(true) + handleLoad(true, 1) }, [search]) - return ( - - { router.back() }} />, - headerTitle: 'Pengumuman', - headerTitleAlign: 'center', - headerRight: () => entityUser.role != 'user' && entityUser.role != 'coadmin' ? : <> - }} - /> + const loadMoreData = () => { + if (waiting) return + setTimeout(() => { + handleLoad(false, page + 1) + }, 1000); + }; - - - - { - loading ? - arrSkeleton.map((item, index) => { - return ( - - ) - }) - : - data.length > 0 - ? - data.map((item, index) => { + const getItem = (_data: unknown, index: number): Props => ({ + id: data[index].id, + title: data[index].title, + desc: data[index].desc, + createdAt: data[index].createdAt, + }) + + return ( + + + + + + { + loading ? + arrSkeleton.map((item, index) => { + return ( + + ) + }) + : + data.length > 0 + ? + data.length} + getItem={getItem} + renderItem={({ item, index }: { item: Props, index: number }) => { return ( ) - }) - : - Tidak ada pengumuman - } - - - + }} + keyExtractor={(item, index) => String(index)} + onEndReached={loadMoreData} + onEndReachedThreshold={0.5} + showsVerticalScrollIndicator={false} + /> + // data.map((item, index) => { + // return ( + // { router.push(`/announcement/${item.id}`) }} + // borderType="bottom" + // icon={ + // + // + // + // } + // title={item.title} + // desc={item.desc.replace(/<[^>]*>?/gm, '')} + // rightTopInfo={item.createdAt} + // /> + // ) + // }) + : + Tidak ada pengumuman + } + + ) } \ No newline at end of file diff --git a/app/(application)/discussion/index.tsx b/app/(application)/discussion/index.tsx index 96af6d5..a4ef2bc 100644 --- a/app/(application)/discussion/index.tsx +++ b/app/(application)/discussion/index.tsx @@ -10,7 +10,7 @@ import { useAuthSession } from "@/providers/AuthProvider"; import { AntDesign, Feather, Ionicons, MaterialIcons } from "@expo/vector-icons"; import { router, useLocalSearchParams } from "expo-router"; import { useEffect, useState } from "react"; -import { SafeAreaView, ScrollView, Text, View } from "react-native"; +import { Text, View, VirtualizedList } from "react-native"; import { useSelector } from "react-redux"; @@ -34,101 +34,165 @@ export default function Discussion() { const [loading, setLoading] = useState(true) const arrSkeleton = Array.from({ length: 5 }, (_, index) => index) const [status, setStatus] = useState<'true' | 'false'>('true') + const [page, setPage] = useState(1) + const [waiting, setWaiting] = useState(false) - async function handleLoad(loading: boolean) { + async function handleLoad(loading: boolean, thisPage: number) { try { + setWaiting(true) setLoading(loading) + setPage(thisPage) const hasil = await decryptToken(String(token?.current)) - const response = await apiGetDiscussionGeneral({ user: hasil, active: status, search: search, group: String(group) }) - setData(response.data) + const response = await apiGetDiscussionGeneral({ user: hasil, active: status, search: search, group: String(group), page: thisPage }) + if (thisPage == 1) { + setData(response.data) + } else if (thisPage > 1 && response.data.length > 0) { + setData([...data, ...response.data]) + } else { + return; + } setNameGroup(response.filter.name) } catch (error) { console.error(error) } finally { setLoading(false) + setWaiting(false) } } - useEffect(() => { - handleLoad(true) - }, [status, search, group]) useEffect(() => { - handleLoad(false) + handleLoad(false, 1) }, [update]) - return ( - - - - - { setStatus("true") }} - label="Aktif" - icon={} - n={2} /> - { setStatus("false") }} - label="Arsip" - icon={} - n={2} /> - - - { - (entityUser.role == "supadmin" || entityUser.role == "developer") && - - Filter : {nameGroup} - - } - - { - loading ? - arrSkeleton.map((item: any, i: number) => { - return ( - - ) - }) - : - data.length > 0 - ? - data.map((item: any, i: number) => { - return ( - { router.push(`/discussion/${item.id}`) }} - borderType="bottom" - icon={ - - - - } - title={item.title} - subtitle={ - status != "false" && - } - rightTopInfo={item.createdAt} - desc={item.desc} - leftBottomInfo={ - - - Diskusikan - - } - rightBottomInfo={`${item.total_komentar} Komentar`} + useEffect(() => { + handleLoad(true, 1) + }, [status, search, group]) - /> - ) - }) - : - Tidak ada data - } - + + const loadMoreData = () => { + if (waiting) return + setTimeout(() => { + handleLoad(false, page + 1) + }, 1000); + }; + + const getItem = (_data: unknown, index: number): Props => ({ + id: data[index].id, + title: data[index].title, + desc: data[index].desc, + status: data[index].status, + total_komentar: data[index].total_komentar, + createdAt: data[index].createdAt, + }) + + return ( + + + + { setStatus("true") }} + label="Aktif" + icon={} + n={2} /> + { setStatus("false") }} + label="Arsip" + icon={} + n={2} /> - - + + { + (entityUser.role == "supadmin" || entityUser.role == "developer") && + + Filter : {nameGroup} + + } + + + { + loading ? + arrSkeleton.map((item: any, i: number) => { + return ( + + ) + }) + : + data.length > 0 + ? + data.length} + getItem={getItem} + renderItem={({ item, index }: { item: Props, index: number }) => { + return ( + { router.push(`/discussion/${item.id}`) }} + borderType="bottom" + icon={ + + + + } + title={item.title} + subtitle={ + status != "false" && + } + rightTopInfo={item.createdAt} + desc={item.desc} + leftBottomInfo={ + + + Diskusikan + + } + rightBottomInfo={`${item.total_komentar} Komentar`} + + /> + ) + }} + keyExtractor={(item, index) => String(index)} + onEndReached={loadMoreData} + onEndReachedThreshold={0.5} + showsVerticalScrollIndicator={false} + /> + // data.map((item: any, i: number) => { + // return ( + // { router.push(`/discussion/${item.id}`) }} + // borderType="bottom" + // icon={ + // + // + // + // } + // title={item.title} + // subtitle={ + // status != "false" && + // } + // rightTopInfo={item.createdAt} + // desc={item.desc} + // leftBottomInfo={ + // + // + // Diskusikan + // + // } + // rightBottomInfo={`${item.total_komentar} Komentar`} + + // /> + // ) + // }) + : + Tidak ada data + } + + ); } \ No newline at end of file diff --git a/app/(application)/group/index.tsx b/app/(application)/group/index.tsx index fc15aed..9720f53 100644 --- a/app/(application)/group/index.tsx +++ b/app/(application)/group/index.tsx @@ -13,7 +13,6 @@ import { apiDeleteGroup, apiEditGroup, apiGetGroup } from "@/lib/api"; import { setUpdateGroup } from "@/lib/groupSlice"; import { useAuthSession } from "@/providers/AuthProvider"; import { AntDesign, Feather, MaterialCommunityIcons } from "@expo/vector-icons"; -import { useLocalSearchParams } from "expo-router"; import { useEffect, useState } from "react"; import { SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native"; import { useDispatch, useSelector } from "react-redux"; @@ -26,7 +25,6 @@ type Props = { export default function Index() { const { token, decryptToken } = useAuthSession() - const { active } = useLocalSearchParams<{ active?: string }>() const [isModal, setModal] = useState(false) const [isVisibleEdit, setVisibleEdit] = useState(false) const [data, setData] = useState([]) @@ -42,6 +40,14 @@ export default function Index() { const dispatch = useDispatch() const update = useSelector((state: any) => state.groupUpdate) + const [data11, setData1] = useState(Array.from({ length: 20 }, (_, i) => `Item ${i}`)); + + const renderItem = ({ item }: { item: string }) => ( + + {item} + + ); + async function handleEdit() { try { diff --git a/app/(application)/member/index.tsx b/app/(application)/member/index.tsx index 3929173..3482d7e 100644 --- a/app/(application)/member/index.tsx +++ b/app/(application)/member/index.tsx @@ -9,7 +9,7 @@ import { useAuthSession } from "@/providers/AuthProvider"; import { AntDesign, Feather } from "@expo/vector-icons"; import { router, useLocalSearchParams } from "expo-router"; import { useEffect, useState } from "react"; -import { SafeAreaView, ScrollView, Text, View } from "react-native"; +import { Text, View, VirtualizedList } from "react-native"; import { useSelector } from "react-redux"; type Props = { @@ -37,88 +37,129 @@ export default function Index() { const arrSkeleton = Array.from({ length: 5 }, (_, index) => index) const [loading, setLoading] = useState(true) const [status, setStatus] = useState<'true' | 'false'>('true') + const [page, setPage] = useState(1) + const [waiting, setWaiting] = useState(false) - async function handleLoad(loading: boolean) { + async function handleLoad(loading: boolean, thisPage: number) { try { + setWaiting(true) setLoading(loading) + setPage(thisPage) const hasil = await decryptToken(String(token?.current)) - const response = await apiGetUser({ user: hasil, active: status, search: search, group: String(group) }) - setData(response.data) + const response = await apiGetUser({ user: hasil, active: status, search, group: String(group), page: thisPage }) + if (thisPage == 1) { + setData(response.data) + } else if (thisPage > 1 && response.data.length > 0) { + setData([...data, ...response.data]) + } else { + return; + } setNameGroup(response.filter.name) } catch (error) { console.error(error) } finally { setLoading(false) + setWaiting(false) } } - useEffect(() => { - handleLoad(true) - }, [status, search, group]) + const loadMoreData = () => { + if (waiting) return + setTimeout(() => { + handleLoad(false, page + 1) + }, 1000); + }; useEffect(() => { - handleLoad(false) + handleLoad(false, 1) }, [update]) + useEffect(() => { + handleLoad(true, 1) + }, [group, search, status]) + + + + + const getItem = (_data: unknown, index: number): Props => ({ + id: data[index].id, + name: data[index].name, + nik: data[index].nik, + email: data[index].email, + phone: data[index].phone, + gender: data[index].gender, + position: data[index].position, + group: data[index].group, + img: data[index].img, + isActive: data[index].isActive, + role: data[index].role, + }); return ( - - - - - { setStatus("true") }} - label="Aktif" - icon={} - n={2} /> - { setStatus("false") }} - label="Tidak Aktif" - icon={} - n={2} /> - - - { - (entityUser.role == "supadmin" || entityUser.role == "developer") && - - Filter : {nameGroup} - - } - - { - loading ? - arrSkeleton.map((item, index) => { - return ( - - ) - }) - : - data.length > 0 - ? - data.map((item, index) => { - return ( - { router.push(`/member/${item.id}`) }} - borderType="all" - icon={ - - } - title={item.name} - subtitle={`${item.group} - ${item.position}`} - /> - ) - }) - : - Tidak ada data - } - + + + + { setStatus("true") }} + label="Aktif" + icon={} + n={2} /> + { setStatus("false") }} + label="Tidak Aktif" + icon={} + n={2} /> - - + + { + (entityUser.role == "supadmin" || entityUser.role == "developer") && + + Filter : {nameGroup} + + } + + + { + loading ? + arrSkeleton.map((item, index) => { + return ( + + ) + }) + : + data.length > 0 + ? + data.length} + getItem={getItem} + renderItem={({ item, index }: { item: Props, index: number }) => { + return ( + { router.push(`/member/${item.id}`) }} + borderType="all" + icon={ + + } + title={item.name} + subtitle={`${item.group} - ${item.position}`} + /> + ) + }} + keyExtractor={(item, index) => String(index)} + onEndReached={loadMoreData} + onEndReachedThreshold={0.5} + showsVerticalScrollIndicator={false} + /> + : + Tidak ada data + } + + ) } \ No newline at end of file diff --git a/components/announcement/headerAnnouncementList.tsx b/components/announcement/headerAnnouncementList.tsx index 8e10aee..039323d 100644 --- a/components/announcement/headerAnnouncementList.tsx +++ b/components/announcement/headerAnnouncementList.tsx @@ -1,18 +1,23 @@ -import { useState } from "react" -import ButtonMenuHeader from "../buttonMenuHeader" -import DrawerBottom from "../drawerBottom" -import { View } from "react-native" -import MenuItemRow from "../menuItemRow" +import Styles from "@/constants/Styles" import { AntDesign } from "@expo/vector-icons" import { router } from "expo-router" -import Styles from "@/constants/Styles" +import { useState } from "react" +import { View } from "react-native" +import { useSelector } from "react-redux" +import ButtonMenuHeader from "../buttonMenuHeader" +import DrawerBottom from "../drawerBottom" +import MenuItemRow from "../menuItemRow" export default function HeaderRightAnnouncementList() { const [isVisible, setVisible] = useState(false) + const entityUser = useSelector((state: any) => state.user) return ( <> - { setVisible(true) }} /> + { + entityUser.role != 'user' && entityUser.role != 'coadmin' ? { setVisible(true) }} /> : <> + } + state.memberUpdate) + const dispatch = useDispatch() async function handleActive() { try { const hasil = await decryptToken(String(token?.current)) const response = await apiDeleteUser({ user: hasil, isActive: active }, id) + if (response.success) { + ToastAndroid.show('Berhasil mengupdate data', ToastAndroid.SHORT) + dispatch(setUpdateMember(!update)) + } else { + ToastAndroid.show(response.message, ToastAndroid.SHORT) + } } catch (error) { console.error(error) } finally { setVisible(false) - ToastAndroid.show('Berhasil mengupdate data', ToastAndroid.SHORT) - router.replace(`/member/${id}`); } } diff --git a/lib/api.ts b/lib/api.ts index 524df58..c07fee9 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -134,8 +134,8 @@ export const apiEditPosition = async (data: { user: string, name: string, idGrou }); }; -export const apiGetUser = async ({ user, active, search, group }: { user: string, active: string, search: string, group?: string }) => { - const response = await api.get(`mobile/user?user=${user}&active=${active}&group=${group}&search=${search}`); +export const apiGetUser = async ({ user, active, search, group, page }: { user: string, active: string, search: string, group?: string, page?: number }) => { + const response = await api.get(`mobile/user?user=${user}&active=${active}&group=${group}&search=${search}&page=${page}`); return response.data; }; @@ -150,12 +150,8 @@ export const apiCreateUser = async (data: FormData) => { }; export const apiDeleteUser = async (data: { user: string, isActive: boolean }, id: string) => { - await api.delete(`mobile/user/${id}`, { data }).then(response => { - return response.data; - }) - .catch(error => { - console.error('Error:', error); - }); + const response = await api.delete(`mobile/user/${id}`, { data }) + return response.data }; export const apiEditUser = async (data: FormData, id: string) => { @@ -167,8 +163,8 @@ export const apiEditUser = async (data: FormData, id: string) => { return response.data; }; -export const apiGetDiscussionGeneral = async ({ user, active, search, group }: { user: string, active: string, search: string, group?: string }) => { - const response = await api.get(`mobile/discussion-general?user=${user}&active=${active}&group=${group}&search=${search}`); +export const apiGetDiscussionGeneral = async ({ user, active, search, group, page }: { user: string, active: string, search: string, group?: string, page?: number }) => { + const response = await api.get(`mobile/discussion-general?user=${user}&active=${active}&group=${group}&search=${search}&page=${page}`); return response.data; }; @@ -223,8 +219,8 @@ export const apiAddMemberDiscussionGeneral = async ({ data, id }: { data: { user return response.data; }; -export const apiGetAnnouncement = async ({ user, search }: { user: string, search: string }) => { - const response = await api.get(`mobile/announcement?user=${user}&search=${search}`); +export const apiGetAnnouncement = async ({ user, search, page }: { user: string, search: string, page?: number }) => { + const response = await api.get(`mobile/announcement?user=${user}&search=${search}&page=${page}`); return response.data; };