Merge pull request 'amalia/20-mei-26' (#51) from amalia/20-mei-26 into join

Reviewed-on: #51
This commit is contained in:
2026-05-20 17:21:40 +08:00
4 changed files with 316 additions and 288 deletions

View File

@@ -1,21 +1,18 @@
import AppHeader from "@/components/AppHeader"; import AppHeader from "@/components/AppHeader";
import BorderBottomItem from "@/components/borderBottomItem";
import DrawerBottom from "@/components/drawerBottom"; import DrawerBottom from "@/components/drawerBottom";
import ImageUser from "@/components/imageNew"; import ImageUser from "@/components/imageNew";
import MenuItemRow from "@/components/menuItemRow"; import MenuItemRow from "@/components/menuItemRow";
import ModalConfirmation from "@/components/ModalConfirmation"; import ModalConfirmation from "@/components/ModalConfirmation";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import Text from '@/components/Text'; import Text from '@/components/Text';
import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api"; import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider"; import { useAuthSession } from "@/providers/AuthProvider";
import { useTheme } from "@/providers/ThemeProvider"; import { useTheme } from "@/providers/ThemeProvider";
import { Feather, MaterialCommunityIcons } from "@expo/vector-icons"; import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { SafeAreaView, ScrollView, View } from "react-native"; import { Pressable, SafeAreaView, ScrollView, View } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
@@ -25,6 +22,8 @@ type Props = {
img: string img: string
} }
const SKELETON_COUNT = 5
export default function MemberDiscussionDetail() { export default function MemberDiscussionDetail() {
const { token, decryptToken } = useAuthSession() const { token, decryptToken } = useAuthSession()
const { colors } = useTheme(); const { colors } = useTheme();
@@ -35,13 +34,12 @@ export default function MemberDiscussionDetail() {
const [chooseUser, setChooseUser] = useState({ idUser: '', name: '', img: '' }) const [chooseUser, setChooseUser] = useState({ idUser: '', name: '', img: '' })
const update = useSelector((state: any) => state.discussionGeneralDetailUpdate) const update = useSelector((state: any) => state.discussionGeneralDetailUpdate)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
const [showDeleteModal, setShowDeleteModal] = useState(false) const [showDeleteModal, setShowDeleteModal] = useState(false)
const canManage = entityUser.role !== "user" && entityUser.role !== "coadmin"
async function handleLoad(showLoadingIndicator: boolean) {
async function handleLoad(loading: boolean) {
try { try {
setLoading(loading) setLoading(showLoadingIndicator)
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
const response = await apiGetDiscussionGeneralOne({ id: id, user: hasil, cat: 'anggota' }) const response = await apiGetDiscussionGeneralOne({ id: id, user: hasil, cat: 'anggota' })
setData(response.data) setData(response.data)
@@ -52,26 +50,18 @@ export default function MemberDiscussionDetail() {
} }
} }
useEffect(() => { useEffect(() => { handleLoad(false) }, [update]);
handleLoad(false) useEffect(() => { handleLoad(true) }, []);
}, [update]);
useEffect(() => {
handleLoad(true)
}, []);
async function handleDeleteUser() { async function handleDeleteUser() {
try { try {
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
await apiDeleteMemberDiscussionGeneral({ user: hasil, idUser: chooseUser.idUser }, id) await apiDeleteMemberDiscussionGeneral({ user: hasil, idUser: chooseUser.idUser }, id)
Toast.show({ type: 'small', text1: 'Berhasil mengeluarkan anggota dari diskusi', }) Toast.show({ type: 'small', text1: 'Berhasil mengeluarkan anggota dari diskusi' })
handleLoad(false) handleLoad(false)
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
const message = error?.response?.data?.message || "Gagal mengeluarkan anggota" Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal mengeluarkan anggota" })
Toast.show({ type: 'small', text1: message })
} finally { } finally {
setModal(false) setModal(false)
} }
@@ -81,9 +71,6 @@ export default function MemberDiscussionDetail() {
<SafeAreaView style={[Styles.flex1, { backgroundColor: colors.background }]}> <SafeAreaView style={[Styles.flex1, { backgroundColor: colors.background }]}>
<Stack.Screen <Stack.Screen
options={{ options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Anggota Diskusi',
headerTitleAlign: 'center',
header: () => ( header: () => (
<AppHeader <AppHeader
title="Anggota Diskusi" title="Anggota Diskusi"
@@ -93,49 +80,82 @@ export default function MemberDiscussionDetail() {
) )
}} }}
/> />
<ScrollView style={[Styles.h100, Styles.flex1, { backgroundColor: colors.background }]}> <ScrollView showsVerticalScrollIndicator={false} style={[Styles.h100, Styles.flex1, { backgroundColor: colors.background }]}>
<View style={[Styles.p15]}> <View style={[Styles.p15, Styles.mb100]}>
<Text style={[Styles.textDefault, Styles.mv05]}>{data.length} Anggota</Text>
<View style={[Styles.wrapPaper, { backgroundColor: colors.card, borderColor: colors.background }]}> {/* Tombol tambah anggota */}
{ {canManage && (
entityUser.role != "user" && entityUser.role != "coadmin" && <View style={[Styles.wrapPaper, Styles.sectionCard, Styles.mb15,
<BorderBottomItem { backgroundColor: colors.card, borderColor: colors.icon + '18' }]}>
onPress={() => { router.push(`/discussion/add-member/${id}`) }} <Pressable
borderType="none" onPress={() => router.push(`/discussion/add-member/${id}`)}
icon={ style={Styles.sectionActionRow}
<View style={[Styles.iconContent, ColorsStatus.gray]}> >
<Feather name="user-plus" size={25} color={'#384288'} /> <View style={[Styles.sectionIconBox, { backgroundColor: colors.icon + '18' }]}>
<MaterialCommunityIcons name="account-plus-outline" size={18} color={colors.icon} />
</View>
<View style={Styles.flex1}>
<Text style={[Styles.textDefaultSemiBold, { color: colors.text }]}>Tambah Anggota</Text>
</View>
<MaterialCommunityIcons name="chevron-right" size={18} color={colors.dimmed} />
</Pressable>
</View>
)}
{/* Full list */}
<View style={[Styles.wrapPaper, Styles.sectionCard,
{ backgroundColor: colors.card, borderColor: colors.icon + '18', padding: 0, overflow: 'hidden' }]}>
<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 }]}>{data.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>
))
: data.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> </View>
} )
title="Tambah Anggota" : data.map((item, index) => (
/> <Pressable
} key={index}
{ onPress={() => { setChooseUser(item); setModal(true) }}
loading ? style={({ pressed }) => [
arrSkeleton.map((item, index) => { Styles.rowItemsCenter, Styles.ph15,
return ( {
<SkeletonTwoItem key={index} /> paddingVertical: 13, gap: 14,
) borderBottomWidth: index < data.length - 1 ? 1 : 0,
}) borderBottomColor: colors.icon + '14',
: backgroundColor: pressed ? colors.icon + '0E' : 'transparent',
data.map((item, index) => { },
return ( ]}
<BorderBottomItem >
key={index} <ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
borderType="bottom" <Text style={[Styles.textDefault, Styles.flex1, { color: colors.text }]} numberOfLines={1}>
icon={ {item.name}
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" /> </Text>
} <MaterialCommunityIcons name="chevron-right" size={18} color={colors.icon + '60'} />
title={item.name} </Pressable>
onPress={() => { ))
setChooseUser(item)
setModal(true)
}}
/>
)
})
} }
</View> </View>
</View> </View>
</ScrollView> </ScrollView>
@@ -149,20 +169,16 @@ export default function MemberDiscussionDetail() {
router.push(`/member/${chooseUser.idUser}`) router.push(`/member/${chooseUser.idUser}`)
}} }}
/> />
{ {canManage && (
entityUser.role != "user" && entityUser.role != "coadmin" &&
<MenuItemRow <MenuItemRow
icon={<MaterialCommunityIcons name="account-remove" color={colors.text} size={25} />} icon={<MaterialCommunityIcons name="account-remove" color={colors.text} size={25} />}
title="Keluarkan" title="Keluarkan"
onPress={() => { onPress={() => {
setModal(false) setModal(false)
setTimeout(() => { setTimeout(() => setShowDeleteModal(true), 600)
setShowDeleteModal(true)
}, 600)
}} }}
/> />
} )}
</View> </View>
</DrawerBottom> </DrawerBottom>
@@ -170,10 +186,7 @@ export default function MemberDiscussionDetail() {
visible={showDeleteModal} visible={showDeleteModal}
title="Konfirmasi" title="Konfirmasi"
message="Apakah anda yakin ingin mengeluarkan anggota?" message="Apakah anda yakin ingin mengeluarkan anggota?"
onConfirm={() => { onConfirm={() => { setShowDeleteModal(false); handleDeleteUser() }}
setShowDeleteModal(false)
handleDeleteUser()
}}
onCancel={() => setShowDeleteModal(false)} onCancel={() => setShowDeleteModal(false)}
confirmText="Hapus" confirmText="Hapus"
cancelText="Batal" cancelText="Batal"

View File

@@ -1,12 +1,9 @@
import ModalConfirmation from "@/components/ModalConfirmation"
import AppHeader from "@/components/AppHeader" import AppHeader from "@/components/AppHeader"
import BorderBottomItem from "@/components/borderBottomItem"
import ButtonBackHeader from "@/components/buttonBackHeader"
import HeaderRightCalendarDetail from "@/components/calendar/headerCalendarDetail"
import DrawerBottom from "@/components/drawerBottom" import DrawerBottom from "@/components/drawerBottom"
import HeaderRightCalendarDetail from "@/components/calendar/headerCalendarDetail"
import ImageUser from "@/components/imageNew" import ImageUser from "@/components/imageNew"
import MenuItemRow from "@/components/menuItemRow" import MenuItemRow from "@/components/menuItemRow"
import Skeleton from "@/components/skeleton" import ModalConfirmation from "@/components/ModalConfirmation"
import Text from "@/components/Text" import Text from "@/components/Text"
import { ConstEnv } from "@/constants/ConstEnv" import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles" import Styles from "@/constants/Styles"
@@ -14,7 +11,7 @@ import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature }
import { setUpdateCalendar } from "@/lib/calendarUpdate" import { setUpdateCalendar } from "@/lib/calendarUpdate"
import { useAuthSession } from "@/providers/AuthProvider" import { useAuthSession } from "@/providers/AuthProvider"
import { useTheme } from "@/providers/ThemeProvider" import { useTheme } from "@/providers/ThemeProvider"
import { MaterialCommunityIcons } from "@expo/vector-icons" import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import Clipboard from "@react-native-clipboard/clipboard" import Clipboard from "@react-native-clipboard/clipboard"
import { router, Stack, useLocalSearchParams } from "expo-router" import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
@@ -156,135 +153,142 @@ export default function DetailEventCalendar() {
setRefreshing(false) setRefreshing(false)
}; };
const canManage = !((entityUser.role === "user" || entityUser.role === "coadmin") && !isMemberDivision)
const repeatLabel: Record<string, string> = {
once: 'Acara 1 Kali',
daily: 'Setiap Hari',
weekly: 'Mingguan',
monthly: 'Bulanan',
yearly: 'Tahunan',
}
function InfoRow({ icon, label, value, onCopy }: { icon: string, label: string, value?: string, onCopy?: () => void }) {
return (
<View style={[Styles.sectionActionRow, { paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: colors.icon + '14' }]}>
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
<MaterialCommunityIcons name={icon as any} size={18} color={colors.dimmed} />
</View>
<View style={Styles.flex1}>
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed, marginBottom: 2 }]}>{label}</Text>
{loading
? <View style={{ height: 13, borderRadius: 6, backgroundColor: colors.icon + '20', width: '70%' }} />
: <Text style={[Styles.textDefault, { color: colors.text }]}>{value || '-'}</Text>
}
</View>
{onCopy && !loading && value && (
<Pressable onPress={onCopy} style={{ padding: 4 }}>
<MaterialCommunityIcons name="content-copy" size={16} color={colors.dimmed} />
</Pressable>
)}
</View>
)
}
return ( return (
<SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}> <SafeAreaView style={[Styles.flex1, { backgroundColor: colors.background }]}>
<Stack.Screen <Stack.Screen
options={{ options={{
// headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: 'Detail Acara',
headerTitleAlign: 'center',
// headerRight: () => (entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision ? <></> : <HeaderRightCalendarDetail id={String(data?.idCalendar)} idReminder={String(detail)} />
header: () => ( header: () => (
<AppHeader <AppHeader
title="Detail Acara" title="Detail Acara"
showBack={true} showBack={true}
onPressLeft={() => router.back()} onPressLeft={() => router.back()}
right={ right={
(entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision ? <></> : <HeaderRightCalendarDetail id={String(data?.idCalendar)} idReminder={String(detail)} /> (entityUser.role === "user" || entityUser.role === "coadmin") && !isMemberDivision
? <></> : <HeaderRightCalendarDetail id={String(data?.idCalendar)} idReminder={String(detail)} />
} }
/> />
) )
}} }}
/> />
<ScrollView <ScrollView
style={[Styles.h100]} showsVerticalScrollIndicator={false}
refreshControl={ style={Styles.h100}
<RefreshControl refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={colors.icon} />}
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={colors.icon}
/>
}
> >
<View style={[Styles.p15]}> <View style={[Styles.p15, Styles.mb100]}>
<View style={[Styles.wrapPaper, Styles.mb15, { backgroundColor: colors.card, borderColor: colors.background }]}>
<View style={[Styles.rowItemsCenter, { alignItems: 'flex-start' }]}>
<MaterialCommunityIcons name="calendar-text" size={30} color={colors.text} style={Styles.mr10} />
{
loading ?
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" />
: <Text style={[Styles.textDefault, Styles.w90]}>{data?.title}</Text>
}
{/* Info acara */}
<View style={[Styles.wrapPaper, Styles.sectionCard, Styles.noShadow, Styles.mb15,
{ backgroundColor: colors.card, borderColor: colors.icon + '18', padding: 0, overflow: 'hidden' }]}>
<View style={{ padding: 16, borderBottomWidth: 1, borderBottomColor: colors.icon + '14' }}>
<View style={Styles.sectionActionRow}>
<View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
<MaterialCommunityIcons name="calendar-text" size={18} color={colors.dimmed} />
</View>
<Text style={[Styles.textDefaultSemiBold, Styles.flex1, { color: colors.text }]}>Detail Acara</Text>
</View>
</View> </View>
<View style={[Styles.rowItemsCenter, Styles.mt10]}> <View style={{ paddingHorizontal: 16 }}>
<MaterialCommunityIcons name="calendar-month-outline" size={30} color={colors.text} style={Styles.mr10} /> <InfoRow icon="format-title" label="Judul" value={data?.title} />
{ <InfoRow icon="calendar-month-outline" label="Tanggal" value={data?.dateStart} />
loading ? <InfoRow icon="clock-outline" label="Waktu" value={data ? `${data.timeStart} ${data.timeEnd}` : undefined} />
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" /> <InfoRow icon="repeat" label="Pengulangan" value={data?.repeatEventTyper ? repeatLabel[data.repeatEventTyper] : undefined} />
: <InfoRow icon="link-variant" label="Link Meet" value={data?.linkMeet} onCopy={data?.linkMeet ? () => handleCopy(data.linkMeet) : undefined} />
<Text style={[Styles.textDefault]}>{data?.dateStart}</Text> <View style={[Styles.sectionActionRow, { paddingVertical: 10, alignItems: 'flex-start' }]}>
} <View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
<MaterialCommunityIcons name="card-text-outline" size={18} color={colors.dimmed} />
</View>
<View style={Styles.flex1}>
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed, marginBottom: 2 }]}>Deskripsi</Text>
{loading
? <View style={{ height: 13, borderRadius: 6, backgroundColor: colors.icon + '20', width: '80%' }} />
: <Text style={[Styles.textDefault, { color: colors.text, lineHeight: 22 }]}>{data?.desc || '-'}</Text>
}
</View>
</View>
</View> </View>
<View style={[Styles.rowItemsCenter, Styles.mt10]}> </View>
<MaterialCommunityIcons name="clock-outline" size={30} color={colors.text} style={Styles.mr10} />
{ {/* Daftar anggota */}
loading ? <View style={[Styles.wrapPaper, Styles.sectionCard, Styles.noShadow,
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" /> { backgroundColor: colors.card, borderColor: colors.icon + '18', padding: 0, overflow: 'hidden' }]}>
:
<Text style={[Styles.textDefault]}>{data?.timeStart} | {data?.timeEnd}</Text> <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>
<Text style={[Styles.textMediumNormal, { color: colors.dimmed }]}>{member.length} anggota</Text>
</View> </View>
<View style={[Styles.rowItemsCenter, Styles.mt10]}>
<MaterialCommunityIcons name="repeat" size={30} color={colors.text} style={Styles.mr10} /> {member.length === 0
{ ? (
loading ? <View style={[Styles.contentItemCenter, { paddingVertical: 40 }]}>
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" /> <MaterialIcons name="people-outline" size={34} color={colors.icon + '50'} />
: <Text style={[Styles.textMediumNormal, Styles.mt10, { color: colors.dimmed }]}>Belum ada anggota</Text>
<Text style={[Styles.textDefault]}> </View>
)
: member.map((item, index) => (
<Pressable
key={index}
onPress={() => {
if (!canManage) return
setMemberChoose({ id: item.idUser, name: item.name })
setModalMember(true)
}}
style={({ pressed }) => [
Styles.rowItemsCenter, Styles.ph15,
{ {
data?.repeatEventTyper.toString() === 'once' ? 'Acara 1 Kali' : paddingVertical: 12, gap: 14,
data?.repeatEventTyper.toString() === 'daily' ? 'Setiap Hari' : borderBottomWidth: index < member.length - 1 ? 1 : 0,
data?.repeatEventTyper.toString() === 'weekly' ? 'Mingguan' : borderBottomColor: colors.icon + '14',
data?.repeatEventTyper.toString() === 'monthly' ? 'Bulanan' : backgroundColor: pressed && canManage ? colors.icon + '0E' : 'transparent',
data?.repeatEventTyper.toString() === 'yearly' ? 'Tahunan' : },
'' ]}
} >
</Text> <ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
} <View style={Styles.flex1}>
</View> <Text style={[Styles.textDefault, { color: colors.text }]} numberOfLines={1}>{item.name}</Text>
<View style={[Styles.rowItemsCenter, Styles.mt10]}> <Text style={[Styles.textMediumNormal, { color: colors.dimmed }]} numberOfLines={1}>{item.email}</Text>
<MaterialCommunityIcons name="link-variant" size={30} color={colors.text} style={Styles.mr10} /> </View>
{ {canManage && <MaterialCommunityIcons name="chevron-right" size={18} color={colors.icon + '60'} />}
loading ? </Pressable>
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" /> ))
: }
data?.linkMeet ?
<Pressable onPress={() => { handleCopy(data.linkMeet) }}>
<Text style={[Styles.textDefault]}>{data.linkMeet}</Text>
</Pressable>
: <Text style={[Styles.textDefault]}>-</Text>
}
</View>
<View style={[Styles.rowItemsCenter, Styles.mt10, { alignItems: 'flex-start' }]}>
<MaterialCommunityIcons name="card-text-outline" size={30} color={colors.text} style={Styles.mr10} />
{
loading ?
<Skeleton width={80} height={10} borderRadius={10} widthType="percent" />
:
<Text style={[Styles.textDefault, Styles.w90]}>{data?.desc}</Text>
}
</View>
</View> </View>
<View style={[Styles.mb15]}>
<View style={[Styles.rowSpaceBetween, Styles.mv05]}>
<Text style={[Styles.textDefaultSemiBold]}>Anggota</Text>
<Text style={[Styles.textDefault]}>Total {member.length} Anggota</Text>
</View>
<View style={[Styles.wrapPaper, { backgroundColor: colors.card, borderColor: colors.background }]}>
{
member.map((item, index) => (
<BorderBottomItem
key={index}
borderType="bottom"
icon={<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} />}
title={item.name}
subtitle={item.email}
onPress={() => {
if ((entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision) {
null
} else {
setMemberChoose({ id: item.idUser, name: item.name })
setModalMember(true)
}
}}
/>
))
}
</View>
</View>
</View> </View>
</ScrollView> </ScrollView>

View File

@@ -169,7 +169,7 @@ export default function AddMemberDivision() {
return ( return (
<Pressable <Pressable
key={index} key={index}
style={[Styles.itemSelectModal]} style={[Styles.itemSelectModal, { borderBottomColor: colors.icon + '20' }]}
onPress={() => { onPress={() => {
!found && onChoose(item.id, item.name, item.img) !found && onChoose(item.id, item.name, item.img)
}} }}

View File

@@ -1,20 +1,17 @@
import ModalConfirmation from "@/components/ModalConfirmation"
import AppHeader from "@/components/AppHeader" import AppHeader from "@/components/AppHeader"
import BorderBottomItem from "@/components/borderBottomItem"
import HeaderRightDivisionInfo from "@/components/division/headerDivisionInfo"
import DrawerBottom from "@/components/drawerBottom" import DrawerBottom from "@/components/drawerBottom"
import HeaderRightDivisionInfo from "@/components/division/headerDivisionInfo"
import ImageUser from "@/components/imageNew" import ImageUser from "@/components/imageNew"
import MenuItemRow from "@/components/menuItemRow"
import ModalConfirmation from "@/components/ModalConfirmation"
import SectionCancel from "@/components/sectionCancel" import SectionCancel from "@/components/sectionCancel"
import Skeleton from "@/components/skeleton"
import SkeletonTwoItem from "@/components/skeletonTwoItem"
import Text from "@/components/Text" import Text from "@/components/Text"
import { ColorsStatus } from "@/constants/ColorsStatus"
import { ConstEnv } from "@/constants/ConstEnv" import { ConstEnv } from "@/constants/ConstEnv"
import Styles from "@/constants/Styles" import Styles from "@/constants/Styles"
import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiGetDivisionOneFeature, apiUpdateStatusAdminDivision } from "@/lib/api" import { apiDeleteMemberDivision, apiGetDivisionOneDetail, apiGetDivisionOneFeature, apiUpdateStatusAdminDivision } from "@/lib/api"
import { useAuthSession } from "@/providers/AuthProvider" import { useAuthSession } from "@/providers/AuthProvider"
import { useTheme } from "@/providers/ThemeProvider" import { useTheme } from "@/providers/ThemeProvider"
import { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons" import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons"
import { router, Stack, useLocalSearchParams } from "expo-router" import { router, Stack, useLocalSearchParams } from "expo-router"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native" import { Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"
@@ -50,8 +47,8 @@ export default function InformationDivision() {
const [dataMember, setDataMember] = useState<PropsMember[]>([]) const [dataMember, setDataMember] = useState<PropsMember[]>([])
const [refresh, setRefresh] = useState(false) const [refresh, setRefresh] = useState(false)
const update = useSelector((state: any) => state.divisionUpdate) const update = useSelector((state: any) => state.divisionUpdate)
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const SKELETON_COUNT = 5
const [isMemberDivision, setIsMemberDivision] = useState(false) const [isMemberDivision, setIsMemberDivision] = useState(false)
const [isAdminDivision, setIsAdminDivision] = useState(false) const [isAdminDivision, setIsAdminDivision] = useState(false)
const [dataMemberChoose, setDataMemberChoose] = useState({ const [dataMemberChoose, setDataMemberChoose] = useState({
@@ -186,109 +183,123 @@ export default function InformationDivision() {
}} }}
/> />
<ScrollView <ScrollView
refreshControl={ showsVerticalScrollIndicator={false}
<RefreshControl refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} tintColor={colors.icon} />}
refreshing={refreshing}
onRefresh={handleRefresh}
tintColor={colors.icon}
/>
}
style={[Styles.h100, { backgroundColor: colors.background }]} style={[Styles.h100, { backgroundColor: colors.background }]}
> >
<View style={[Styles.p15]}> <View style={[Styles.p15, Styles.mb100]}>
{
dataDetail?.isActive == false && ( {dataDetail?.isActive === false && <SectionCancel title="Divisi nonaktif" />}
<SectionCancel title={'Divisi nonaktif'} />
) {/* Deskripsi */}
} <View style={[Styles.wrapPaper, Styles.sectionCard, Styles.noShadow, Styles.mb15,
<View style={[Styles.mb15]}> { backgroundColor: colors.card, borderColor: colors.icon + '18' }]}>
<Text style={[Styles.textDefaultSemiBold, Styles.mb05]}>Deskripsi Divisi</Text> <View style={[Styles.sectionActionRow, { marginBottom: 12 }]}>
<View style={[Styles.wrapPaper, Styles.noShadow, { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.icon + '20' }]}> <View style={[Styles.sectionIconBox, { backgroundColor: colors.dimmed + '18' }]}>
{loading ? <MaterialIcons name="info-outline" size={18} color={colors.dimmed} />
arrSkeleton.map((item, index) => { </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 ( return (
<Skeleton key={index} width={100} height={10} widthType="percent" borderRadius={10} /> <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>
) )
}) })
: }
<Text>{dataDetail?.desc}</Text>
}
</View>
</View> </View>
<View style={[Styles.mb15]}>
<Text style={[Styles.textDefault, Styles.mv05]}>{dataMember.length} Anggota</Text>
<View style={[Styles.wrapPaper, Styles.noShadow, { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.icon + '20' }]}>
{
((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) &&
dataDetail?.isActive && (
<BorderBottomItem
onPress={() => { router.push(`/division/${id}/add-member`) }}
borderType="none"
icon={
<View style={[Styles.iconContent]}>
<Feather name="user-plus" size={25} color={'black'} />
</View>
}
title="Tambah Anggota"
/>
)
}
{
loading ?
arrSkeleton.map((item, index) => {
return (
<SkeletonTwoItem key={index} />
)
})
:
dataMember.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="bottom"
onPress={() => { dataDetail?.isActive && (isAdminDivision || (entityUser.role != "user" && entityUser.role != "coadmin")) && handleChooseMember(item) }}
icon={
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="sm" />
}
title={item.name}
rightTopInfo={item.isAdmin ? "Admin" : "Anggota"}
/>
)
})
}
</View>
</View>
</View> </View>
</ScrollView> </ScrollView>
<DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title={dataMemberChoose.name}> <DrawerBottom animation="slide" isVisible={isModal} setVisible={setModal} title={dataMemberChoose.name}>
<View> <View style={Styles.rowItemsCenter}>
<Pressable style={[Styles.wrapItemBorderBottom]} onPress={() => { handleMemberAdmin() }}> <MenuItemRow
<View style={[Styles.rowItemsCenter]}> icon={<MaterialIcons name="verified-user" color={colors.text} size={25} />}
<View style={[Styles.iconContent]}> title={dataMemberChoose.isAdmin ? 'Berhentikan admin' : 'Jadikan admin'}
<MaterialIcons name="verified-user" size={25} color={'black'} /> onPress={handleMemberAdmin}
</View> />
<View style={[Styles.rowSpaceBetween, { width: '88%' }]}> <MenuItemRow
<View style={[Styles.ml10]}> icon={<MaterialCommunityIcons name="account-remove" color={colors.text} size={25} />}
<Text style={[Styles.textDefault]}>{dataMemberChoose.isAdmin ? 'Memberhentikan sebagai admin' : 'Jadikan admin'}</Text> title="Keluarkan"
</View> onPress={handleMemberOut}
</View> />
</View>
</Pressable>
<Pressable style={[Styles.wrapItemBorderBottom]} onPress={() => { handleMemberOut() }}>
<View style={[Styles.rowItemsCenter]}>
<View style={[Styles.iconContent, ColorsStatus.info]}>
<MaterialCommunityIcons name="close-circle" size={25} color={colors.primary} />
</View>
<View style={[Styles.rowSpaceBetween, { width: '88%' }]}>
<View style={[Styles.ml10]}>
<Text style={[Styles.textDefault]}>Keluarkan dari divisi</Text>
</View>
</View>
</View>
</Pressable>
</View> </View>
</DrawerBottom> </DrawerBottom>