matiru

Blockchain-Based Supply Chain Transparency for Agricultural Produce

frontend/screens/Notifications.js (5252B)


  1import { useState, useEffect, useContext, useCallback } from "react";
  2import {
  3  ActivityIndicator,
  4  Alert,
  5  FlatList,
  6  RefreshControl,
  7  StyleSheet,
  8  Text,
  9  View,
 10} from "react-native";
 11import { SafeAreaView } from "react-native-safe-area-context";
 12import AsyncStorage from "@react-native-async-storage/async-storage";
 13import { MaterialCommunityIcons } from "@expo/vector-icons";
 14
 15import ScreenHeader from "../components/ScreenHeader";
 16import { api } from "../services/api";
 17import { AuthContext } from "../AuthContext";
 18import styles, { colors } from "../styles";
 19
 20export default function NotificationsScreen({ navigation }) {
 21  const { user } = useContext(AuthContext);
 22  const [notifications, setNotifications] = useState([]);
 23  const [loading, setLoading] = useState(true);
 24  const [refreshing, setRefreshing] = useState(false);
 25
 26  const CACHE_KEY = `@notifications_${user?.id}`;
 27
 28  const loadNotifications = useCallback(
 29    async (isRefreshing = false) => {
 30      if (!user?.token) return;
 31      if (!isRefreshing) setLoading(true);
 32
 33      try {
 34        const cachedData = await AsyncStorage.getItem(CACHE_KEY);
 35        if (cachedData && !isRefreshing) {
 36          const parsedData = JSON.parse(cachedData);
 37          parsedData.sort(sortByLastAction);
 38          setProduces(parsedData);
 39        }
 40      } catch (e) {
 41        console.error("Failed to load cache:", e);
 42      }
 43
 44      try {
 45        const data = await api.getNotifications(user.token);
 46        const sorted = (data.notifications || []).sort(
 47          (a, b) => new Date(b.date) - new Date(a.date)
 48        );
 49        setNotifications(sorted);
 50        await AsyncStorage.setItem(CACHE_KEY, JSON.stringify(sorted));
 51      } catch (err) {
 52        if (notifications.length === 0)
 53          Alert.alert("Error", "Failed to fetch new notifications");
 54        console.error("Network fetch failed:", err.message);
 55      } finally {
 56        if (!isRefreshing) setLoading(false);
 57      }
 58    },
 59    [user?.token, notifications.length, CACHE_KEY]
 60  );
 61
 62  useEffect(() => {
 63    setLoading(true);
 64    loadNotifications().finally(() => setLoading(false));
 65  }, [loadNotifications]);
 66
 67  const onRefresh = useCallback(async () => {
 68    setRefreshing(true);
 69    await loadNotifications(true);
 70    setRefreshing(false);
 71  }, [loadNotifications]);
 72
 73  const getIconForTitle = (title) => {
 74    if (title.toLowerCase().includes("registered")) return "sprout";
 75    if (title.toLowerCase().includes("location")) return "map-marker-check";
 76    if (title.toLowerCase().includes("inspected")) return "shield-search";
 77    if (title.toLowerCase().includes("received")) return "package-down";
 78    if (title.toLowerCase().includes("transferred")) return "package-up";
 79    if (title.toLowerCase().includes("updated")) return "pencil";
 80    if (title.toLowerCase().includes("split")) return "call-split";
 81    return "bell";
 82  };
 83
 84  const renderItem = ({ item }) => (
 85    <View style={localStyles.card}>
 86      <MaterialCommunityIcons
 87        name={getIconForTitle(item.title)}
 88        size={24}
 89        color={colors.darkGreen}
 90        style={localStyles.icon}
 91      />
 92      <View style={localStyles.content}>
 93        <Text style={localStyles.title}>{item.title}</Text>
 94        <Text style={localStyles.message}>{item.message}</Text>
 95        <Text style={localStyles.date}>
 96          {new Date(item.date).toLocaleString()}
 97        </Text>
 98      </View>
 99    </View>
100  );
101
102  return (
103    <SafeAreaView style={styles.container}>
104      <ScreenHeader
105        title="Notifications"
106        navigation={navigation}
107        showBack={true}
108        hideSearchButton={true}
109        hideNotificationsButton={true}
110      />
111      {loading && notifications.length === 0 ? (
112        <ActivityIndicator
113          size="large"
114          color={colors.darkGreen}
115          style={{ marginTop: 50 }}
116        />
117      ) : (
118        <FlatList
119          data={notifications}
120          renderItem={renderItem}
121          keyExtractor={(item) => item.id}
122          ListEmptyComponent={
123            <Text style={localStyles.emptyText}>No notifications found</Text>
124          }
125          contentContainerStyle={{ paddingHorizontal: 16 }}
126          refreshControl={
127            <RefreshControl
128              refreshing={refreshing}
129              onRefresh={onRefresh}
130              colors={[colors.darkGreen]}
131              tintColor={colors.darkGreen}
132            />
133          }
134        />
135      )}
136    </SafeAreaView>
137  );
138}
139
140const localStyles = StyleSheet.create({
141  card: {
142    backgroundColor: "white",
143    borderRadius: 12,
144    padding: 16,
145    marginBottom: 12,
146    flexDirection: "row",
147    alignItems: "flex-start",
148    shadowColor: "#000",
149    shadowOpacity: 0.08,
150    shadowOffset: { width: 0, height: 2 },
151    shadowRadius: 5,
152    elevation: 3,
153  },
154  icon: {
155    marginRight: 16,
156    marginTop: 2,
157  },
158  content: {
159    flex: 1,
160  },
161  title: {
162    fontSize: 16,
163    fontWeight: "bold",
164    color: colors.darkGreen,
165  },
166  message: {
167    fontSize: 14,
168    color: colors.gray,
169    marginTop: 4,
170    lineHeight: 20,
171  },
172  date: {
173    fontSize: 12,
174    color: "#999",
175    marginTop: 8,
176    textAlign: "right",
177  },
178  emptyText: {
179    textAlign: "center",
180    marginTop: 50,
181    fontSize: 16,
182    color: colors.gray,
183  },
184});