diff --git a/app/(application)/discussion/member/[id].tsx b/app/(application)/discussion/member/[id].tsx index 7dae308..5e0b107 100644 --- a/app/(application)/discussion/member/[id].tsx +++ b/app/(application)/discussion/member/[id].tsx @@ -1,21 +1,18 @@ import AppHeader from "@/components/AppHeader"; -import BorderBottomItem from "@/components/borderBottomItem"; import DrawerBottom from "@/components/drawerBottom"; import ImageUser from "@/components/imageNew"; import MenuItemRow from "@/components/menuItemRow"; import ModalConfirmation from "@/components/ModalConfirmation"; -import SkeletonTwoItem from "@/components/skeletonTwoItem"; import Text from '@/components/Text'; -import { ColorsStatus } from "@/constants/ColorsStatus"; import { ConstEnv } from "@/constants/ConstEnv"; import Styles from "@/constants/Styles"; import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api"; import { useAuthSession } from "@/providers/AuthProvider"; 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 { 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 { useSelector } from "react-redux"; @@ -25,6 +22,8 @@ type Props = { img: string } +const SKELETON_COUNT = 5 + export default function MemberDiscussionDetail() { const { token, decryptToken } = useAuthSession() const { colors } = useTheme(); @@ -35,13 +34,12 @@ export default function MemberDiscussionDetail() { const [chooseUser, setChooseUser] = useState({ idUser: '', name: '', img: '' }) const update = useSelector((state: any) => state.discussionGeneralDetailUpdate) const [loading, setLoading] = useState(true) - const arrSkeleton = Array.from({ length: 5 }, (_, index) => index) const [showDeleteModal, setShowDeleteModal] = useState(false) + const canManage = entityUser.role !== "user" && entityUser.role !== "coadmin" - - async function handleLoad(loading: boolean) { + async function handleLoad(showLoadingIndicator: boolean) { try { - setLoading(loading) + setLoading(showLoadingIndicator) const hasil = await decryptToken(String(token?.current)) const response = await apiGetDiscussionGeneralOne({ id: id, user: hasil, cat: 'anggota' }) setData(response.data) @@ -52,26 +50,18 @@ export default function MemberDiscussionDetail() { } } - useEffect(() => { - handleLoad(false) - }, [update]); - - - useEffect(() => { - handleLoad(true) - }, []); + useEffect(() => { handleLoad(false) }, [update]); + useEffect(() => { handleLoad(true) }, []); async function handleDeleteUser() { try { const hasil = await decryptToken(String(token?.current)) 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) } catch (error: any) { console.error(error); - const message = error?.response?.data?.message || "Gagal mengeluarkan anggota" - - Toast.show({ type: 'small', text1: message }) + Toast.show({ type: 'small', text1: error?.response?.data?.message || "Gagal mengeluarkan anggota" }) } finally { setModal(false) } @@ -81,9 +71,6 @@ export default function MemberDiscussionDetail() { { router.back() }} />, - headerTitle: 'Anggota Diskusi', - headerTitleAlign: 'center', header: () => ( - - - {data.length} Anggota - - { - entityUser.role != "user" && entityUser.role != "coadmin" && - { router.push(`/discussion/add-member/${id}`) }} - borderType="none" - icon={ - - + + + + {/* Tombol tambah anggota */} + {canManage && ( + + router.push(`/discussion/add-member/${id}`)} + style={Styles.sectionActionRow} + > + + + + + Tambah Anggota + + + + + )} + + {/* Full list */} + + + + + + Anggota + {!loading && ( + {data.length} anggota + )} + + {loading + ? Array.from({ length: SKELETON_COUNT }).map((_, i) => ( + + + + + )) + : data.length === 0 + ? ( + + + Belum ada anggota - } - title="Tambah Anggota" - /> - } - { - loading ? - arrSkeleton.map((item, index) => { - return ( - - ) - }) - : - data.map((item, index) => { - return ( - - } - title={item.name} - onPress={() => { - setChooseUser(item) - setModal(true) - }} - /> - ) - }) + ) + : data.map((item, index) => ( + { setChooseUser(item); setModal(true) }} + style={({ pressed }) => [ + Styles.rowItemsCenter, Styles.ph15, + { + paddingVertical: 13, gap: 14, + borderBottomWidth: index < data.length - 1 ? 1 : 0, + borderBottomColor: colors.icon + '14', + backgroundColor: pressed ? colors.icon + '0E' : 'transparent', + }, + ]} + > + + + {item.name} + + + + )) } + @@ -149,20 +169,16 @@ export default function MemberDiscussionDetail() { router.push(`/member/${chooseUser.idUser}`) }} /> - { - entityUser.role != "user" && entityUser.role != "coadmin" && + {canManage && ( } title="Keluarkan" onPress={() => { setModal(false) - setTimeout(() => { - setShowDeleteModal(true) - }, 600) + setTimeout(() => setShowDeleteModal(true), 600) }} /> - } - + )} @@ -170,10 +186,7 @@ export default function MemberDiscussionDetail() { visible={showDeleteModal} title="Konfirmasi" message="Apakah anda yakin ingin mengeluarkan anggota?" - onConfirm={() => { - setShowDeleteModal(false) - handleDeleteUser() - }} + onConfirm={() => { setShowDeleteModal(false); handleDeleteUser() }} onCancel={() => setShowDeleteModal(false)} confirmText="Hapus" cancelText="Batal" diff --git a/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx b/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx index 9574196..b9e9951 100644 --- a/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx +++ b/app/(application)/division/[id]/(fitur-division)/calendar/[detail]/index.tsx @@ -1,12 +1,9 @@ -import ModalConfirmation from "@/components/ModalConfirmation" 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 HeaderRightCalendarDetail from "@/components/calendar/headerCalendarDetail" import ImageUser from "@/components/imageNew" import MenuItemRow from "@/components/menuItemRow" -import Skeleton from "@/components/skeleton" +import ModalConfirmation from "@/components/ModalConfirmation" import Text from "@/components/Text" import { ConstEnv } from "@/constants/ConstEnv" import Styles from "@/constants/Styles" @@ -14,7 +11,7 @@ import { apiDeleteCalendarMember, apiGetCalendarOne, apiGetDivisionOneFeature } import { setUpdateCalendar } from "@/lib/calendarUpdate" import { useAuthSession } from "@/providers/AuthProvider" 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 { router, Stack, useLocalSearchParams } from "expo-router" import { useEffect, useState } from "react" @@ -156,135 +153,142 @@ export default function DetailEventCalendar() { setRefreshing(false) }; + const canManage = !((entityUser.role === "user" || entityUser.role === "coadmin") && !isMemberDivision) + + const repeatLabel: Record = { + 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 ( + + + + + + {label} + {loading + ? + : {value || '-'} + } + + {onCopy && !loading && value && ( + + + + )} + + ) + } + return ( - + { router.back() }} />, - headerTitle: 'Detail Acara', - headerTitleAlign: 'center', - // headerRight: () => (entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision ? <> : header: () => ( router.back()} right={ - (entityUser.role == "user" || entityUser.role == "coadmin") && !isMemberDivision ? <> : + (entityUser.role === "user" || entityUser.role === "coadmin") && !isMemberDivision + ? <> : } /> ) }} /> - } + showsVerticalScrollIndicator={false} + style={Styles.h100} + refreshControl={} > - - - - - { - loading ? - - : {data?.title} - } + + {/* Info acara */} + + + + + + + Detail Acara + - - - { - loading ? - - : - {data?.dateStart} - } + + + + + + handleCopy(data.linkMeet) : undefined} /> + + + + + + Deskripsi + {loading + ? + : {data?.desc || '-'} + } + + - - - { - loading ? - - : - {data?.timeStart} | {data?.timeEnd} - } + + + {/* Daftar anggota */} + + + + + + + Anggota + {member.length} anggota - - - { - loading ? - - : - + + {member.length === 0 + ? ( + + + Belum ada anggota + + ) + : member.map((item, index) => ( + { + 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' : - data?.repeatEventTyper.toString() === 'daily' ? 'Setiap Hari' : - data?.repeatEventTyper.toString() === 'weekly' ? 'Mingguan' : - data?.repeatEventTyper.toString() === 'monthly' ? 'Bulanan' : - data?.repeatEventTyper.toString() === 'yearly' ? 'Tahunan' : - '' - } - - } - - - - { - loading ? - - : - data?.linkMeet ? - { handleCopy(data.linkMeet) }}> - {data.linkMeet} - - : - - } - - - - { - loading ? - - : - {data?.desc} - } - + paddingVertical: 12, gap: 14, + borderBottomWidth: index < member.length - 1 ? 1 : 0, + borderBottomColor: colors.icon + '14', + backgroundColor: pressed && canManage ? colors.icon + '0E' : 'transparent', + }, + ]} + > + + + {item.name} + {item.email} + + {canManage && } + + )) + } - - - Anggota - Total {member.length} Anggota - - - - { - member.map((item, index) => ( - } - 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) - } - }} - /> - )) - } - - diff --git a/app/(application)/division/[id]/add-member.tsx b/app/(application)/division/[id]/add-member.tsx index 7c959d5..1b172c4 100644 --- a/app/(application)/division/[id]/add-member.tsx +++ b/app/(application)/division/[id]/add-member.tsx @@ -169,7 +169,7 @@ export default function AddMemberDivision() { return ( { !found && onChoose(item.id, item.name, item.img) }} diff --git a/app/(application)/division/[id]/info.tsx b/app/(application)/division/[id]/info.tsx index b8907be..a967513 100644 --- a/app/(application)/division/[id]/info.tsx +++ b/app/(application)/division/[id]/info.tsx @@ -1,20 +1,17 @@ -import ModalConfirmation from "@/components/ModalConfirmation" import AppHeader from "@/components/AppHeader" -import BorderBottomItem from "@/components/borderBottomItem" -import HeaderRightDivisionInfo from "@/components/division/headerDivisionInfo" 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 Skeleton from "@/components/skeleton" -import SkeletonTwoItem from "@/components/skeletonTwoItem" import Text from "@/components/Text" -import { ColorsStatus } from "@/constants/ColorsStatus" 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 { Feather, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons" +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" @@ -50,8 +47,8 @@ export default function InformationDivision() { const [dataMember, setDataMember] = useState([]) const [refresh, setRefresh] = useState(false) const update = useSelector((state: any) => state.divisionUpdate) - const arrSkeleton = Array.from({ length: 5 }, (_, index) => index) const [loading, setLoading] = useState(true) + const SKELETON_COUNT = 5 const [isMemberDivision, setIsMemberDivision] = useState(false) const [isAdminDivision, setIsAdminDivision] = useState(false) const [dataMemberChoose, setDataMemberChoose] = useState({ @@ -186,109 +183,123 @@ export default function InformationDivision() { }} /> - } + showsVerticalScrollIndicator={false} + refreshControl={} style={[Styles.h100, { backgroundColor: colors.background }]} > - - { - dataDetail?.isActive == false && ( - - ) - } - - Deskripsi Divisi - - {loading ? - arrSkeleton.map((item, index) => { + + + {dataDetail?.isActive === false && } + + {/* Deskripsi */} + + + + + + Deskripsi + + {loading + ? Array.from({ length: 3 }).map((_, i) => ( + + )) + : {dataDetail?.desc} + } + + + {/* Tombol tambah anggota */} + {((entityUser.role !== "user" && entityUser.role !== "coadmin") || isAdminDivision) && dataDetail?.isActive && ( + + router.push(`/division/${id}/add-member`)} + style={Styles.sectionActionRow} + > + + + + Tambah Anggota + + + + )} + + {/* Daftar anggota */} + + + {/* Header */} + + + + + Anggota + {!loading && ( + {dataMember.length} anggota + )} + + + {loading + ? Array.from({ length: SKELETON_COUNT }).map((_, i) => ( + + + + + )) + : dataMember.length === 0 + ? ( + + + Belum ada anggota + + ) + : dataMember.map((item, index) => { + const canPress = dataDetail?.isActive && (isAdminDivision || (entityUser.role !== "user" && entityUser.role !== "coadmin")) return ( - + 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', + }, + ]} + > + + + {item.name} + + + {item.isAdmin ? 'Admin' : 'Anggota'} + + {canPress && } + ) }) - : - {dataDetail?.desc} - } - + } - - {dataMember.length} Anggota - - { - ((entityUser.role != "user" && entityUser.role != "coadmin") || isAdminDivision) && - dataDetail?.isActive && ( - { router.push(`/division/${id}/add-member`) }} - borderType="none" - icon={ - - - - } - title="Tambah Anggota" - /> - ) - } - { - loading ? - arrSkeleton.map((item, index) => { - return ( - - ) - }) - : - dataMember.map((item, index) => { - return ( - { dataDetail?.isActive && (isAdminDivision || (entityUser.role != "user" && entityUser.role != "coadmin")) && handleChooseMember(item) }} - icon={ - - } - title={item.name} - rightTopInfo={item.isAdmin ? "Admin" : "Anggota"} - /> - ) - }) - } - - - - { handleMemberAdmin() }}> - - - - - - - {dataMemberChoose.isAdmin ? 'Memberhentikan sebagai admin' : 'Jadikan admin'} - - - - - - { handleMemberOut() }}> - - - - - - - Keluarkan dari divisi - - - - + + } + title={dataMemberChoose.isAdmin ? 'Berhentikan admin' : 'Jadikan admin'} + onPress={handleMemberAdmin} + /> + } + title="Keluarkan" + onPress={handleMemberOut} + />