matiru
Blockchain-Based Supply Chain Transparency for Agricultural Producegit clone https://github.com/maydayv7/matiru
frontend/screens/Inventory.js (4488B)
1import { useContext, useEffect, useState, useCallback } from "react";
2import {
3 ActivityIndicator,
4 Alert,
5 RefreshControl,
6 ScrollView,
7 Text,
8 View,
9} from "react-native";
10import { SafeAreaView } from "react-native-safe-area-context";
11import AsyncStorage from "@react-native-async-storage/async-storage";
12
13import ScreenHeader from "../components/ScreenHeader";
14import ProduceCard from "../components/ProduceCard";
15
16import { AuthContext } from "../AuthContext";
17import { api } from "../services/api";
18import styles, { colors } from "../styles";
19
20const sortByLastAction = (a, b) => {
21 const getLatestTimestamp = (produce) => {
22 if (produce.actionHistory && produce.actionHistory.length > 0)
23 return produce.actionHistory[produce.actionHistory.length - 1].timestamp;
24 return produce.harvestDate;
25 };
26
27 const timeA = getLatestTimestamp(a);
28 const timeB = getLatestTimestamp(b);
29
30 return new Date(timeB) - new Date(timeA);
31};
32
33export default function InventoryScreen({ navigation, route }) {
34 const { user } = useContext(AuthContext);
35 const routeUserId = route.params?.userId || user?.id;
36 const role = route.params?.role || user?.role;
37 const [loading, setLoading] = useState(false);
38 const [produces, setProduces] = useState([]);
39 const [refreshing, setRefreshing] = useState(false);
40
41 const CACHE_KEY = `@inventory_${routeUserId}`;
42
43 const loadInventory = useCallback(
44 async (isRefreshing = false) => {
45 if (!isRefreshing) setLoading(true);
46
47 try {
48 const cachedData = await AsyncStorage.getItem(CACHE_KEY);
49 if (cachedData && !isRefreshing) {
50 const parsedData = JSON.parse(cachedData);
51 parsedData.sort(sortByLastAction);
52 setProduces(parsedData);
53 }
54 } catch (e) {
55 console.error("Failed to load cache:", e);
56 }
57
58 try {
59 let fetchedProduces;
60 if (role.toUpperCase() === "INSPECTOR") {
61 const userKey = `INSPECTOR-${routeUserId}`;
62 const { user: userDetails } = await api.getUserDetails(userKey);
63 const inspectedIds = userDetails.inspectedProduce || [];
64 const producePromises = inspectedIds.map((pid) =>
65 api.getProduceById(pid).catch(() => null)
66 );
67 const results = await Promise.all(producePromises);
68 fetchedProduces = results
69 .filter((res) => res && res.produce)
70 .map((res) => res.produce);
71 } else {
72 const { produces: ownerProduces } =
73 await api.getProduceByOwner(routeUserId);
74 fetchedProduces = ownerProduces || [];
75 }
76
77 fetchedProduces.sort(sortByLastAction);
78 setProduces(fetchedProduces);
79 await AsyncStorage.setItem(CACHE_KEY, JSON.stringify(fetchedProduces));
80 } catch (err) {
81 if (!produces.length) Alert.alert("Error", "Failed to fetch inventory");
82 console.error("Network fetch failed:", err.message);
83 } finally {
84 if (!isRefreshing) setLoading(false);
85 }
86 },
87 [role, routeUserId, CACHE_KEY]
88 );
89
90 useEffect(() => {
91 loadInventory();
92 }, [loadInventory]);
93
94 const onRefresh = useCallback(async () => {
95 setRefreshing(true);
96 await loadInventory(true);
97 setRefreshing(false);
98 }, [loadInventory]);
99
100 return (
101 <SafeAreaView style={styles.container}>
102 <ScreenHeader
103 title={route.params?.title || "My Inventory"}
104 navigation={navigation}
105 role={role}
106 hideSearchButton={true}
107 hideNotificationsButton={true}
108 showBack={true}
109 />
110 <ScrollView
111 contentContainerStyle={{ padding: 12 }}
112 refreshControl={
113 <RefreshControl
114 refreshing={refreshing}
115 onRefresh={onRefresh}
116 colors={[colors.darkGreen]}
117 tintColor={colors.darkGreen}
118 />
119 }
120 >
121 {loading && produces.length === 0 ? (
122 <View style={{ alignItems: "center", padding: 20 }}>
123 <ActivityIndicator size="large" />
124 <Text>Loading Inventory...</Text>
125 </View>
126 ) : (
127 <>
128 {produces.length === 0 ? (
129 <View style={{ marginTop: 20, alignItems: "center" }}>
130 <Text style={{ color: colors.gray }}>No produce found</Text>
131 </View>
132 ) : (
133 produces.map((p) => <ProduceCard key={p.id} produce={p} />)
134 )}
135 </>
136 )}
137 </ScrollView>
138 </SafeAreaView>
139 );
140}