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

@@ -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>