amalia/17-apr-26 #37

Merged
amaliadwiy merged 3 commits from amalia/17-apr-26 into join 2026-04-17 17:40:05 +08:00
4 changed files with 151 additions and 98 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,6 +211,11 @@ export default function DetailAnnouncement() {
/> />
} }
> >
{isError && !loading ? (
<View style={[Styles.mv50]}>
<ErrorView />
</View>
) : (
<View style={[Styles.p15, Styles.mb50]}> <View style={[Styles.p15, Styles.mb50]}>
<View style={[Styles.wrapPaper, Styles.borderAll, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}> <View style={[Styles.wrapPaper, Styles.borderAll, { backgroundColor: colors.card, borderColor: colors.icon + '20' }]}>
{ {
@@ -304,6 +314,7 @@ export default function DetailAnnouncement() {
} }
</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>
);
}

View File

@@ -7,10 +7,6 @@ import Text from "../Text";
export default function ReportChartDocument({ data }: { data: { label: string; value: number; }[] }) { export default function ReportChartDocument({ data }: { data: { label: string; value: number; }[] }) {
const { colors } = useTheme(); const { colors } = useTheme();
const maxValue = Math.max(...data.map(i => i.value)) const maxValue = Math.max(...data.map(i => i.value))
const barData = [
{ value: 23, label: 'Gambar', },
{ value: 12, label: 'Dokumen' },
];
const width = Dimensions.get("window").width; const width = Dimensions.get("window").width;
return ( return (

View File

@@ -20,6 +20,7 @@ export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }
const { colors } = useTheme(); const { colors } = useTheme();
const [data, setData] = useState<Props>([]) const [data, setData] = useState<Props>([])
const [maxValue, setMaxValue] = useState(5) const [maxValue, setMaxValue] = useState(5)
const [chartKey, setChartKey] = useState(0)
const barData = [ const barData = [
{ value: 23, label: 'Gambar', frontColor: '#fac858' }, { value: 23, label: 'Gambar', frontColor: '#fac858' },
{ value: 12, label: 'Dokumen', frontColor: '#92cc76' }, { value: 12, label: 'Dokumen', frontColor: '#92cc76' },
@@ -32,8 +33,8 @@ export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }
setLoading(loading) setLoading(loading)
const hasil = await decryptToken(String(token?.current)) const hasil = await decryptToken(String(token?.current))
const response = await apiGetDataHome({ cat: "dokumen", user: hasil }) const response = await apiGetDataHome({ cat: "dokumen", user: hasil })
const maxValue = response.data.reduce((max: number, obj: { value: number; }) => Math.max(max, obj.value), -Infinity); const maxVal = response.data.reduce((max: number, obj: { value: number; }) => Math.max(max, Number(obj.value)), 0);
const roundUp = Math.ceil(maxValue / 10) * 10 const roundUp = maxVal > 0 ? Math.ceil(maxVal / 10) * 10 : 10;
setMaxValue(roundUp) setMaxValue(roundUp)
const convertedArray = response.data.map((item: { color: any; label: any; value: any; }) => ({ const convertedArray = response.data.map((item: { color: any; label: any; value: any; }) => ({
frontColor: item.color, frontColor: item.color,
@@ -41,6 +42,7 @@ export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }
value: Number(item.value) value: Number(item.value)
})); }));
setData(convertedArray) setData(convertedArray)
setChartKey((prev: number) => prev + 1)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {
@@ -49,8 +51,9 @@ export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }
} }
useEffect(() => { useEffect(() => {
if (refreshing) if (refreshing) {
handleData(false) handleData(false)
}
}, [refreshing]); }, [refreshing]);
useEffect(() => { useEffect(() => {
@@ -64,6 +67,7 @@ export default function ChartDokumenHome({ refreshing }: { refreshing: boolean }
loading ? <Skeleton width={100} height={200} borderRadius={10} widthType="percent" /> loading ? <Skeleton width={100} height={200} borderRadius={10} widthType="percent" />
: :
<BarChart <BarChart
key={chartKey}
showFractionalValues={false} showFractionalValues={false}
showYAxisIndices showYAxisIndices
noOfSections={4} noOfSections={4}