upd: skeleton

Deskripsi:
- diskusi umum
- detail diskusi umum
- list pengumuman
- detail pengumuman
- list kegiatan
- detail kegiatan

No Issues
This commit is contained in:
amel
2025-06-03 17:43:45 +08:00
parent 36294ec4eb
commit 1e6a6d08ae
20 changed files with 541 additions and 293 deletions

View File

@@ -1,9 +1,10 @@
import HeaderRightAnnouncementDetail from "@/components/announcement/headerAnnouncementDetail"; import HeaderRightAnnouncementDetail from "@/components/announcement/headerAnnouncementDetail";
import ButtonBackHeader from "@/components/buttonBackHeader"; import ButtonBackHeader from "@/components/buttonBackHeader";
import Skeleton from "@/components/skeleton";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetAnnouncementOne } from "@/lib/api"; import { apiGetAnnouncementOne } from "@/lib/api";
import { useAuthSession } from "@/providers/AuthProvider"; import { useAuthSession } from "@/providers/AuthProvider";
import { AntDesign, Entypo, MaterialIcons } from "@expo/vector-icons"; import { Entypo, 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 { Dimensions, SafeAreaView, ScrollView, Text, View } from "react-native"; import { Dimensions, SafeAreaView, ScrollView, Text, View } from "react-native";
@@ -24,22 +25,31 @@ export default function DetailAnnouncement() {
const update = useSelector((state: any) => state.announcementUpdate) const update = useSelector((state: any) => state.announcementUpdate)
const entityUser = useSelector((state: any) => state.user) const entityUser = useSelector((state: any) => state.user)
const contentWidth = Dimensions.get('window').width const contentWidth = Dimensions.get('window').width
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 2 }, (_, index) => index)
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
const response = await apiGetAnnouncementOne({ id: id, user: hasil }) const response = await apiGetAnnouncementOne({ id: id, user: hasil })
setData(response.data) setData(response.data)
setDataMember(response.member) setDataMember(response.member)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally {
setLoading(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad() handleLoad(false)
}, [update]) }, [update])
useEffect(() => {
handleLoad(true)
}, [])
return ( return (
<SafeAreaView> <SafeAreaView>
<Stack.Screen <Stack.Screen
@@ -53,6 +63,21 @@ export default function DetailAnnouncement() {
<ScrollView> <ScrollView>
<View style={[Styles.p15]}> <View style={[Styles.p15]}>
<View style={[Styles.wrapPaper]}> <View style={[Styles.wrapPaper]}>
{
loading ?
<View>
<View style={[Styles.rowOnly]}>
<Skeleton width={30} height={30} borderRadius={10} />
<View style={[{ flex: 1 }, Styles.ph05]}>
<Skeleton width={100} widthType="percent" height={30} borderRadius={10} />
</View>
</View>
<Skeleton width={100} widthType="percent" height={10} borderRadius={10} />
<Skeleton width={100} widthType="percent" height={10} borderRadius={10} />
<Skeleton width={100} widthType="percent" height={10} borderRadius={10} />
</View>
:
<>
<View style={Styles.rowItemsCenter}> <View style={Styles.rowItemsCenter}>
<MaterialIcons name="campaign" size={30} color="black" style={Styles.mr05} /> <MaterialIcons name="campaign" size={30} color="black" style={Styles.mr05} />
<Text style={[Styles.textDefaultSemiBold]}>{data?.title}</Text> <Text style={[Styles.textDefaultSemiBold]}>{data?.title}</Text>
@@ -63,9 +88,23 @@ export default function DetailAnnouncement() {
source={{ html: data?.desc }} source={{ html: data?.desc }}
/> />
</View> </View>
</>
}
</View> </View>
<View style={[Styles.wrapPaper, Styles.mv15]}> <View style={[Styles.wrapPaper, Styles.mv15]}>
{ {
loading ?
arrSkeleton.map((item, index) => {
return (
<View key={index}>
<Skeleton width={30} widthType="percent" height={10} borderRadius={10} />
<Skeleton width={100} widthType="percent" height={10} borderRadius={10} />
<Skeleton width={100} widthType="percent" height={10} borderRadius={10} />
</View>
)
})
:
Object.keys(dataMember).map((v: any, i: any) => { Object.keys(dataMember).map((v: any, i: any) => {
return ( return (
<View key={i} style={[Styles.mb05]}> <View key={i} style={[Styles.mb05]}>
@@ -73,7 +112,7 @@ export default function DetailAnnouncement() {
{ {
dataMember[v].map((item: any, x: any) => { dataMember[v].map((item: any, x: any) => {
return ( return (
<View key={x} style={[Styles.rowItemsCenter]}> <View key={x} style={[Styles.rowItemsCenter, Styles.w90]}>
<Entypo name="dot-single" size={24} color="black" /> <Entypo name="dot-single" size={24} color="black" />
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{item.division}</Text> <Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{item.division}</Text>
</View> </View>

View File

@@ -2,6 +2,7 @@ import HeaderRightAnnouncementList from "@/components/announcement/headerAnnounc
import BorderBottomItem from "@/components/borderBottomItem"; import BorderBottomItem from "@/components/borderBottomItem";
import ButtonBackHeader from "@/components/buttonBackHeader"; import ButtonBackHeader from "@/components/buttonBackHeader";
import InputSearch from "@/components/inputSearch"; import InputSearch from "@/components/inputSearch";
import SkeletonContent from "@/components/skeletonContent";
import { ColorsStatus } from "@/constants/ColorsStatus"; import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetAnnouncement } from "@/lib/api"; import { apiGetAnnouncement } from "@/lib/api";
@@ -26,20 +27,29 @@ export default function Announcement() {
const [search, setSearch] = useState('') const [search, setSearch] = useState('')
const entityUser = useSelector((state: any) => state.user) const entityUser = useSelector((state: any) => state.user)
const update = useSelector((state: any) => state.announcementUpdate) const update = useSelector((state: any) => state.announcementUpdate)
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
const response = await apiGetAnnouncement({ user: hasil, search: search }) const response = await apiGetAnnouncement({ user: hasil, search: search })
setData(response.data) setData(response.data)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally {
setLoading(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad() handleLoad(false)
}, [search, update]) }, [update])
useEffect(() => {
handleLoad(true)
}, [search])
return ( return (
<SafeAreaView> <SafeAreaView>
@@ -56,6 +66,13 @@ export default function Announcement() {
<View style={[Styles.p15, Styles.mb100]}> <View style={[Styles.p15, Styles.mb100]}>
<InputSearch onChange={setSearch} /> <InputSearch onChange={setSearch} />
{ {
loading ?
arrSkeleton.map((item, index) => {
return (
<SkeletonContent key={index} />
)
})
:
data.length > 0 data.length > 0
? ?
data.map((item, index) => { data.map((item, index) => {

View File

@@ -4,6 +4,8 @@ import HeaderRightDiscussionGeneralDetail from "@/components/discussion_general/
import ImageUser from "@/components/imageNew"; import ImageUser from "@/components/imageNew";
import { InputForm } from "@/components/inputForm"; import { InputForm } from "@/components/inputForm";
import LabelStatus from "@/components/labelStatus"; import LabelStatus from "@/components/labelStatus";
import Skeleton from "@/components/skeleton";
import SkeletonContent from "@/components/skeletonContent";
import { ColorsStatus } from "@/constants/ColorsStatus"; import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api"; import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api";
@@ -41,12 +43,22 @@ export default function DetailDiscussionGeneral() {
const [memberDiscussion, setMemberDiscussion] = useState(false) const [memberDiscussion, setMemberDiscussion] = useState(false)
const [komentar, setKomentar] = useState('') const [komentar, setKomentar] = useState('')
const update = useSelector((state: any) => state.discussionGeneralDetailUpdate) const update = useSelector((state: any) => state.discussionGeneralDetailUpdate)
const [loading, setLoading] = useState(true)
const [loadingKomentar, setLoadingKomentar] = useState(true)
const arrSkeleton = Array.from({ length: 3 }, (_, index) => index)
async function handleLoad(cat: 'detail' | 'komentar' | 'cek-anggota') { async function handleLoad(cat: 'detail' | 'komentar' | 'cek-anggota', loading: boolean) {
try { try {
if (cat == "detail") {
setLoading(loading)
} else if (cat == "komentar") {
setLoadingKomentar(loading)
}
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
const response = await apiGetDiscussionGeneralOne({ id: id, user: hasil, cat }) const response = await apiGetDiscussionGeneralOne({ id: id, user: hasil, cat })
if (cat == 'detail') { if (cat == 'detail') {
setData(response.data) setData(response.data)
} else if (cat == 'komentar') { } else if (cat == 'komentar') {
@@ -54,17 +66,27 @@ export default function DetailDiscussionGeneral() {
} else if (cat == 'cek-anggota') { } else if (cat == 'cek-anggota') {
setMemberDiscussion(response.data) setMemberDiscussion(response.data)
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally {
setLoading(false)
setLoadingKomentar(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad('detail') handleLoad('detail', false)
handleLoad('komentar') handleLoad('komentar', false)
handleLoad('cek-anggota') handleLoad('cek-anggota', false)
}, [update]); }, [update]);
useEffect(() => {
handleLoad('detail', true)
handleLoad('komentar', true)
handleLoad('cek-anggota', true)
}, []);
async function handleKomentar() { async function handleKomentar() {
try { try {
if (komentar != '') { if (komentar != '') {
@@ -72,7 +94,7 @@ export default function DetailDiscussionGeneral() {
const response = await apiSendDiscussionGeneralCommentar({ id: id, data: { desc: komentar, user: hasil } }) const response = await apiSendDiscussionGeneralCommentar({ id: id, data: { desc: komentar, user: hasil } })
if (response.success) { if (response.success) {
setKomentar('') setKomentar('')
handleLoad('komentar') handleLoad('komentar', false)
} }
} }
} catch (error) { } catch (error) {
@@ -93,6 +115,10 @@ export default function DetailDiscussionGeneral() {
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<ScrollView> <ScrollView>
<View style={[Styles.p15, Styles.mb100]}> <View style={[Styles.p15, Styles.mb100]}>
{
loading ?
<SkeletonContent />
:
<BorderBottomItem <BorderBottomItem
borderType="bottom" borderType="bottom"
icon={ icon={
@@ -116,8 +142,16 @@ export default function DetailDiscussionGeneral() {
</View> </View>
} }
/> />
}
<View style={[Styles.p15]}> <View style={[Styles.p15]}>
{ {
loadingKomentar ?
arrSkeleton.map((item: any, i: number) => {
return (
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
)
})
:
dataKomentar.map((item, i) => { dataKomentar.map((item, i) => {
return ( return (
<BorderBottomItem <BorderBottomItem

View File

@@ -2,6 +2,7 @@ import BorderBottomItem from "@/components/borderBottomItem";
import ButtonTab from "@/components/buttonTab"; import ButtonTab from "@/components/buttonTab";
import InputSearch from "@/components/inputSearch"; import InputSearch from "@/components/inputSearch";
import LabelStatus from "@/components/labelStatus"; import LabelStatus from "@/components/labelStatus";
import SkeletonContent from "@/components/skeletonContent";
import { ColorsStatus } from "@/constants/ColorsStatus"; import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneral } from "@/lib/api"; import { apiGetDiscussionGeneral } from "@/lib/api";
@@ -30,21 +31,30 @@ export default function Discussion() {
const [nameGroup, setNameGroup] = useState('') const [nameGroup, setNameGroup] = useState('')
const [data, setData] = useState<Props[]>([]) const [data, setData] = useState<Props[]>([])
const update = useSelector((state: any) => state.discussionGeneralDetailUpdate) const update = useSelector((state: any) => state.discussionGeneralDetailUpdate)
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
const response = await apiGetDiscussionGeneral({ user: hasil, active: String(active), search: search, group: String(group) }) const response = await apiGetDiscussionGeneral({ user: hasil, active: String(active), search: search, group: String(group) })
setData(response.data) setData(response.data)
setNameGroup(response.filter.name) setNameGroup(response.filter.name)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally {
setLoading(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad() handleLoad(true)
}, [active, search, group, update]) }, [active, search, group])
useEffect(() => {
handleLoad(false)
}, [update])
return ( return (
<SafeAreaView> <SafeAreaView>
@@ -75,6 +85,13 @@ export default function Discussion() {
} }
<View> <View>
{ {
loading ?
arrSkeleton.map((item: any, i: number) => {
return (
<SkeletonContent key={i} />
)
})
:
data.length > 0 data.length > 0
? ?
data.map((item: any, i: number) => { data.map((item: any, i: number) => {
@@ -101,6 +118,7 @@ export default function Discussion() {
</View> </View>
} }
rightBottomInfo={`${item.total_komentar} Komentar`} rightBottomInfo={`${item.total_komentar} Komentar`}
/> />
) )
}) })

View File

@@ -4,6 +4,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
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 SkeletonTwoItem from "@/components/skeletonTwoItem";
import { ColorsStatus } from "@/constants/ColorsStatus"; import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api"; import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api";
@@ -28,28 +29,37 @@ export default function MemberDiscussionDetail() {
const [isModal, setModal] = useState(false) const [isModal, setModal] = useState(false)
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 arrSkeleton = Array.from({ length: 5 }, (_, index) => index)
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
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)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally {
setLoading(false)
} }
} }
useEffect(() => {
handleLoad(false)
}, [update]);
useEffect(() => { useEffect(() => {
handleLoad() handleLoad(true)
}, [update]); }, []);
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)
ToastAndroid.show("Berhasil mengelluarkan anggota dari diskusi", ToastAndroid.SHORT) ToastAndroid.show("Berhasil mengeluarkan anggota dari diskusi", ToastAndroid.SHORT)
handleLoad() handleLoad(false)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {
@@ -84,6 +94,13 @@ export default function MemberDiscussionDetail() {
/> />
} }
{ {
loading ?
arrSkeleton.map((item, index) => {
return (
<SkeletonTwoItem key={index} />
)
})
:
data.map((item, index) => { data.map((item, index) => {
return ( return (
<BorderBottomItem <BorderBottomItem

View File

@@ -84,13 +84,15 @@ export default function Index() {
} }
} }
useEffect(() => {
handleLoad(false)
}, [update])
useEffect(() => { useEffect(() => {
handleLoad(true) handleLoad(true)
}, [active, search]) }, [active, search])
useEffect(() => {
handleLoad(false)
}, [update])
return ( return (

View File

@@ -59,15 +59,16 @@ export default function Index() {
} }
} }
useEffect(() => {
handleLoad(true)
}, [active, search, group])
useEffect(() => { useEffect(() => {
handleLoad(false) handleLoad(false)
}, [update]) }, [update])
useEffect(() => {
handleLoad(true)
}, [active, search, group])
function handleChooseData(id: string, name: string, active: boolean, group: string) { function handleChooseData(id: string, name: string, active: boolean, group: string) {
setChooseData({ id, name, active, idGroup: group }) setChooseData({ id, name, active, idGroup: group })
setModal(true) setModal(true)

View File

@@ -78,7 +78,7 @@ export default function DetailProject() {
<Stack.Screen <Stack.Screen
options={{ options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />, headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: loading ? 'Loading...' : data?.title, headerTitle: loading ? 'Loading... ' : data?.title,
headerTitleAlign: 'center', headerTitleAlign: 'center',
headerRight: () => (entityUser.role == "user" || entityUser.role == "coadmin") && !isMember ? null : <HeaderRightProjectDetail id={id} status={data?.status} />, headerRight: () => (entityUser.role == "user" || entityUser.role == "coadmin") && !isMember ? null : <HeaderRightProjectDetail id={id} status={data?.status} />,
}} }}

View File

@@ -4,6 +4,8 @@ import InputSearch from "@/components/inputSearch";
import LabelStatus from "@/components/labelStatus"; import LabelStatus from "@/components/labelStatus";
import PaperGridContent from "@/components/paperGridContent"; import PaperGridContent from "@/components/paperGridContent";
import ProgressBar from "@/components/progressBar"; import ProgressBar from "@/components/progressBar";
import Skeleton from "@/components/skeleton";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import { ColorsStatus } from "@/constants/ColorsStatus"; import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { apiGetProject } from "@/lib/api"; import { apiGetProject } from "@/lib/api";
@@ -41,9 +43,12 @@ export default function ListProject() {
const [data, setData] = useState<Props[]>([]); const [data, setData] = useState<Props[]>([]);
const [isList, setList] = useState(false); const [isList, setList] = useState(false);
const update = useSelector((state: any) => state.projectUpdate) const update = useSelector((state: any) => state.projectUpdate)
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 3 }, (_, index) => index)
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current)); const hasil = await decryptToken(String(token?.current));
const response = await apiGetProject({ const response = await apiGetProject({
user: hasil, user: hasil,
@@ -59,12 +64,19 @@ export default function ListProject() {
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
setLoading(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad(); handleLoad(false);
}, [status, search, group, cat, update.data]); }, [update.data]);
useEffect(() => {
handleLoad(true);
}, [status, search, group, cat]);
return ( return (
<SafeAreaView> <SafeAreaView>
@@ -75,7 +87,7 @@ export default function ListProject() {
active={String(status)} active={String(status)}
value="0" value="0"
onPress={() => { onPress={() => {
router.push( router.replace(
`/project?status=0&group=${group}&search=${search}&cat=${cat}` `/project?status=0&group=${group}&search=${search}&cat=${cat}`
); );
}} }}
@@ -93,7 +105,7 @@ export default function ListProject() {
active={String(status)} active={String(status)}
value="1" value="1"
onPress={() => { onPress={() => {
router.push( router.replace(
`/project?status=1&group=${group}&search=${search}&cat=${cat}` `/project?status=1&group=${group}&search=${search}&cat=${cat}`
); );
}} }}
@@ -111,7 +123,7 @@ export default function ListProject() {
active={String(status)} active={String(status)}
value="2" value="2"
onPress={() => { onPress={() => {
router.push( router.replace(
`/project?status=2&group=${group}&search=${search}&cat=${cat}` `/project?status=2&group=${group}&search=${search}&cat=${cat}`
); );
}} }}
@@ -129,7 +141,7 @@ export default function ListProject() {
active={String(status)} active={String(status)}
value="3" value="3"
onPress={() => { onPress={() => {
router.push( router.replace(
`/project?status=3&group=${group}&search=${search}&cat=${cat}` `/project?status=3&group=${group}&search=${search}&cat=${cat}`
); );
}} }}
@@ -171,6 +183,16 @@ export default function ListProject() {
</Text> </Text>
</View> </View>
{ {
loading ?
isList ?
arrSkeleton.map((item, index) => (
<SkeletonTwoItem key={index} />
))
:
arrSkeleton.map((item, index) => (
<Skeleton key={index} width={100} height={180} widthType="percent" borderRadius={10} />
))
:
data.length > 0 data.length > 0
? ?
isList ? ( isList ? (

View File

@@ -27,8 +27,8 @@ export default function BorderBottomItem({ title, subtitle, icon, desc, onPress,
<Pressable style={[borderType == 'bottom' ? Styles.wrapItemBorderBottom : borderType == 'all' ? Styles.wrapItemBorderAll : Styles.wrapItemBorderNone, bgColor && bgColor == 'white' && ColorsStatus.white]} onPress={onPress}> <Pressable style={[borderType == 'bottom' ? Styles.wrapItemBorderBottom : borderType == 'all' ? Styles.wrapItemBorderAll : Styles.wrapItemBorderNone, bgColor && bgColor == 'white' && ColorsStatus.white]} onPress={onPress}>
<View style={[Styles.rowItemsCenter]}> <View style={[Styles.rowItemsCenter]}>
{icon} {icon}
<View style={[Styles.rowSpaceBetween, width ? { width: lebar } : { width: '88%' },]}> <View style={[Styles.rowSpaceBetween, width ? { width: lebar } : { width: '88%' }]}>
<View style={[Styles.ml10, width ? { width: lebar } : { width: '88%' }]}> <View style={[Styles.ml10, width ? { width: lebar } : { width: '70%' }]}>
<Text style={[titleWeight == 'normal' ? Styles.textDefault : Styles.textDefaultSemiBold]} numberOfLines={1} ellipsizeMode='tail'>{title}</Text> <Text style={[titleWeight == 'normal' ? Styles.textDefault : Styles.textDefaultSemiBold]} numberOfLines={1} ellipsizeMode='tail'>{title}</Text>
{ {
subtitle && subtitle &&

View File

@@ -133,7 +133,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
<ImageWithLabel <ImageWithLabel
key={index} key={index}
label={item.name} label={item.name}
src={item.img} src={`https://wibu-storage.wibudev.com/api/files/${item.img}`}
onClick={() => onChoose(item.idUser, item.name, item.img)} onClick={() => onChoose(item.idUser, item.name, item.img)}
/> />
)) ))

View File

@@ -145,7 +145,7 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
}; };
return ( return (
<DrawerBottom animation="slide" isVisible={open} setVisible={close} title={title} height={75}> <DrawerBottom animation="slide" isVisible={open} setVisible={close} title={title} height={85}>
<ScrollView style={[Styles.mb50]}> <ScrollView style={[Styles.mb50]}>
{ {
category == 'share-division' ? category == 'share-division' ?
@@ -170,8 +170,8 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
: :
data.map((item: any, index: number) => { data.map((item: any, index: number) => {
return ( return (
<> <View key={index}>
<Pressable key={index} style={[Styles.itemSelectModal]} onPress={() => { handleGroupCheck(item.id) }}> <Pressable style={[Styles.itemSelectModal]} onPress={() => { handleGroupCheck(item.id) }}>
<Text style={[Styles.textMediumSemiBold]}>{item.name}</Text> <Text style={[Styles.textMediumSemiBold]}>{item.name}</Text>
{ {
checked[item.id] && checked[item.id].length === item.Division.length checked[item.id] && checked[item.id].length === item.Division.length
@@ -185,7 +185,7 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
item.Division.map((child: any, v: number) => { item.Division.map((child: any, v: number) => {
return ( return (
<Pressable key={v} style={[Styles.itemSelectModal]} onPress={() => { handleCheck(item.id, child.id) }}> <Pressable key={v} style={[Styles.itemSelectModal]} onPress={() => { handleCheck(item.id, child.id) }}>
<Text style={[Styles.ml10, Styles.textMediumNormal]}>{child.name}</Text> <Text style={[Styles.ml10, Styles.textMediumNormal, Styles.w80]} numberOfLines={1} ellipsizeMode="tail" >{child.name}</Text>
{ {
checked[item.id] && checked[item.id].includes(child.id) && <AntDesign name="check" size={20} /> checked[item.id] && checked[item.id].includes(child.id) && <AntDesign name="check" size={20} />
} }
@@ -193,7 +193,7 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
) )
}) })
} }
</> </View>
) )
}) })
} }

View File

@@ -14,6 +14,7 @@ import AlertKonfirmasi from "../alertKonfirmasi";
import BorderBottomItem from "../borderBottomItem"; import BorderBottomItem from "../borderBottomItem";
import DrawerBottom from "../drawerBottom"; import DrawerBottom from "../drawerBottom";
import MenuItemRow from "../menuItemRow"; import MenuItemRow from "../menuItemRow";
import Skeleton from "../skeleton";
type Props = { type Props = {
@@ -32,9 +33,12 @@ export default function SectionFile({ status, member }: { status: number | undef
const update = useSelector((state: any) => state.projectUpdate) const update = useSelector((state: any) => state.projectUpdate)
const [idSelect, setIdSelect] = useState('') const [idSelect, setIdSelect] = useState('')
const dispatch = useDispatch() const dispatch = useDispatch()
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 3 })
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current)); const hasil = await decryptToken(String(token?.current));
const response = await apiGetProjectOne({ const response = await apiGetProjectOne({
user: hasil, user: hasil,
@@ -44,13 +48,20 @@ export default function SectionFile({ status, member }: { status: number | undef
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
setLoading(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad(); handleLoad(false);
}, [update.file]); }, [update.file]);
useEffect(() => {
handleLoad(true);
}, []);
async function handleDelete() { async function handleDelete() {
try { try {
const hasil = await decryptToken(String(token?.current)); const hasil = await decryptToken(String(token?.current));
@@ -141,6 +152,13 @@ export default function SectionFile({ status, member }: { status: number | undef
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>File</Text> <Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>File</Text>
<View style={[Styles.wrapPaper]}> <View style={[Styles.wrapPaper]}>
{ {
loading ?
arrSkeleton.map((item, index) => {
return (
<Skeleton key={index} width={100} height={40} widthType="percent" borderRadius={10} />
)
})
:
data.length > 0 ? data.length > 0 ?
data.map((item, index) => { data.map((item, index) => {
return ( return (

View File

@@ -12,6 +12,7 @@ import BorderBottomItem from "../borderBottomItem";
import DrawerBottom from "../drawerBottom"; import DrawerBottom from "../drawerBottom";
import ImageUser from "../imageNew"; import ImageUser from "../imageNew";
import MenuItemRow from "../menuItemRow"; import MenuItemRow from "../menuItemRow";
import SkeletonTwoItem from "../skeletonTwoItem";
type Props = { type Props = {
id: string; id: string;
@@ -30,14 +31,17 @@ export default function SectionMember({ status }: { status: number | undefined }
const { token, decryptToken } = useAuthSession(); const { token, decryptToken } = useAuthSession();
const { id } = useLocalSearchParams<{ id: string }>(); const { id } = useLocalSearchParams<{ id: string }>();
const [data, setData] = useState<Props[]>([]); const [data, setData] = useState<Props[]>([]);
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 3 })
const [memberChoose, setMemberChoose] = useState({ const [memberChoose, setMemberChoose] = useState({
id: '', id: '',
name: '', name: '',
}) })
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current)); const hasil = await decryptToken(String(token?.current));
const response = await apiGetProjectOne({ const response = await apiGetProjectOne({
user: hasil, user: hasil,
@@ -47,13 +51,19 @@ export default function SectionMember({ status }: { status: number | undefined }
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
setLoading(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad(); handleLoad(false);
}, [update.member]); }, [update.member]);
useEffect(() => {
handleLoad(true);
}, []);
async function handleDeleteMember() { async function handleDeleteMember() {
try { try {
const hasil = await decryptToken(String(token?.current)); const hasil = await decryptToken(String(token?.current));
@@ -81,6 +91,13 @@ export default function SectionMember({ status }: { status: number | undefined }
<View style={[Styles.wrapPaper]}> <View style={[Styles.wrapPaper]}>
{ {
loading ?
arrSkeleton.map((item, index) => {
return (
<SkeletonTwoItem key={index} />
)
})
:
data.length > 0 data.length > 0
? ?
data.map((item, index) => { data.map((item, index) => {

View File

@@ -12,6 +12,7 @@ import DrawerBottom from "../drawerBottom";
import ItemSectionTanggalTugas from "../itemSectionTanggalTugas"; import ItemSectionTanggalTugas from "../itemSectionTanggalTugas";
import MenuItemRow from "../menuItemRow"; import MenuItemRow from "../menuItemRow";
import ModalSelect from "../modalSelect"; import ModalSelect from "../modalSelect";
import SkeletonTask from "../skeletonTask";
type Props = { type Props = {
id: string; id: string;
@@ -32,13 +33,16 @@ export default function SectionTanggalTugasProject({ status, member }: { status:
const { token, decryptToken } = useAuthSession(); const { token, decryptToken } = useAuthSession();
const { id } = useLocalSearchParams<{ id: string }>(); const { id } = useLocalSearchParams<{ id: string }>();
const [data, setData] = useState<Props[]>([]); const [data, setData] = useState<Props[]>([]);
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 5 });
const [tugas, setTugas] = useState({ const [tugas, setTugas] = useState({
id: '', id: '',
status: 0, status: 0,
}) })
async function handleLoad() { async function handleLoad(loading: boolean) {
try { try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current)); const hasil = await decryptToken(String(token?.current));
const response = await apiGetProjectOne({ const response = await apiGetProjectOne({
user: hasil, user: hasil,
@@ -48,13 +52,18 @@ export default function SectionTanggalTugasProject({ status, member }: { status:
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally {
setLoading(false)
} }
} }
useEffect(() => { useEffect(() => {
handleLoad(); handleLoad(false);
}, [update.task]); }, [update.task]);
useEffect(() => {
handleLoad(true);
}, []);
async function handleUpdate(status: number) { async function handleUpdate(status: number) {
@@ -100,6 +109,13 @@ export default function SectionTanggalTugasProject({ status, member }: { status:
</Text> </Text>
<View style={[Styles.wrapPaper]}> <View style={[Styles.wrapPaper]}>
{ {
loading ?
arrSkeleton.map((item, index) => {
return (
<SkeletonTask key={index} />
)
})
:
data.length > 0 data.length > 0
? ?
data.map((item, index) => { data.map((item, index) => {

View File

@@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
import { StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'; import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
const Skeleton = ({ width, height, borderRadius = 0, widthType = 'number' }: { width: number, widthType?: 'number' | 'percent', height: number, borderRadius?: number }) => { const Skeleton = ({ width, height, borderRadius = 0, widthType = 'number', style }: { width: number, widthType?: 'number' | 'percent', height: number, borderRadius?: number, style?: any }) => {
const opacity = useSharedValue(0.3); const opacity = useSharedValue(0.3);
useEffect(() => { useEffect(() => {
@@ -25,7 +25,7 @@ const Skeleton = ({ width, height, borderRadius = 0, widthType = 'number' }: { w
style={[ style={[
styles.skeleton, styles.skeleton,
{ width: widthType === 'percent' ? `${width}%` : width, height: height, borderRadius: borderRadius }, { width: widthType === 'percent' ? `${width}%` : width, height: height, borderRadius: borderRadius },
animatedStyle, Styles.mv05 animatedStyle, Styles.mv05, style
]} ]}
/> />
); );

View File

@@ -0,0 +1,19 @@
import Styles from "@/constants/Styles";
import { View } from "react-native";
import Skeleton from "./skeleton";
export default function SkeletonContent() {
return (
<View style={[Styles.borderBottom, Styles.mv05]}>
<View style={[Styles.rowOnly]}>
<Skeleton width={40} height={40} borderRadius={10} />
<View style={[{ flex: 1, justifyContent: 'center' }, Styles.ph05]}>
<Skeleton width={30} widthType="percent" height={10} borderRadius={10} />
<Skeleton width={50} widthType="percent" height={10} borderRadius={10} />
</View>
</View>
<Skeleton width={100} widthType="percent" height={40} borderRadius={5} />
<Skeleton width={100} widthType="percent" height={10} borderRadius={5} />
</View>
)
}

View File

@@ -0,0 +1,25 @@
import Styles from "@/constants/Styles";
import { View } from "react-native";
import Skeleton from "./skeleton";
export default function SkeletonTask() {
return (
<View style={[Styles.borderBottom, Styles.mv05]}>
<View style={[Styles.rowOnly]}>
<Skeleton width={10} height={10} borderRadius={3} style={[Styles.mr05]} />
<Skeleton width={30} widthType="percent" height={10} borderRadius={10} />
</View>
<Skeleton width={100} widthType="percent" height={30} borderRadius={5} />
<View style={[Styles.rowSpaceBetween]}>
<View style={[Styles.w45]}>
<Skeleton width={100} height={10} borderRadius={5} />
<Skeleton width={100} widthType="percent" height={30} borderRadius={5} />
</View>
<View style={[Styles.w45]}>
<Skeleton width={100} height={10} borderRadius={5} />
<Skeleton width={100} widthType="percent" height={30} borderRadius={5} />
</View>
</View>
</View>
)
}

View File

@@ -5,7 +5,7 @@ import Skeleton from "./skeleton";
export default function SkeletonTwoItem() { export default function SkeletonTwoItem() {
return ( return (
<View style={[Styles.rowOnly]}> <View style={[Styles.rowOnly]}>
<Skeleton width={50} height={50} borderRadius={10} /> <Skeleton width={50} height={50} borderRadius={100} />
<View style={[{ flex: 1 }, Styles.ph05]}> <View style={[{ flex: 1 }, Styles.ph05]}>
<Skeleton width={100} widthType="percent" height={50} borderRadius={10} /> <Skeleton width={100} widthType="percent" height={50} borderRadius={10} />
</View> </View>

View File

@@ -182,6 +182,9 @@ const Styles = StyleSheet.create({
w50: { w50: {
width: '50%' width: '50%'
}, },
w45: {
width: '45%'
},
w40: { w40: {
width: '40%' width: '40%'
}, },