demeter
Autonomous Hydroponic Intelligencegit clone https://github.com/maydayv7/demeter
frontend/src/api/farmApi.jsx (4025B)
1import { USE_MOCK_DATA, MOCK_DASHBOARD, MOCK_HISTORY } from "../data/mockData";
2
3const API_BASE_URL =
4 process.env.REACT_APP_FARM_API_URL || "http://localhost:3001/api";
5
6// MongoDB CRUD
7
8/**
9 * Fetches all crops
10 */
11export const fetchDashboardData = async () => {
12 if (USE_MOCK_DATA) {
13 await new Promise((r) => setTimeout(r, 300));
14 return MOCK_DASHBOARD;
15 }
16
17 try {
18 const res = await fetch(`${API_BASE_URL}/crops/all`);
19 if (!res.ok) throw new Error("Network error");
20 const json = await res.json();
21 // Unwrap { data: [...] } response from the node server
22 return json.data || json || [];
23 } catch {
24 return [];
25 }
26};
27
28/**
29 * Creates a new crop
30 */
31export const createCrop = async (cropData) => {
32 const res = await fetch(`${API_BASE_URL}/crops/create`, {
33 method: "POST",
34 headers: { "Content-Type": "application/json" },
35 body: JSON.stringify(cropData),
36 });
37 if (!res.ok) {
38 const err = await res.json().catch(() => ({}));
39 throw new Error(err.error || "Failed to create crop");
40 }
41 return res.json();
42};
43
44/**
45 * Fetches a single crop document by ID
46 */
47export const fetchCropById = async (cropId) => {
48 if (USE_MOCK_DATA) {
49 await new Promise((r) => setTimeout(r, 150));
50 const found = MOCK_DASHBOARD.find(
51 (d) => (d.crop_id || d._id || d.id) === cropId,
52 );
53 return found || null;
54 }
55
56 try {
57 const res = await fetch(`${API_BASE_URL}/crops/${cropId}`);
58 if (!res.ok) return null;
59 const json = await res.json();
60 return json.data || null;
61 } catch {
62 return null;
63 }
64};
65
66/**
67 * Partially updates a crop
68 */
69export const updateCrop = async (cropId, fields) => {
70 const res = await fetch(`${API_BASE_URL}/crops/${cropId}`, {
71 method: "PUT",
72 headers: { "Content-Type": "application/json" },
73 body: JSON.stringify(fields),
74 });
75 if (!res.ok) {
76 const err = await res.json().catch(() => ({}));
77 throw new Error(err.error || "Failed to update crop");
78 }
79 return res.json();
80};
81
82/**
83 * Deletes a crop
84 */
85export const deleteCrop = async (cropId) => {
86 const res = await fetch(`${API_BASE_URL}/crops/${cropId}`, {
87 method: "DELETE",
88 });
89 if (!res.ok) {
90 const err = await res.json().catch(() => ({}));
91 throw new Error(err.error || "Failed to delete crop");
92 }
93 return res.json();
94};
95
96// Qdrant (history / analytics)
97
98/**
99 * Fetches the full cycle history for a specific crop
100 */
101export const fetchCropDetails = async (cropId) => {
102 if (USE_MOCK_DATA) {
103 await new Promise((r) => setTimeout(r, 200));
104 return MOCK_HISTORY.filter((h) => h.payload?.crop_id === cropId);
105 }
106
107 try {
108 const res = await fetch(`${API_BASE_URL}/crop/${cropId}`);
109 if (!res.ok) throw new Error("Network error");
110 return res.json();
111 } catch {
112 return [];
113 }
114};
115
116/**
117 * Fetches full history for every crop present in the dashboard snapshot
118 * dashboardItems is the raw normalized array (each item has .payload.crop_id or .crop_id)
119 */
120export const fetchAllCropHistories = async (dashboardItems) => {
121 if (USE_MOCK_DATA) {
122 await new Promise((r) => setTimeout(r, 200));
123 return MOCK_HISTORY.sort((a, b) => {
124 const ta = new Date(a.payload?.timestamp || 0).getTime();
125 const tb = new Date(b.payload?.timestamp || 0).getTime();
126 return ta - tb;
127 });
128 }
129
130 if (!dashboardItems?.length) return [];
131
132 // Support both normalized { payload: { crop_id } } and flat { crop_id }
133 const cropIds = [
134 ...new Set(
135 dashboardItems
136 .map((i) => i.payload?.crop_id || i.crop_id)
137 .filter(Boolean),
138 ),
139 ];
140
141 const results = await Promise.allSettled(
142 cropIds.map((id) =>
143 fetch(`${API_BASE_URL}/crop/${id}`)
144 .then((r) => (r.ok ? r.json() : []))
145 .catch(() => []),
146 ),
147 );
148
149 const all = results.flatMap((r) => (r.status === "fulfilled" ? r.value : []));
150 return all.sort((a, b) => {
151 const ta = new Date(a.payload?.timestamp || 0).getTime();
152 const tb = new Date(b.payload?.timestamp || 0).getTime();
153 return ta - tb;
154 });
155};