Files
mobile-darmasaba/app/(application)/division/[id]/info.tsx
amaliadwiy d8a50cbc75 feat: redesign halaman anggota diskusi dan info divisi dengan pola sectionCard
- redesign discussion/member/[id]: flat list airy, bubble avatar row, header jumlah anggota, button tambah ikuti pola create
- redesign division/[id]/info: sectionCard deskripsi, list anggota dengan role label, drawer menu konsisten
- fix division/[id]/add-member: border warna mengikuti tema
2026-05-20 17:01:08 +08:00

320 lines
14 KiB
TypeScript

import AppHeader from "@/components/AppHeader"
import DrawerBottom from "@/components/drawerBottom"
import HeaderRightDivisionInfo from "@/components/division/headerDivisionInfo"
import ImageUser from "@/components/imageNew"
import MenuItemRow from "@/components/menuItemRow"
import ModalConfirmation from "@/components/ModalConfirmation"
import SectionCancel from "@/components/sectionCancel"
import Text from "@/components/Text"
import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiGetDivisionOneFeature, apiUpdateStatusAdminDivision } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider"
import { useTheme } from "@/providers/ThemeProvider"
import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react"
import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
import Toast from "react-native-toast-message"
import { useSelector } from "react-redux"
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 { colors } = useTheme()
const [refreshing, setRefreshing] = useState(false)
const entityUser = useSelector((state: any) => state.user)
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 update = useSelector((state: any) => state.divisionUpdate)
const [loading, setLoading] = useState(true)
const SKELETON_COUNT = 5
const [isMemberDivision, setIsMemberDivision] = useState(false)
const [isAdminDivision, setIsAdminDivision] = useState(false)
const [dataMemberChoose, setDataMemberChoose] = useState({
id: '',
name: '',
isAdmin: false
})
const [showDeleteModal, setShowDeleteModal] = useState(false)
function handleMemberOut() {
setModal(false)
setTimeout(() => {
setShowDeleteModal(true)
}, 600)
}
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)
Toast.show({ type: 'small', text1: 'Berhasil mengeluarkan anggota', })
} else {
Toast.show({ type: 'small', text1: response.message, })
}
} catch (error: any) {
console.error(error);
const message = error?.response?.data?.message || "Gagal mengeluarkan anggota"
Toast.show({ type: 'small', text1: message })
} 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)
Toast.show({ type: 'small', text1: dataMemberChoose.isAdmin ? 'Berhasil memberhentikan admin' : 'Berhasil menjadi admin', })
} else {
Toast.show({ type: 'small', text1: response.message, })
}
} catch (error: any) {
console.error(error);
const message = error?.response?.data?.message || "Gagal mengubah status admin"
Toast.show({ type: 'small', text1: message })
} finally {
setModal(false)
}
}
async function handleLoad(loading: boolean) {
try {
setLoading(loading)
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)
} finally {
setLoading(false)
}
}
const handleRefresh = async () => {
setRefreshing(true)
handleLoad(false)
handleCheckMember()
await new Promise(resolve => setTimeout(resolve, 2000));
setRefreshing(false)
};
async function handleCheckMember() {
try {
const hasil = await decryptToken(String(token?.current));
const response = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-member",
});
const response2 = await apiGetDivisionOneFeature({
id,
user: hasil,
cat: "check-admin",
});
setIsMemberDivision(response.data);
setIsAdminDivision(response2.data);
} catch (error) {
console.error(error);
}
}
useEffect(() => {
handleLoad(false)
}, [refresh, update])
useEffect(() => {
handleLoad(true)
handleCheckMember()
}, [])
function handleChooseMember(item: PropsMember) {
setDataMemberChoose(item)
setModal(true)
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}>
<Stack.Screen
options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Informasi Divisi',
headerTitleAlign: 'center',
// headerRight: () => ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />,
header: () => (
<AppHeader
title="Informasi Divisi"
showBack={true}
onPressLeft={() => router.back()}
right={
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && <HeaderRightDivisionInfo id={id} active={dataDetail?.isActive} />
}
/>
)
}}
/>
<ScrollView
showsVerticalScrollIndicator={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={colors.icon} />}
style={[Styles.h100, { backgroundColor: colors.background }]}
>
<View style={[Styles.p15, Styles.mb100]}>
{dataDetail?.isActive === false && <SectionCancel title="Divisi nonaktif" />}
{/* Deskripsi */}
<View style={[Styles.wrapPaper, Styles.sectionCard, Styles.noShadow, Styles.mb15,
{ backgroundColor: colors.card, borderColor: colors.icon + '18' }]}>
<View style={[Styles.sectionActionRow, { marginBottom: 12 }]}>
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
<MaterialIcons name="info-outline" size={18} color={colors.dimmed} />
</View>
<Text style={[Styles.textDefaultSemiBold, Styles.flex1, { color: colors.text }]}>Deskripsi</Text>
</View>
{loading
? Array.from({ length: 3 }).map((_, i) => (
<View key={i} style={{ height: 13, borderRadius: 6, marginBottom: 8, backgroundColor: colors.icon + '20', width: i === 2 ? '60%' : '100%' }} />
))
: <Text style={[Styles.textDefault, { color: colors.text, lineHeight: 22 }]}>{dataDetail?.desc}</Text>
}
</View>
{/* Tombol tambah anggota */}
{((entityUser.role !== "user" && entityUser.role !== "coadmin") || isAdminDivision) && dataDetail?.isActive && (
<View style={[Styles.wrapPaper, Styles.sectionCard, Styles.mb15,
{ backgroundColor: colors.card, borderColor: colors.icon + '18' }]}>
<Pressable
onPress={() => router.push(`/division/${id}/add-member`)}
style={Styles.sectionActionRow}
>
<View style={[Styles.sectionIconBox, { backgroundColor: colors.icon + '18' }]}>
<MaterialCommunityIcons name="account-plus-outline" size={18} color={colors.icon} />
</View>
<Text style={[Styles.textDefaultSemiBold, Styles.flex1, { color: colors.text }]}>Tambah Anggota</Text>
<MaterialCommunityIcons name="chevron-right" size={18} color={colors.dimmed} />
</Pressable>
</View>
)}
{/* Daftar anggota */}
<View style={[Styles.wrapPaper, Styles.sectionCard,
{ backgroundColor: colors.card, borderColor: colors.icon + '18', padding: 0, overflow: 'hidden' }]}>
{/* Header */}
<View style={[Styles.sectionActionRow, { padding: 16, borderBottomWidth: 1, borderBottomColor: colors.icon + '14' }]}>
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
<MaterialIcons name="people" size={18} color={colors.dimmed} />
</View>
<Text style={[Styles.textDefaultSemiBold, Styles.flex1, { color: colors.text }]}>Anggota</Text>
{!loading && (
<Text style={[Styles.textMediumNormal, { color: colors.dimmed }]}>{dataMember.length} anggota</Text>
)}
</View>
{loading
? Array.from({ length: SKELETON_COUNT }).map((_, i) => (
<View key={i} style={[Styles.rowItemsCenter, Styles.ph15,
{ paddingVertical: 14, gap: 14, borderBottomWidth: i < SKELETON_COUNT - 1 ? 1 : 0, borderBottomColor: colors.icon + '14' }]}>
<View style={[Styles.userProfileExtraSmall, { backgroundColor: colors.icon + '20', borderRadius: 100 }]} />
<View style={{ height: 13, borderRadius: 6, flex: 1, backgroundColor: colors.icon + '20', maxWidth: 140 + (i % 3) * 30 }} />
</View>
))
: dataMember.length === 0
? (
<View style={[Styles.contentItemCenter, { paddingVertical: 40 }]}>
<MaterialIcons name="people-outline" size={34} color={colors.icon + '50'} />
<Text style={[Styles.textMediumNormal, Styles.mt10, { color: colors.dimmed }]}>Belum ada anggota</Text>
</View>
)
: dataMember.map((item, index) => {
const canPress = dataDetail?.isActive && (isAdminDivision || (entityUser.role !== "user" && entityUser.role !== "coadmin"))
return (
<Pressable
key={index}
onPress={() => canPress && handleChooseMember(item)}
style={({ pressed }) => [
Styles.rowItemsCenter, Styles.ph15,
{
paddingVertical: 13, gap: 14,
borderBottomWidth: index < dataMember.length - 1 ? 1 : 0,
borderBottomColor: colors.icon + '14',
backgroundColor: pressed && canPress ? colors.icon + '0E' : 'transparent',
},
]}
>
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
<Text style={[Styles.textDefault, Styles.flex1, { color: colors.text }]} numberOfLines={1}>
{item.name}
</Text>
<Text style={[Styles.textMediumNormal, { color: item.isAdmin ? colors.tabActive : colors.dimmed }]}>
{item.isAdmin ? 'Admin' : 'Anggota'}
</Text>
{canPress && <MaterialCommunityIcons name="chevron-right" size={18} color={colors.icon + '60'} />}
</Pressable>
)
})
}
</View>
</View>
</ScrollView>
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title={dataMemberChoose.name}>
<View style={Styles.rowItemsCenter}>
<MenuItemRow
icon={<MaterialIcons name="verified-user" color={colors.text} size={25} />}
title={dataMemberChoose.isAdmin ? 'Berhentikan admin' : 'Jadikan admin'}
onPress={handleMemberAdmin}
/>
<MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color={colors.text} size={25} />}
title="Keluarkan"
onPress={handleMemberOut}
/>
</View>
</DrawerBottom>
<ModalConfirmation
visible={showDeleteModal}
title="Konfirmasi"
message="Apakah anda yakin ingin mengeluarkan anggota?"
onConfirm={() => {
setShowDeleteModal(false)
memberOut()
}}
onCancel={() => setShowDeleteModal(false)}
confirmText="Keluar"
cancelText="Batal"
/>
</SafeAreaView>
)
}