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 ButtonBackHeader from "@/components/buttonBackHeader";
import Skeleton from "@/components/skeleton";
import Styles from "@/constants/Styles";
import { apiGetAnnouncementOne } from "@/lib/api";
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 { useEffect, useState } from "react";
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 entityUser = useSelector((state: any) => state.user)
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 {
setLoading(loading)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetAnnouncementOne({ id: id, user: hasil })
setData(response.data)
setDataMember(response.member)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad()
handleLoad(false)
}, [update])
useEffect(() => {
handleLoad(true)
}, [])
return (
<SafeAreaView>
<Stack.Screen
@@ -53,37 +63,66 @@ export default function DetailAnnouncement() {
<ScrollView>
<View style={[Styles.p15]}>
<View style={[Styles.wrapPaper]}>
<View style={Styles.rowItemsCenter}>
<MaterialIcons name="campaign" size={30} color="black" style={Styles.mr05} />
<Text style={[Styles.textDefaultSemiBold]}>{data?.title}</Text>
</View>
<View style={[Styles.mt10]}>
<RenderHTML
contentWidth={contentWidth}
source={{ html: data?.desc }}
/>
</View>
{
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}>
<MaterialIcons name="campaign" size={30} color="black" style={Styles.mr05} />
<Text style={[Styles.textDefaultSemiBold]}>{data?.title}</Text>
</View>
<View style={[Styles.mt10]}>
<RenderHTML
contentWidth={contentWidth}
source={{ html: data?.desc }}
/>
</View>
</>
}
</View>
<View style={[Styles.wrapPaper, Styles.mv15]}>
{
Object.keys(dataMember).map((v: any, i: any) => {
return (
<View key={i} style={[Styles.mb05]}>
<Text style={[Styles.textDefaultSemiBold]}>{dataMember[v]?.[0].group}</Text>
{
dataMember[v].map((item: any, x: any) => {
return (
<View key={x} style={[Styles.rowItemsCenter]}>
<Entypo name="dot-single" size={24} color="black" />
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{item.division}</Text>
</View>
)
})
}
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) => {
return (
<View key={i} style={[Styles.mb05]}>
<Text style={[Styles.textDefaultSemiBold]}>{dataMember[v]?.[0].group}</Text>
{
dataMember[v].map((item: any, x: any) => {
return (
<View key={x} style={[Styles.rowItemsCenter, Styles.w90]}>
<Entypo name="dot-single" size={24} color="black" />
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{item.division}</Text>
</View>
)
})
}
</View>
)
})
</View>
)
})
}
</View>
</View>

View File

@@ -2,6 +2,7 @@ import HeaderRightAnnouncementList from "@/components/announcement/headerAnnounc
import BorderBottomItem from "@/components/borderBottomItem";
import ButtonBackHeader from "@/components/buttonBackHeader";
import InputSearch from "@/components/inputSearch";
import SkeletonContent from "@/components/skeletonContent";
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { apiGetAnnouncement } from "@/lib/api";
@@ -26,20 +27,29 @@ export default function Announcement() {
const [search, setSearch] = useState('')
const entityUser = useSelector((state: any) => state.user)
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 {
setLoading(loading)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetAnnouncement({ user: hasil, search: search })
setData(response.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad()
}, [search, update])
handleLoad(false)
}, [update])
useEffect(() => {
handleLoad(true)
}, [search])
return (
<SafeAreaView>
@@ -56,27 +66,34 @@ export default function Announcement() {
<View style={[Styles.p15, Styles.mb100]}>
<InputSearch onChange={setSearch} />
{
data.length > 0
?
data.map((item, index) => {
loading ?
arrSkeleton.map((item, index) => {
return (
<BorderBottomItem
key={index}
onPress={() => { router.push(`/announcement/${item.id}`) }}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<MaterialIcons name="campaign" size={25} color={'#384288'} />
</View>
}
title={item.title}
desc={item.desc.replace(/<[^>]*>?/gm, '')}
rightTopInfo={item.createdAt}
/>
<SkeletonContent key={index} />
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada pengumuman</Text>
data.length > 0
?
data.map((item, index) => {
return (
<BorderBottomItem
key={index}
onPress={() => { router.push(`/announcement/${item.id}`) }}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<MaterialIcons name="campaign" size={25} color={'#384288'} />
</View>
}
title={item.title}
desc={item.desc.replace(/<[^>]*>?/gm, '')}
rightTopInfo={item.createdAt}
/>
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada pengumuman</Text>
}
</View>
</ScrollView>

View File

@@ -4,6 +4,8 @@ import HeaderRightDiscussionGeneralDetail from "@/components/discussion_general/
import ImageUser from "@/components/imageNew";
import { InputForm } from "@/components/inputForm";
import LabelStatus from "@/components/labelStatus";
import Skeleton from "@/components/skeleton";
import SkeletonContent from "@/components/skeletonContent";
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneralOne, apiSendDiscussionGeneralCommentar } from "@/lib/api";
@@ -41,12 +43,22 @@ export default function DetailDiscussionGeneral() {
const [memberDiscussion, setMemberDiscussion] = useState(false)
const [komentar, setKomentar] = useState('')
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 {
if (cat == "detail") {
setLoading(loading)
} else if (cat == "komentar") {
setLoadingKomentar(loading)
}
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDiscussionGeneralOne({ id: id, user: hasil, cat })
if (cat == 'detail') {
setData(response.data)
} else if (cat == 'komentar') {
@@ -54,17 +66,27 @@ export default function DetailDiscussionGeneral() {
} else if (cat == 'cek-anggota') {
setMemberDiscussion(response.data)
}
} catch (error) {
console.error(error)
} finally {
setLoading(false)
setLoadingKomentar(false)
}
}
useEffect(() => {
handleLoad('detail')
handleLoad('komentar')
handleLoad('cek-anggota')
handleLoad('detail', false)
handleLoad('komentar', false)
handleLoad('cek-anggota', false)
}, [update]);
useEffect(() => {
handleLoad('detail', true)
handleLoad('komentar', true)
handleLoad('cek-anggota', true)
}, []);
async function handleKomentar() {
try {
if (komentar != '') {
@@ -72,7 +94,7 @@ export default function DetailDiscussionGeneral() {
const response = await apiSendDiscussionGeneralCommentar({ id: id, data: { desc: komentar, user: hasil } })
if (response.success) {
setKomentar('')
handleLoad('komentar')
handleLoad('komentar', false)
}
}
} catch (error) {
@@ -93,45 +115,57 @@ export default function DetailDiscussionGeneral() {
<View style={{ flex: 1 }}>
<ScrollView>
<View style={[Styles.p15, Styles.mb100]}>
<BorderBottomItem
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<MaterialIcons name="chat" size={25} color={'#384288'} />
</View>
}
title={data?.title}
subtitle={
!data?.isActive ?
<LabelStatus category='warning' text='ARSIP' size="small" />
:
<LabelStatus category={data.status == 1 ? 'success' : 'error'} text={data.status == 1 ? 'BUKA' : 'TUTUP'} size="small" />
}
rightTopInfo={data?.createdAt}
desc={data?.desc}
leftBottomInfo={
<View style={[Styles.rowItemsCenter]}>
<Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{dataKomentar.length} Komentar</Text>
</View>
}
/>
{
loading ?
<SkeletonContent />
:
<BorderBottomItem
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<MaterialIcons name="chat" size={25} color={'#384288'} />
</View>
}
title={data?.title}
subtitle={
!data?.isActive ?
<LabelStatus category='warning' text='ARSIP' size="small" />
:
<LabelStatus category={data.status == 1 ? 'success' : 'error'} text={data.status == 1 ? 'BUKA' : 'TUTUP'} size="small" />
}
rightTopInfo={data?.createdAt}
desc={data?.desc}
leftBottomInfo={
<View style={[Styles.rowItemsCenter]}>
<Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{dataKomentar.length} Komentar</Text>
</View>
}
/>
}
<View style={[Styles.p15]}>
{
dataKomentar.map((item, i) => {
return (
<BorderBottomItem
key={i}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="xs" />
}
title={item.username}
rightTopInfo={item.createdAt}
desc={item.comment}
/>
)
})
loadingKomentar ?
arrSkeleton.map((item: any, i: number) => {
return (
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
)
})
:
dataKomentar.map((item, i) => {
return (
<BorderBottomItem
key={i}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="xs" />
}
title={item.username}
rightTopInfo={item.createdAt}
desc={item.comment}
/>
)
})
}
</View>
</View>

View File

@@ -2,6 +2,7 @@ import BorderBottomItem from "@/components/borderBottomItem";
import ButtonTab from "@/components/buttonTab";
import InputSearch from "@/components/inputSearch";
import LabelStatus from "@/components/labelStatus";
import SkeletonContent from "@/components/skeletonContent";
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { apiGetDiscussionGeneral } from "@/lib/api";
@@ -30,21 +31,30 @@ export default function Discussion() {
const [nameGroup, setNameGroup] = useState('')
const [data, setData] = useState<Props[]>([])
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 {
setLoading(loading)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDiscussionGeneral({ user: hasil, active: String(active), search: search, group: String(group) })
setData(response.data)
setNameGroup(response.filter.name)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad()
}, [active, search, group, update])
handleLoad(true)
}, [active, search, group])
useEffect(() => {
handleLoad(false)
}, [update])
return (
<SafeAreaView>
@@ -75,37 +85,45 @@ export default function Discussion() {
}
<View>
{
data.length > 0
?
data.map((item: any, i: number) => {
loading ?
arrSkeleton.map((item: any, i: number) => {
return (
<BorderBottomItem
key={i}
onPress={() => { router.push(`/discussion/${item.id}`) }}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<MaterialIcons name="chat" size={25} color={'#384288'} />
</View>
}
title={item.title}
subtitle={
active != "false" && <LabelStatus category={item.status === 1 ? "success" : "error"} text={item.status === 1 ? "BUKA" : "TUTUP"} size="small" />
}
rightTopInfo={item.createdAt}
desc={item.desc}
leftBottomInfo={
<View style={[Styles.rowItemsCenter]}>
<Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>Diskusikan</Text>
</View>
}
rightBottomInfo={`${item.total_komentar} Komentar`}
/>
<SkeletonContent key={i} />
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
data.length > 0
?
data.map((item: any, i: number) => {
return (
<BorderBottomItem
key={i}
onPress={() => { router.push(`/discussion/${item.id}`) }}
borderType="bottom"
icon={
<View style={[Styles.iconContent, ColorsStatus.lightGreen]}>
<MaterialIcons name="chat" size={25} color={'#384288'} />
</View>
}
title={item.title}
subtitle={
active != "false" && <LabelStatus category={item.status === 1 ? "success" : "error"} text={item.status === 1 ? "BUKA" : "TUTUP"} size="small" />
}
rightTopInfo={item.createdAt}
desc={item.desc}
leftBottomInfo={
<View style={[Styles.rowItemsCenter]}>
<Ionicons name="chatbox-ellipses-outline" size={18} color="grey" style={Styles.mr05} />
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>Diskusikan</Text>
</View>
}
rightBottomInfo={`${item.total_komentar} Komentar`}
/>
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada data</Text>
}
</View>
</View>

View File

@@ -4,6 +4,7 @@ import ButtonBackHeader from "@/components/buttonBackHeader";
import DrawerBottom from "@/components/drawerBottom";
import ImageUser from "@/components/imageNew";
import MenuItemRow from "@/components/menuItemRow";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { apiDeleteMemberDiscussionGeneral, apiGetDiscussionGeneralOne } from "@/lib/api";
@@ -28,28 +29,37 @@ export default function MemberDiscussionDetail() {
const [isModal, setModal] = useState(false)
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)
async function handleLoad() {
async function handleLoad(loading: boolean) {
try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current))
const response = await apiGetDiscussionGeneralOne({ id: id, user: hasil, cat: 'anggota' })
setData(response.data)
} catch (error) {
console.error(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad(false)
}, [update]);
useEffect(() => {
handleLoad()
}, [update]);
handleLoad(true)
}, []);
async function handleDeleteUser() {
try {
const hasil = await decryptToken(String(token?.current))
await apiDeleteMemberDiscussionGeneral({ user: hasil, idUser: chooseUser.idUser }, id)
ToastAndroid.show("Berhasil mengelluarkan anggota dari diskusi", ToastAndroid.SHORT)
handleLoad()
ToastAndroid.show("Berhasil mengeluarkan anggota dari diskusi", ToastAndroid.SHORT)
handleLoad(false)
} catch (error) {
console.error(error)
} finally {
@@ -84,22 +94,29 @@ export default function MemberDiscussionDetail() {
/>
}
{
data.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
}
title={item.name}
onPress={() => {
setChooseUser(item)
setModal(true)
}}
/>
)
})
loading ?
arrSkeleton.map((item, index) => {
return (
<SkeletonTwoItem key={index} />
)
})
:
data.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="bottom"
icon={
<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} size="sm" />
}
title={item.name}
onPress={() => {
setChooseUser(item)
setModal(true)
}}
/>
)
})
}
</View>
</View>

View File

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

View File

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

View File

@@ -78,7 +78,7 @@ export default function DetailProject() {
<Stack.Screen
options={{
headerLeft: () => <ButtonBackHeader onPress={() => { router.back() }} />,
headerTitle: loading ? 'Loading...' : data?.title,
headerTitle: loading ? 'Loading... ' : data?.title,
headerTitleAlign: 'center',
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 PaperGridContent from "@/components/paperGridContent";
import ProgressBar from "@/components/progressBar";
import Skeleton from "@/components/skeleton";
import SkeletonTwoItem from "@/components/skeletonTwoItem";
import { ColorsStatus } from "@/constants/ColorsStatus";
import Styles from "@/constants/Styles";
import { apiGetProject } from "@/lib/api";
@@ -41,9 +43,12 @@ export default function ListProject() {
const [data, setData] = useState<Props[]>([]);
const [isList, setList] = useState(false);
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 {
setLoading(loading)
const hasil = await decryptToken(String(token?.current));
const response = await apiGetProject({
user: hasil,
@@ -59,12 +64,19 @@ export default function ListProject() {
}
} catch (error) {
console.error(error);
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad();
}, [status, search, group, cat, update.data]);
handleLoad(false);
}, [update.data]);
useEffect(() => {
handleLoad(true);
}, [status, search, group, cat]);
return (
<SafeAreaView>
@@ -75,7 +87,7 @@ export default function ListProject() {
active={String(status)}
value="0"
onPress={() => {
router.push(
router.replace(
`/project?status=0&group=${group}&search=${search}&cat=${cat}`
);
}}
@@ -93,7 +105,7 @@ export default function ListProject() {
active={String(status)}
value="1"
onPress={() => {
router.push(
router.replace(
`/project?status=1&group=${group}&search=${search}&cat=${cat}`
);
}}
@@ -111,7 +123,7 @@ export default function ListProject() {
active={String(status)}
value="2"
onPress={() => {
router.push(
router.replace(
`/project?status=2&group=${group}&search=${search}&cat=${cat}`
);
}}
@@ -129,7 +141,7 @@ export default function ListProject() {
active={String(status)}
value="3"
onPress={() => {
router.push(
router.replace(
`/project?status=3&group=${group}&search=${search}&cat=${cat}`
);
}}
@@ -171,77 +183,87 @@ export default function ListProject() {
</Text>
</View>
{
data.length > 0
?
isList ? (
<View>
{data.map((item, index) => {
return (
<BorderBottomItem
key={index}
onPress={() => { router.push(`/project/${item.id}`); }}
borderType="bottom"
icon={
<View
style={[Styles.iconContent, ColorsStatus.lightGreen]}
>
<AntDesign
name="areachart"
size={25}
color={"#384288"}
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
?
isList ? (
<View>
{data.map((item, index) => {
return (
<BorderBottomItem
key={index}
onPress={() => { router.push(`/project/${item.id}`); }}
borderType="bottom"
icon={
<View
style={[Styles.iconContent, ColorsStatus.lightGreen]}
>
<AntDesign
name="areachart"
size={25}
color={"#384288"}
/>
</View>
}
title={item.title}
/>
);
})}
</View>
) : (
<View>
{data.map((item, index) => {
return (
<PaperGridContent
key={index}
onPress={() => {
router.push(`/project/${item.id}`);
}}
content="page"
title={item.title}
headerColor="primary"
>
<ProgressBar value={item.progress} category="page" />
<View style={[Styles.rowSpaceBetween]}>
<Text style={[Styles.textDefault, Styles.cGray]}>
{item.createdAt}
</Text>
<LabelStatus
size="default"
category={
item.status === 0 ? 'primary' :
item.status === 1 ? 'warning' :
item.status === 2 ? 'success' :
item.status === 3 ? 'error' :
'primary'
}
text={
item.status === 0 ? 'SEGERA' :
item.status === 1 ? 'DIKERJAKAN' :
item.status === 2 ? 'SELESAI' :
item.status === 3 ? 'DIBATALKAN' :
'SEGERA'
}
/>
</View>
}
title={item.title}
/>
);
})}
</PaperGridContent>
);
})}
</View>
)
:
<View style={[Styles.mt15]}>
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada kegiatan</Text>
</View>
) : (
<View>
{data.map((item, index) => {
return (
<PaperGridContent
key={index}
onPress={() => {
router.push(`/project/${item.id}`);
}}
content="page"
title={item.title}
headerColor="primary"
>
<ProgressBar value={item.progress} category="page" />
<View style={[Styles.rowSpaceBetween]}>
<Text style={[Styles.textDefault, Styles.cGray]}>
{item.createdAt}
</Text>
<LabelStatus
size="default"
category={
item.status === 0 ? 'primary' :
item.status === 1 ? 'warning' :
item.status === 2 ? 'success' :
item.status === 3 ? 'error' :
'primary'
}
text={
item.status === 0 ? 'SEGERA' :
item.status === 1 ? 'DIKERJAKAN' :
item.status === 2 ? 'SELESAI' :
item.status === 3 ? 'DIBATALKAN' :
'SEGERA'
}
/>
</View>
</PaperGridContent>
);
})}
</View>
)
:
<View style={[Styles.mt15]}>
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada kegiatan</Text>
</View>
}
</View>
</ScrollView>

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}>
<View style={[Styles.rowItemsCenter]}>
{icon}
<View style={[Styles.rowSpaceBetween, width ? { width: lebar } : { width: '88%' },]}>
<View style={[Styles.ml10, width ? { width: lebar } : { width: '88%' }]}>
<View style={[Styles.rowSpaceBetween, 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>
{
subtitle &&

View File

@@ -133,7 +133,7 @@ export default function ModalSelect({ open, close, title, category, idParent, on
<ImageWithLabel
key={index}
label={item.name}
src={item.img}
src={`https://wibu-storage.wibudev.com/api/files/${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 (
<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]}>
{
category == 'share-division' ?
@@ -170,8 +170,8 @@ export default function ModalSelectMultiple({ open, close, title, category, choo
:
data.map((item: any, index: number) => {
return (
<>
<Pressable key={index} style={[Styles.itemSelectModal]} onPress={() => { handleGroupCheck(item.id) }}>
<View key={index}>
<Pressable style={[Styles.itemSelectModal]} onPress={() => { handleGroupCheck(item.id) }}>
<Text style={[Styles.textMediumSemiBold]}>{item.name}</Text>
{
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) => {
return (
<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} />
}
@@ -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 DrawerBottom from "../drawerBottom";
import MenuItemRow from "../menuItemRow";
import Skeleton from "../skeleton";
type Props = {
@@ -32,9 +33,12 @@ export default function SectionFile({ status, member }: { status: number | undef
const update = useSelector((state: any) => state.projectUpdate)
const [idSelect, setIdSelect] = useState('')
const dispatch = useDispatch()
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 3 })
async function handleLoad() {
async function handleLoad(loading: boolean) {
try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current));
const response = await apiGetProjectOne({
user: hasil,
@@ -44,13 +48,20 @@ export default function SectionFile({ status, member }: { status: number | undef
setData(response.data);
} catch (error) {
console.error(error);
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad();
handleLoad(false);
}, [update.file]);
useEffect(() => {
handleLoad(true);
}, []);
async function handleDelete() {
try {
const hasil = await decryptToken(String(token?.current));
@@ -141,21 +152,28 @@ export default function SectionFile({ status, member }: { status: number | undef
<Text style={[Styles.textDefaultSemiBold, Styles.mv05]}>File</Text>
<View style={[Styles.wrapPaper]}>
{
data.length > 0 ?
data.map((item, index) => {
loading ?
arrSkeleton.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="all"
icon={<MaterialCommunityIcons name="file-outline" size={25} color="black" />}
title={item.name + '.' + item.extension}
titleWeight="normal"
onPress={() => { setIdSelect(item.id); setModal(true) }}
/>
<Skeleton key={index} width={100} height={40} widthType="percent" borderRadius={10} />
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada file</Text>
data.length > 0 ?
data.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="all"
icon={<MaterialCommunityIcons name="file-outline" size={25} color="black" />}
title={item.name + '.' + item.extension}
titleWeight="normal"
onPress={() => { setIdSelect(item.id); setModal(true) }}
/>
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada file</Text>
}
</View>
</View>

View File

@@ -12,6 +12,7 @@ import BorderBottomItem from "../borderBottomItem";
import DrawerBottom from "../drawerBottom";
import ImageUser from "../imageNew";
import MenuItemRow from "../menuItemRow";
import SkeletonTwoItem from "../skeletonTwoItem";
type Props = {
id: string;
@@ -30,14 +31,17 @@ export default function SectionMember({ status }: { status: number | undefined }
const { token, decryptToken } = useAuthSession();
const { id } = useLocalSearchParams<{ id: string }>();
const [data, setData] = useState<Props[]>([]);
const [loading, setLoading] = useState(true)
const arrSkeleton = Array.from({ length: 3 })
const [memberChoose, setMemberChoose] = useState({
id: '',
name: '',
})
async function handleLoad() {
async function handleLoad(loading: boolean) {
try {
setLoading(loading)
const hasil = await decryptToken(String(token?.current));
const response = await apiGetProjectOne({
user: hasil,
@@ -47,13 +51,19 @@ export default function SectionMember({ status }: { status: number | undefined }
setData(response.data);
} catch (error) {
console.error(error);
} finally {
setLoading(false)
}
}
useEffect(() => {
handleLoad();
handleLoad(false);
}, [update.member]);
useEffect(() => {
handleLoad(true);
}, []);
async function handleDeleteMember() {
try {
const hasil = await decryptToken(String(token?.current));
@@ -81,28 +91,35 @@ export default function SectionMember({ status }: { status: number | undefined }
<View style={[Styles.wrapPaper]}>
{
data.length > 0
?
data.map((item, index) => {
loading ?
arrSkeleton.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="bottom"
icon={<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />}
title={item.name}
onPress={() => {
if (status == 3) return
setMemberChoose({
id: item.idUser,
name: item.name,
})
setModal(true);
}}
/>
);
<SkeletonTwoItem key={index} />
)
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada anggota</Text>
data.length > 0
?
data.map((item, index) => {
return (
<BorderBottomItem
key={index}
borderType="bottom"
icon={<ImageUser src={`https://wibu-storage.wibudev.com/api/files/${item.img}`} />}
title={item.name}
onPress={() => {
if (status == 3) return
setMemberChoose({
id: item.idUser,
name: item.name,
})
setModal(true);
}}
/>
);
})
:
<Text style={[Styles.textDefault, Styles.cGray, { textAlign: 'center' }]}>Tidak ada anggota</Text>
}
</View>
</View>

View File

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

View File

@@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
import { StyleSheet } from 'react-native';
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);
useEffect(() => {
@@ -25,7 +25,7 @@ const Skeleton = ({ width, height, borderRadius = 0, widthType = 'number' }: { w
style={[
styles.skeleton,
{ 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() {
return (
<View style={[Styles.rowOnly]}>
<Skeleton width={50} height={50} borderRadius={10} />
<Skeleton width={50} height={50} borderRadius={100} />
<View style={[{ flex: 1 }, Styles.ph05]}>
<Skeleton width={100} widthType="percent" height={50} borderRadius={10} />
</View>

View File

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