upd: page kosong atau data udah di hapus pada pengumuman

This commit is contained in:
2026-04-17 16:46:49 +08:00
parent 555b9e4037
commit 772551a917
2 changed files with 144 additions and 91 deletions

View File

@@ -3,6 +3,7 @@ import AppHeader from "@/components/AppHeader";
import BorderBottomItem from "@/components/borderBottomItem"; import BorderBottomItem from "@/components/borderBottomItem";
import Skeleton from "@/components/skeleton"; import Skeleton from "@/components/skeleton";
import Text from '@/components/Text'; import Text from '@/components/Text';
import ErrorView from "@/components/ErrorView";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import { isImageFile } from "@/constants/FileExtensions"; import { isImageFile } from "@/constants/FileExtensions";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
@@ -65,6 +66,7 @@ export default function DetailAnnouncement() {
const [loadingOpen, setLoadingOpen] = useState(false) const [loadingOpen, setLoadingOpen] = useState(false)
const [preview, setPreview] = useState(false) const [preview, setPreview] = useState(false)
const [chooseFile, setChooseFile] = useState<FileData>() const [chooseFile, setChooseFile] = useState<FileData>()
const [isError, setIsError] = useState(false)
/** /**
* Opens the image preview modal for the selected image file * Opens the image preview modal for the selected image file
@@ -79,6 +81,7 @@ export default function DetailAnnouncement() {
async function handleLoad(loading: boolean) { async function handleLoad(loading: boolean) {
try { try {
setIsError(false)
setLoading(loading) setLoading(loading)
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
const response: ApiResponse = await apiGetAnnouncementOne({ id: id, user: hasil }) const response: ApiResponse = await apiGetAnnouncementOne({ id: id, user: hasil })
@@ -87,10 +90,12 @@ export default function DetailAnnouncement() {
setDataMember(response.member) setDataMember(response.member)
setDataFile(response.file) setDataFile(response.file)
} else { } else {
setIsError(true)
Toast.show({ type: 'small', text1: response.message }) Toast.show({ type: 'small', text1: response.message })
} }
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
setIsError(true)
const message = error?.response?.data?.message || "Gagal mengambil data" const message = error?.response?.data?.message || "Gagal mengambil data"
Toast.show({ type: 'small', text1: message }) Toast.show({ type: 'small', text1: message })
@@ -206,104 +211,110 @@ export default function DetailAnnouncement() {
/> />
} }
> >
<View style={[Styles.p15, Styles.mb50]}> {isError && !loading ? (
<View style={[Styles.wrapPaper, Styles.borderAll, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}> <View style={[Styles.mv50]}>
{ <ErrorView />
loading ?
<View>
<View style={[Styles.rowOnly]}>
<Skeleton width={30} height={30} borderRadius={10} />
<View style={[Styles.flex1, 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.rowOnly, Styles.alignStart]}>
<MaterialIcons name="campaign" size={25} color={colors.text} style={[Styles.mr05]} />
<Text style={[Styles.textDefaultSemiBold, Styles.w90, Styles.mt02]}>{data?.title}</Text>
</View>
<View style={[Styles.mt10]}>
{
hasHtmlTags(data?.desc) ?
<RenderHTML
contentWidth={contentWidth}
source={{ html: data?.desc }}
baseStyle={{ color: colors.text }}
/>
:
<Text>{data?.desc}</Text>
}
</View>
</>
}
</View> </View>
{ ) : (
dataFile.length > 0 && ( <View style={[Styles.p15, Styles.mb50]}>
<View style={[Styles.wrapPaper, Styles.borderAll, Styles.mt10, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}> <View style={[Styles.wrapPaper, Styles.borderAll, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
<View style={[Styles.mb05]}> {
<Text style={[Styles.textDefaultSemiBold]}>File</Text> loading ?
</View> <View>
{dataFile.map((item, index) => ( <View style={[Styles.rowOnly]}>
<BorderBottomItem <Skeleton width={30} height={30} borderRadius={10} />
key={`${item.id}-${index}`} <View style={[Styles.flex1, Styles.ph05]}>
borderType={index === dataFile.length - 1 ? 'none' : 'bottom'} <Skeleton width={100} widthType="percent" height={30} borderRadius={10} />
icon={<MaterialCommunityIcons </View>
name={isImageFile(item.extension) ? "file-image-outline" : "file-document-outline"}
size={25}
color={colors.text}
/>}
title={item.name + '.' + item.extension}
titleWeight="normal"
onPress={() => {
isImageFile(item.extension) ?
handleChooseFile(item)
: openFile(item)
}}
/>
))}
</View>
)
}
<View style={[Styles.wrapPaper, Styles.borderAll, Styles.mt10, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
{
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> </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} />
Object.keys(dataMember).map((v: any, i: any) => { </View>
return ( :
<View key={i} style={[Styles.mb05]}> <>
<Text style={[Styles.textDefaultSemiBold]}>{dataMember[v]?.[0].group}</Text> <View style={[Styles.rowOnly, Styles.alignStart]}>
<MaterialIcons name="campaign" size={25} color={colors.text} style={[Styles.mr05]} />
<Text style={[Styles.textDefaultSemiBold, Styles.w90, Styles.mt02]}>{data?.title}</Text>
</View>
<View style={[Styles.mt10]}>
{ {
dataMember[v].map((item: any, x: any) => { hasHtmlTags(data?.desc) ?
return ( <RenderHTML
<View key={x} style={[Styles.rowItemsCenter, Styles.w90]}> contentWidth={contentWidth}
<Entypo name="dot-single" size={24} color={colors.text} /> source={{ html: data?.desc }}
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{item.division}</Text> baseStyle={{ color: colors.text }}
</View> />
) :
}) <Text>{data?.desc}</Text>
} }
</View> </View>
) </>
}) }
</View>
{
dataFile.length > 0 && (
<View style={[Styles.wrapPaper, Styles.borderAll, Styles.mt10, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
<View style={[Styles.mb05]}>
<Text style={[Styles.textDefaultSemiBold]}>File</Text>
</View>
{dataFile.map((item, index) => (
<BorderBottomItem
key={`${item.id}-${index}`}
borderType={index === dataFile.length - 1 ? 'none' : 'bottom'}
icon={<MaterialCommunityIcons
name={isImageFile(item.extension) ? "file-image-outline" : "file-document-outline"}
size={25}
color={colors.text}
/>}
title={item.name + '.' + item.extension}
titleWeight="normal"
onPress={() => {
isImageFile(item.extension) ?
handleChooseFile(item)
: openFile(item)
}}
/>
))}
</View>
)
} }
<View style={[Styles.wrapPaper, Styles.borderAll, Styles.mt10, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
{
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={colors.text} />
<Text style={[Styles.textDefault]} numberOfLines={1} ellipsizeMode='tail'>{item.division}</Text>
</View>
)
})
}
</View>
)
})
}
</View>
</View> </View>
</View> )}
</ScrollView> </ScrollView>
<ImageViewing <ImageViewing

42
components/ErrorView.tsx Normal file
View File

@@ -0,0 +1,42 @@
import Text from '@/components/Text';
import Styles from '@/constants/Styles';
import { useTheme } from '@/providers/ThemeProvider';
import { MaterialCommunityIcons } from "@expo/vector-icons";
import React from 'react';
import { View } from 'react-native';
interface ErrorViewProps {
title?: string;
message?: string;
icon?: keyof typeof MaterialCommunityIcons.glyphMap;
}
/**
* ErrorView component to display error or empty states.
* Used when data is not found, deleted, or an error occurs during fetching.
*/
export default function ErrorView({
title = "Terjadi Kesalahan",
message = "Data tidak ditemukan atau sudah dihapus.",
icon = "alert-circle-outline"
}: ErrorViewProps) {
const { colors } = useTheme();
return (
<View style={[Styles.flex1, Styles.contentItemCenter, Styles.ph20]}>
<View style={[Styles.mb10]}>
<MaterialCommunityIcons
name={icon}
size={40}
color={colors.dimmed}
/>
</View>
<Text style={[Styles.textDefaultSemiBold, { color: colors.text, textAlign: 'center' }]}>
{title}
</Text>
<Text style={[Styles.textMediumNormal, { color: colors.dimmed, textAlign: 'center', marginTop: 4 }]}>
{message}
</Text>
</View>
);
}