Implement program fetching and loading state in Programs component

- Added asynchronous fetching of program activities using getPrograms.
- Introduced loading and error states to enhance user experience.
- Refactored activity rendering to handle dynamic data and improved icon rendering logic.
This commit is contained in:
Mekan1206
2025-09-20 13:21:20 +05:00
parent 803bbfc30f
commit 3ee7ec87be
2 changed files with 120 additions and 63 deletions

View File

@@ -1,60 +1,79 @@
import { useState } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, ScrollView } from 'react-native';
import { StyleSheet, Text, View, TouchableOpacity, ScrollView, ActivityIndicator } from 'react-native';
import Colors from '@/constants/Colors';
import { FontAwesome, FontAwesome5 } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { getPrograms, ProgramActivities, ProgramActivity } from '@/utils/programs';
import { useEffect } from 'react';
type Activity = {
time: string;
title: string;
description: string;
icon: React.ReactNode;
transport?: string;
};
const Icon = ({ iconSet, iconName }: { iconSet: ProgramActivity['iconSet']; iconName: ProgramActivity['iconName'] }) => {
const color = "black";
const size = 24;
type Activities = {
[key: string]: Activity[];
switch (iconSet) {
case 'FontAwesome':
return <FontAwesome name={iconName as any} size={size} color={color} />;
case 'FontAwesome5':
return <FontAwesome5 name={iconName as any} size={size} color={color} />;
default:
return null;
}
};
export default function Programs() {
const colorScheme = 'dark';
const [activeDay, setActiveDay] = useState('Day 1');
const [activeDay, setActiveDay] = useState<string | null>(null);
const insets = useSafeAreaInsets();
const [activities, setActivities] = useState<ProgramActivities>({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const activities: Activities = {
'Day 1': [
{
time: '16:30 - 19:15',
title: 'Depart for Mecca',
description: 'From your location to Mecca',
icon: <FontAwesome name="plane" size={24} color="black" />,
transport: 'Transport to hotel',
},
{
time: '21:00 - 23:00',
title: 'Dinner at Al-Baik',
description: 'Masjid al-',
icon: <FontAwesome name="cutlery" size={24} color="black" />,
},
{
time: '00:00 - 04:00',
title: 'Tawaf',
description: 'Kaaba',
icon: <FontAwesome5 name="kaaba" size={24} color="black" />,
},
],
'Day 2': [
{
time: '10:00 - 12:00',
title: 'Shopping',
description: 'Zamzam Tower',
icon: <FontAwesome name="shopping-bag" size={24} color="black" />,
},
],
'Day 3': [],
'Day 4': [],
useEffect(() => {
const fetchPrograms = async () => {
try {
const data = await getPrograms();
setActivities(data);
if (Object.keys(data).length > 0) {
const today = new Date();
const day = String(today.getDate()).padStart(2, '0');
const month = String(today.getMonth() + 1).padStart(2, '0');
const year = today.getFullYear();
const formattedDate = `${day}.${month}.${year}`;
if (data[formattedDate]) {
setActiveDay(formattedDate);
} else {
setActiveDay(Object.keys(data)[0]);
}
}
} catch (e) {
setError('Failed to load program activities.');
} finally {
setLoading(false);
}
};
fetchPrograms();
}, []);
const dayKeys = Object.keys(activities);
if (loading) {
return (
<View style={[styles.container, styles.center]}>
<ActivityIndicator size="large" color={Colors[colorScheme].text} />
</View>
);
}
if (error) {
return (
<View style={[styles.container, styles.center]}>
<Text style={{ color: Colors[colorScheme].text }}>{error}</Text>
</View>
);
}
return (
<View style={[styles.container, { backgroundColor: Colors[colorScheme].background, paddingTop: insets.top }]}>
<View style={styles.header}>
@@ -63,7 +82,7 @@ export default function Programs() {
<View>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.daysScroll}>
{Object.keys(activities).map((day) => (
{dayKeys.map((day) => (
<TouchableOpacity
key={day}
style={[styles.dayButton, activeDay === day && styles.activeDayButton]}
@@ -76,7 +95,8 @@ export default function Programs() {
</View>
<ScrollView style={styles.content}>
{activities[activeDay].map((activity, index) => (
{activeDay &&
activities[activeDay]?.map((activity, index) => (
<View key={index}>
<View style={styles.activityCard}>
<View style={styles.timeContainer}>
@@ -84,7 +104,9 @@ export default function Programs() {
<Text style={styles.activityTitle}>{activity.title}</Text>
<Text style={styles.activityDescription}>{activity.description}</Text>
</View>
<View style={styles.iconContainer}>{activity.icon}</View>
<View style={styles.iconContainer}>
<Icon iconSet={activity.iconSet} iconName={activity.iconName} />
</View>
</View>
{activity.transport && (
<View style={styles.transportContainer}>
@@ -105,6 +127,10 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
center: {
justifyContent: 'center',
alignItems: 'center',
},
header: {
alignItems: 'center',
paddingVertical: 10,

31
utils/programs.ts Normal file
View File

@@ -0,0 +1,31 @@
import { FontAwesome, FontAwesome5, MaterialCommunityIcons } from '@expo/vector-icons';
export type ProgramActivity = {
time: string;
title: string;
description: string;
iconSet: 'FontAwesome' | 'FontAwesome5' | 'MaterialCommunityIcons';
iconName: React.ComponentProps<typeof FontAwesome>['name'] | React.ComponentProps<typeof FontAwesome5>['name'] | React.ComponentProps<typeof MaterialCommunityIcons>['name'];
transport?: string;
};
export type ProgramActivities = {
[date: string]: ProgramActivity[];
};
const PROGRAMS_URL = 'http://kepilhyzmat.com/assets/programs.json';
export const getPrograms = async (): Promise<ProgramActivities> => {
try {
const response = await fetch(PROGRAMS_URL);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
const data: ProgramActivities = await response.json();
return data;
} catch (error) {
console.error('Failed to fetch programs:', error);
throw error;
}
};