matiru
Blockchain-Based Supply Chain Transparency for Agricultural Producegit clone https://github.com/maydayv7/matiru
frontend/screens/Farmer.js (11649B)
1import { useContext, useState, useRef } from "react";
2import {
3 Alert,
4 ScrollView,
5 Text,
6 TextInput,
7 TouchableOpacity,
8 View,
9} from "react-native";
10import { SafeAreaView } from "react-native-safe-area-context";
11
12import ScreenHeader from "../components/ScreenHeader";
13import ActionButton from "../components/ActionButton";
14import Scanner from "../components/Scanner";
15import LocationPicker from "../components/LocationPicker";
16import QRModal from "../components/QRModal";
17import ImageUploader from "../components/ImageUploader";
18import DatePicker from "../components/DatePicker";
19import TransferOwnershipForm from "../components/TransferOwnershipForm";
20
21import { api } from "../services/api";
22import styles from "../styles";
23import { AuthContext } from "../AuthContext";
24
25export default function FarmerScreen({ navigation, route }) {
26 const { user } = useContext(AuthContext);
27 const userId = route.params?.userId || user?.id;
28 const token = user?.token;
29
30 const [active, setActive] = useState(null);
31 const imageUploaderRef = useRef(null);
32
33 const [produceId, setProduceId] = useState("");
34 const [location, setLocation] = useState("");
35 const [pricePerUnit, setPricePerUnit] = useState("");
36 const [storageConditions, setStorageConditions] = useState("");
37 const [certification, setCertification] = useState("");
38
39 const [cropType, setCropType] = useState("");
40 const [qty, setQty] = useState("");
41 const [qtyUnit, setQtyUnit] = useState("KG");
42 const [harvestDate, setHarvestDate] = useState("");
43 const [quality, setQuality] = useState("");
44 const [expiryDate, setExpiryDate] = useState("");
45
46 const [splitQty, setSplitQty] = useState("");
47
48 const [qrVisible, setQrVisible] = useState(false);
49 const [lastProduceId, setLastProduceId] = useState(null);
50
51 const resetRegisterForm = () => {
52 setCropType("");
53 setQty("");
54 setQtyUnit("KG");
55 setPricePerUnit("");
56 setHarvestDate("");
57 setQuality("");
58 setExpiryDate("");
59 setStorageConditions("");
60 setCertification("");
61 setLocation("");
62 imageUploaderRef.current?.reset();
63 };
64
65 const resetCommon = () => {
66 setProduceId("");
67 };
68
69 const registerProduce = async () => {
70 try {
71 const imageUrl = await imageUploaderRef.current?.upload();
72 const { produce } = await api.registerProduce(
73 {
74 farmerId: userId,
75 details: {
76 imageUrl,
77 cropType,
78 qty: parseFloat(qty) || 0,
79 qtyUnit,
80 pricePerUnit: parseFloat(pricePerUnit) || 0,
81 harvestDate,
82 quality,
83 expiryDate,
84 storageConditions: storageConditions
85 ? storageConditions.split(",").map((c) => c.trim())
86 : [],
87 certification: certification
88 ? certification.split(",").map((c) => c.trim())
89 : [],
90 location,
91 },
92 },
93 token
94 );
95 setLastProduceId(produce.id);
96 setQrVisible(true);
97 resetRegisterForm();
98 setActive(null);
99 } catch (err) {
100 Alert.alert("Error", err.message);
101 }
102 };
103
104 const updateDetails = async () => {
105 if (!produceId) return Alert.alert("Error", "Please enter Produce ID");
106 try {
107 await api.updateDetails(
108 {
109 produceId,
110 actorId: userId,
111 details: {
112 pricePerUnit: pricePerUnit ? parseFloat(pricePerUnit) : undefined,
113 storageConditions: storageConditions
114 ? storageConditions.split(",").map((c) => c.trim())
115 : undefined,
116 certification: certification
117 ? certification.split(",").map((c) => c.trim())
118 : undefined,
119 },
120 },
121 token
122 );
123 Alert.alert("Updated", "Produce details updated successfully");
124 resetCommon();
125 setPricePerUnit("");
126 setStorageConditions("");
127 setCertification("");
128 setActive(null);
129 } catch (err) {
130 Alert.alert("Error", err.message);
131 }
132 };
133
134 const splitProduce = async () => {
135 if (!produceId || !splitQty)
136 return Alert.alert("Error", "Please enter Produce ID and quantity");
137 try {
138 await api.splitProduce(
139 {
140 produceId,
141 qty: parseFloat(splitQty),
142 ownerId: userId,
143 },
144 token
145 );
146 Alert.alert("Split", "Produce split successfully");
147 setSplitQty("");
148 resetCommon();
149 setActive(null);
150 } catch (err) {
151 Alert.alert("Error", err.message);
152 }
153 };
154
155 const updateLocation = async () => {
156 if (!produceId || !location)
157 return Alert.alert("Error", "Please enter Produce ID and location");
158 try {
159 await api.updateLocation(
160 {
161 produceId,
162 actorId: userId,
163 newLocation: location,
164 },
165 token
166 );
167 Alert.alert("Moved", "Location updated successfully");
168 resetCommon();
169 setLocation("");
170 setActive(null);
171 } catch (err) {
172 Alert.alert("Error", err.message);
173 }
174 };
175
176 const transferOwnership = async ({
177 produceId,
178 newOwnerId,
179 qty,
180 salePrice,
181 onComplete,
182 }) => {
183 if (!produceId || !newOwnerId || !qty)
184 return Alert.alert(
185 "Error",
186 "Please provide Produce ID, New Owner ID, and Quantity."
187 );
188 try {
189 await api.transferOwnership(
190 {
191 produceId,
192 newOwnerId,
193 qty: parseFloat(qty),
194 salePrice: salePrice ? parseFloat(salePrice) : 0,
195 },
196 token
197 );
198 Alert.alert("Transferred", "Ownership transferred successfully");
199 onComplete();
200 setActive(null);
201 } catch (err) {
202 Alert.alert("Error", err.message);
203 }
204 };
205
206 return (
207 <SafeAreaView style={styles.container}>
208 <ScreenHeader
209 title="Farmer Dashboard"
210 navigation={navigation}
211 role="Farmer"
212 />
213 <ScrollView>
214 <View style={styles.actionGrid}>
215 <ActionButton
216 icon="plus"
217 text="Register Produce"
218 onPress={() => setActive("register")}
219 />
220 <ActionButton
221 icon="update"
222 text="Update Details"
223 onPress={() => setActive("update")}
224 />
225 <ActionButton
226 icon="call-split"
227 text="Split Produce"
228 onPress={() => setActive("split")}
229 />
230 <ActionButton
231 icon="map-marker"
232 text="Update Location"
233 onPress={() => setActive("location")}
234 />
235 <ActionButton
236 icon="cash"
237 text="Transfer Ownership"
238 onPress={() => setActive("transfer")}
239 />
240 </View>
241
242 {active === "register" && (
243 <View>
244 <TextInput
245 style={styles.input}
246 placeholder="Crop Type"
247 value={cropType}
248 onChangeText={setCropType}
249 />
250 <TextInput
251 style={styles.input}
252 placeholder="Quantity"
253 keyboardType="numeric"
254 value={qty}
255 onChangeText={setQty}
256 />
257 <TextInput
258 style={styles.input}
259 placeholder="Unit (KG/Number)"
260 value={qtyUnit}
261 onChangeText={setQtyUnit}
262 />
263 <TextInput
264 style={styles.input}
265 placeholder="Price Per Unit"
266 keyboardType="numeric"
267 value={pricePerUnit}
268 onChangeText={setPricePerUnit}
269 />
270 <DatePicker
271 label="Harvest Date"
272 value={harvestDate}
273 onChange={setHarvestDate}
274 />
275 <TextInput
276 style={styles.input}
277 placeholder="Quality"
278 value={quality}
279 onChangeText={setQuality}
280 />
281 <DatePicker
282 label="Expiry Date"
283 value={expiryDate}
284 onChange={setExpiryDate}
285 />
286 <TextInput
287 style={styles.input}
288 placeholder="Storage Conditions (comma separated)"
289 value={storageConditions}
290 onChangeText={setStorageConditions}
291 />
292 <TextInput
293 style={styles.input}
294 placeholder="Certifications (comma separated)"
295 value={certification}
296 onChangeText={setCertification}
297 />
298 <LocationPicker value={location} onChange={setLocation} />
299 <ImageUploader ref={imageUploaderRef} token={token} />
300 <TouchableOpacity
301 style={styles.primaryButton}
302 onPress={registerProduce}
303 >
304 <Text style={styles.buttonText}>Register Produce</Text>
305 </TouchableOpacity>
306 </View>
307 )}
308
309 {active === "update" && (
310 <View>
311 <Scanner value={produceId} onChange={setProduceId} />
312 <TextInput
313 style={styles.input}
314 placeholder="New Price Per Unit"
315 keyboardType="numeric"
316 value={pricePerUnit}
317 onChangeText={setPricePerUnit}
318 />
319 <TextInput
320 style={styles.input}
321 placeholder="Storage Conditions (comma separated)"
322 value={storageConditions}
323 onChangeText={setStorageConditions}
324 />
325 <TextInput
326 style={styles.input}
327 placeholder="Certifications (comma separated)"
328 value={certification}
329 onChangeText={setCertification}
330 />
331 <TouchableOpacity
332 style={styles.primaryButton}
333 onPress={updateDetails}
334 >
335 <Text style={styles.buttonText}>Update Details</Text>
336 </TouchableOpacity>
337 </View>
338 )}
339
340 {active === "split" && (
341 <View>
342 <Scanner value={produceId} onChange={setProduceId} />
343 <TextInput
344 style={styles.input}
345 placeholder="Quantity to Split"
346 keyboardType="numeric"
347 value={splitQty}
348 onChangeText={setSplitQty}
349 />
350 <TouchableOpacity
351 style={styles.primaryButton}
352 onPress={splitProduce}
353 >
354 <Text style={styles.buttonText}>Split Produce</Text>
355 </TouchableOpacity>
356 </View>
357 )}
358
359 {active === "location" && (
360 <View>
361 <Scanner value={produceId} onChange={setProduceId} />
362 <LocationPicker value={location} onChange={setLocation} />
363 <TouchableOpacity
364 style={styles.primaryButton}
365 onPress={updateLocation}
366 >
367 <Text style={styles.buttonText}>Update Location</Text>
368 </TouchableOpacity>
369 </View>
370 )}
371
372 {active === "transfer" && (
373 <TransferOwnershipForm onSubmit={transferOwnership} />
374 )}
375 </ScrollView>
376
377 <QRModal
378 visible={qrVisible}
379 onClose={() => setQrVisible(false)}
380 value={lastProduceId || ""}
381 />
382
383 <View style={{ padding: 12 }}>
384 <TouchableOpacity
385 style={styles.secondaryButton}
386 onPress={() =>
387 navigation.navigate("Inventory", { userId: userId, role: "Farmer" })
388 }
389 >
390 <Text style={styles.secondaryButtonText}>View Inventory</Text>
391 </TouchableOpacity>
392 </View>
393 </SafeAreaView>
394 );
395}