upd: divisi

Deskripsi:
- load data divisi
- user role pada page list divisi
- detail divisi
- info divisi
- tambah anggota divisi
- hapus anggota divisi
- update status admin divisi

No Issues
This commit is contained in:
amel
2025-05-21 15:57:03 +08:00
parent 2db0b45964
commit 1f5e00e612
12 changed files with 536 additions and 148 deletions

View File

@@ -0,0 +1,163 @@
import ButtonBackHeader from "@/components/buttonBackHeader";
import ButtonSaveHeader from "@/components/buttonSaveHeader";
import ImageUser from "@/components/imageNew";
import ImageWithLabel from "@/components/imageWithLabel";
import InputSearch from "@/components/inputSearch";
import Styles from "@/constants/Styles";
import { apiAddMemberDivision, apiGetDivisionOneDetail, apiGetProjectOne, apiGetUser } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react";
import { Pressable, SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native";
type Props = {
idUser: string,
name: string,
img: string
}
export default function AddMemberDivision() {
const { token, decryptToken } = useAuthSession()
const { id } = useLocalSearchParams<{ id: string }>()
const [dataOld, setDataOld] = useState<Props[]>([])
const [data, setData] = useState<Props[]>([])
const [idGroup, setIdGroup] = useState('')
const [selectMember, setSelectMember] = useState<any[]>([])
const [search, setSearch] = useState('')
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDivisionOneDetail({ user: hasil, id })
setDataOld(response.data.member)
setIdGroup(response.data.division.idGroup)
const responsemember = await apiGetUser({ user: hasil, active: "true", search: search, group: String(response.data.division.idGroup) })
setData(responsemember.data.filter((i: any) => i.idUserRole != 'supadmin'))
} catch (error) {
console.error(error)
}
}
async function handleLoadMember(search: string) {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetUser({ user: hasil, active: "true", search: search, group: String(idGroup) })
setData(response.data.filter((i: any) => i.idUserRole != 'supadmin'))
}
useEffect(() => {
handleLoad()
}, []);
function handleSearch(search: string) {
setSearch(search)
handleLoadMember(search)
}
function onChoose(val: string, label: string, img?: string) {
if (selectMember.some((i: any) => i.idUser == val)) {
setSelectMember(selectMember.filter((i: any) => i.idUser != val))
} else {
setSelectMember([...selectMember, { idUser: val, name: label, img }])
}
}
async function handleAddMember() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiAddMemberDivision({ id: id, data: { user: hasil, member: selectMember } })
if (response.success) {
ToastAndroid.show('Berhasil menambahkan anggota', ToastAndroid.SHORT)
router.replace(`/division/${id}/info`)
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT)
}
} catch (error) {
console.error(error)
ToastAndroid.show('Gagal menambahkan anggota', ToastAndroid.SHORT)
}
}
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Tambah Anggota',
headerTitleAlign: 'center',
headerRight: () => (
<ButtonSaveHeader
category="update"
disable={selectMember.length > 0 ? false : true}
onPress={() => {
handleAddMember()
}}
/>
)
}}
/>
<View style={[Styles.p15]}>
<InputSearch onChange={(val) => handleSearch(val)} value={search} />
{
selectMember.length > 0
?
<View>
<ScrollView horizontal style={[Styles.mb10, Styles.pv10]}>
{
selectMember.map((item: any, index: any) => (
<ImageWithLabel
key={index}
label={item.name}
src={item.img}
onClick={() => onChoose(item.idUser, item.name, item.img)}
/>
))
}
</ScrollView>
</View>
:
<Text style={[Styles.textDefault, Styles.cGray, Styles.pv05, { textAlign: 'center' }]}>Tidak ada member yang dipilih</Text>
}
<ScrollView>
{
data.length > 0 ?
data.map((item: any, index: any) => {
const found = dataOld.some((i: any) => i.idUser == item.id)
return (
<Pressable
key={index}
style={[Styles.itemSelectModal]}
onPress={() => {
!found && onChoose(item.id, item.name, item.img)
}}
>
<View style={[Styles.rowItemsCenter]}>
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} border />
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode="tail">{item.name}</Text>
{
found && <Text style={[Styles.textInformation, Styles.cGray]}>sudah menjadi anggota</Text>
}
</View>
</View>
{
selectMember.some((i: any) => i.idUser == item.id) && <AntDesign name="check" size={20} />
}
</Pressable>
)
}
)
:
<Text style={[Styles.textDefault, { textAlign: 'center' }]}>Tidak ada data</Text>
}
</ScrollView>
</View>
</SafeAreaView>
)
}

View File

@@ -6,17 +6,51 @@ import HeaderRightDivisionDetail from "@/components/division/headerDivisionDetai
import TaskDivisionDetail from "@/components/division/taskDivisionDetail"
import CaraouselHome from "@/components/home/carouselHome"
import Styles from "@/constants/Styles"
import { apiGetDivisionOneDetail } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react"
import { SafeAreaView, ScrollView, View } from "react-native"
type Props = {
id: string,
idVillage: string,
idGroup: string,
name: string,
desc: string,
isActive: boolean,
}
export default function DetailDivisionFitur() {
const { id } = useLocalSearchParams()
const { token, decryptToken } = useAuthSession()
const { id } = useLocalSearchParams<{ id: string }>()
const [data, setData] = useState<Props>()
const [loading, setLoading] = useState(true)
async function handleLoad() {
try {
setLoading(true)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDivisionOneDetail({ user: hasil, id })
setData(response.data.division)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad()
}, [])
return (
<SafeAreaView>
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Judul Divisi',
headerTitle: loading ? 'Loading...' : data?.name,
headerTitleAlign: 'center',
headerRight: () => <HeaderRightDivisionDetail id={id} />,
}}

View File

@@ -3,31 +3,109 @@ import BorderBottomItem from "@/components/borderBottomItem"
import ButtonBackHeader from "@/components/buttonBackHeader"
import HeaderRightDivisionInfo from "@/components/division/headerDivisionInfo"
import DrawerBottom from "@/components/drawerBottom"
import ImageUser from "@/components/imageNew"
import { ColorsStatus } from "@/constants/ColorsStatus"
import Styles from "@/constants/Styles"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiUpdateStatusAdminDivision } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { useState } from "react"
import { Image, Pressable, SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native"
import { useEffect, useState } from "react"
import { Pressable, SafeAreaView, ScrollView, Text, ToastAndroid, View } from "react-native"
type PropsDetail = {
id: string,
idVillage: string,
idGroup: string,
name: string,
desc: string,
isActive: boolean,
}
type PropsMember = {
id: string,
isAdmin: boolean,
isLeader: boolean,
idUser: string,
name: string,
img: string
}
export default function InformationDivision() {
const { id } = useLocalSearchParams()
const { id } = useLocalSearchParams<{ id: string }>()
const [isModal, setModal] = useState(false)
const { token, decryptToken } = useAuthSession()
const [dataDetail, setDataDetail] = useState<PropsDetail>()
const [dataMember, setDataMember] = useState<PropsMember[]>([])
const [refresh, setRefresh] = useState(false)
const [dataMemberChoose, setDataMemberChoose] = useState({
id: '',
name: '',
isAdmin: false
})
function handleMemberOut() {
AlertKonfirmasi({
title: 'Konfirmasi',
desc: 'Apakah anda yakin ingin mengeluarkan anggota?',
onPress: () => {
ToastAndroid.show('Berhasil mengeluarkan anggota', ToastAndroid.SHORT)
setModal(false)
}
onPress: () => { memberOut() }
})
}
function handleMemberAdmin() {
ToastAndroid.show('Berhasil memberhentikan admin', ToastAndroid.SHORT)
setModal(false)
async function memberOut() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiDeleteMemberDivision({ user: hasil, id: dataMemberChoose.id }, id)
if (response.success) {
setRefresh(!refresh)
ToastAndroid.show('Berhasil mengeluarkan anggota', ToastAndroid.SHORT)
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT)
}
} catch (error) {
console.error(error)
ToastAndroid.show('Terjadi kesalahan', ToastAndroid.SHORT)
} finally {
setModal(false)
}
}
async function handleMemberAdmin() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiUpdateStatusAdminDivision({ user: hasil, id: dataMemberChoose.id, isAdmin: dataMemberChoose.isAdmin }, id)
if (response.success) {
setRefresh(!refresh)
ToastAndroid.show(dataMemberChoose.isAdmin ? 'Berhasil memberhentikan admin' : 'Berhasil menjadi admin', ToastAndroid.SHORT)
} else {
ToastAndroid.show(response.message, ToastAndroid.SHORT)
}
} catch (error) {
console.error(error)
ToastAndroid.show('Terjadi kesalahan', ToastAndroid.SHORT)
} finally {
setModal(false)
}
}
async function handleLoad() {
try {
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDivisionOneDetail({ user: hasil, id })
setDataDetail(response.data.division)
setDataMember(response.data.member)
} catch (error) {
console.error(error)
}
}
useEffect(() => {
handleLoad()
}, [refresh])
function handleChooseMember(item: PropsMember) {
setDataMemberChoose(item)
setModal(true)
}
return (
@@ -45,13 +123,14 @@ export default function InformationDivision() {
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mb05]}>Deskripsi Divisi</Text>
<View style={[Styles.wrapPaper]}>
<Text>There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable.</Text>
<Text>{dataDetail?.desc}</Text>
</View>
</View>
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefault, Styles.mv05]}>5 Anggota</Text>
<Text style={[Styles.textDefault, Styles.mv05]}>{dataMember.length} Anggota</Text>
<View style={[Styles.wrapPaper]}>
<BorderBottomItem
onPress={() => { router.push(`/division/${id}/add-member`) }}
borderType="none"
icon={
<View style={[Styles.iconContent, ColorsStatus.gray]}>
@@ -60,73 +139,29 @@ export default function InformationDivision() {
}
title="Tambah Anggota"
/>
<BorderBottomItem
borderType="bottom"
onPress={() => { setModal(true) }}
icon={
<Image
source={require("../../../../assets/images/user.jpeg")}
style={[Styles.userProfileSmall]}
/>
}
title="Amalia Dwi"
rightTopInfo="Admin"
/>
<BorderBottomItem
borderType="bottom"
onPress={() => { setModal(true) }}
icon={
<Image
source={require("../../../../assets/images/user.jpeg")}
style={[Styles.userProfileSmall]}
/>
}
title="Amalia Dwi"
rightTopInfo="Admin"
/>
<BorderBottomItem
borderType="bottom"
onPress={() => { setModal(true) }}
icon={
<Image
source={require("../../../../assets/images/user.jpeg")}
style={[Styles.userProfileSmall]}
/>
}
title="Amalia Dwi"
rightTopInfo="Anggota"
/>
<BorderBottomItem
borderType="bottom"
onPress={() => { setModal(true) }}
icon={
<Image
source={require("../../../../assets/images/user.jpeg")}
style={[Styles.userProfileSmall]}
/>
}
title="Amalia Dwi"
rightTopInfo="Anggota"
/>
<BorderBottomItem
borderType="bottom"
onPress={() => { setModal(true) }}
icon={
<Image
source={require("../../../../assets/images/user.jpeg")}
style={[Styles.userProfileSmall]}
/>
}
title="Amalia Dwi"
rightTopInfo="Anggota"
/>
{
dataMember.map((item, index) => {
return (
<BorderBottomItem
width={55}
key={index}
borderType="bottom"
onPress={() => { handleChooseMember(item) }}
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
}
title={item.name}
rightTopInfo={item.isAdmin ? "Admin" : "Anggota"}
/>
)
})
}
</View>
</View>
</View>
</ScrollView>
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title="Amalia">
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title={dataMemberChoose.name}>
<View>
<Pressable style={[Styles.wrapItemBorderBottom]} onPress={() => { handleMemberAdmin() }}>
<View style={[Styles.rowItemsCenter]}>
@@ -135,7 +170,7 @@ export default function InformationDivision() {
</View>
<View style={[Styles.rowSpaceBetween, { width: '88%' }]}>
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>Memberhentikan sebagai admin</Text>
<Text style={[Styles.textDefault]}>{dataMemberChoose.isAdmin ? 'Memberhentikan sebagai admin' : 'Jadikan admin'}</Text>
</View>
</View>
</View>