Skip to content

Commit c8c2747

Browse files
Merge pull request #18 from AuthEceSoftEng/staging
Staging
2 parents 9c379c4 + f0a479e commit c8c2747

24 files changed

+2107
-1193
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"homepage": "",
77
"repository": "github:AuthEceSoftEng/ecoready-dashboard-frontend",
88
"scripts": {
9-
"build": "cross-env NODE_OPTIONS='--max-old-space-size=2048' CHOKIDAR_USEPOLLING=true craco build",
9+
"build": "cross-env NODE_OPTIONS='--max-old-space-size=4096' CHOKIDAR_USEPOLLING=true craco build",
1010
"lint": "eslint . --cache",
1111
"serve": "serve -s -l 3050 build",
12-
"start": "cross-env NODE_OPTIONS='--max-old-space-size=2048' CHOKIDAR_USEPOLLING=true craco start",
12+
"start": "cross-env NODE_OPTIONS='--max-old-space-size=4096' CHOKIDAR_USEPOLLING=true craco start",
1313
"test": "npm run lint"
1414
},
1515
"browserslist": {

src/_colors.scss

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $theme: (
2020
'choropleth4': #de2d26,
2121
'choropleth5': #a50f15,
2222

23-
// Agriculture colors complementing primary (dark blues/teals)
23+
// Agriculture colors complementing primary (dark blues/teals)
2424
'ag1': #003F5C,
2525
'ag2': #2F4B7C,
2626
'ag3': #1E6091,
@@ -40,8 +40,25 @@ $theme: (
4040
'ag17': #414833,
4141
'ag18': #FF7C43,
4242
'ag19': #FFA07A,
43-
'ag20': #665191,
43+
'ag20': #665191,
44+
45+
// Risk Level Colors
46+
'riskVeryLow': #4CAF50,
47+
'riskLow': #8BC34A,
48+
'riskMedium': #FF9800,
49+
'riskHigh': #f45c36ff,
50+
'riskVeryHigh': #e10202ff,
51+
52+
// Opportunity Level Colors
53+
'opportunityNo': #9E9E9E,
54+
'opportunityLow': #4DB6AC,
55+
'opportunityMedium': #26A69A,
56+
'opportunityHigh': #00695C,
57+
58+
// No Data Color
59+
'noData': #757575,
4460
);
61+
4562
:export {
4663
@each $key, $value in $theme {
4764
#{unquote($key)}: $value;

src/api/fetch-data.js

Lines changed: 148 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
import { getCollectionData, getCollectionDataStatistics, getCollections } from "./index.js";
22

3+
// Request deduplication map
4+
const pendingRequests = new Map();
5+
36
export const fetchCollections = async (dispatch, organization, project) => {
7+
const requestKey = `collections_${organization}_${project}`;
8+
9+
// Check for pending request
10+
if (pendingRequests.has(requestKey)) {
11+
return pendingRequests.get(requestKey);
12+
}
13+
414
try {
5-
const response = await getCollections(organization, project);
15+
const requestPromise = getCollections(organization, project)
16+
.then((response) => {
17+
if (response.success === false) {
18+
dispatch({
19+
type: "FETCH_ERROR",
20+
payload: { response },
21+
error: "No collections found",
22+
});
23+
console.warn("Warning: Collections fetched are empty.");
24+
} else {
25+
dispatch({
26+
type: "FETCH_SUCCESS",
27+
payload: { response },
28+
});
29+
console.log("Collections fetched:", response);
30+
}
631

7-
if (response.success === false) {
8-
dispatch({
9-
type: "FETCH_ERROR",
10-
payload: { response },
11-
error: "No collections found",
32+
return { response };
33+
})
34+
.finally(() => {
35+
pendingRequests.delete(requestKey);
1236
});
13-
console.warn("Warning: Collections fetched are empty.");
14-
} else {
15-
dispatch({
16-
type: "FETCH_SUCCESS",
17-
payload: { response },
18-
});
19-
console.log("Collections fetched:", response);
20-
}
2137

22-
return { response };
38+
pendingRequests.set(requestKey, requestPromise);
39+
return await requestPromise;
2340
} catch (error) {
41+
pendingRequests.delete(requestKey);
2442
dispatch({
2543
type: "FETCH_ERROR",
2644
payload: { error },
@@ -29,55 +47,136 @@ export const fetchCollections = async (dispatch, organization, project) => {
2947
}
3048
};
3149

32-
export const fetchData = async (dispatch, organization, project, collection, params, plotId, type = "data") => {
50+
// Optimized fetchData that returns structured results without dispatching
51+
export const fetchData = async (organization, project, collection, params, plotId, type = "data") => {
52+
const requestKey = `${type}_${organization}_${project}_${collection}_${plotId}_${JSON.stringify(params)}`;
53+
54+
// Check for pending request
55+
if (pendingRequests.has(requestKey)) {
56+
return pendingRequests.get(requestKey);
57+
}
58+
3359
try {
34-
const response = await (type === "stats"
60+
const requestPromise = (type === "stats"
3561
? getCollectionDataStatistics(organization, project, collection, params)
36-
: getCollectionData(organization, project, collection, params));
62+
: getCollectionData(organization, project, collection, params))
63+
.then((response) => {
64+
const isEmpty = (Array.isArray(response) && response.length === 0) || response.success === false;
65+
66+
return {
67+
plotId,
68+
response: isEmpty ? [] : response,
69+
isEmpty,
70+
warning: isEmpty ? `No data available for ${plotId}` : null,
71+
type: plotId.toLowerCase().includes("price") ? "price"
72+
: plotId.toLowerCase().includes("production") ? "production"
73+
: "general",
74+
};
75+
})
76+
.catch((error) => ({
77+
plotId,
78+
error: error.message || "Unknown error",
79+
hasError: true,
80+
type: plotId.toLowerCase().includes("price") ? "price"
81+
: plotId.toLowerCase().includes("production") ? "production"
82+
: "general",
83+
}))
84+
.finally(() => {
85+
pendingRequests.delete(requestKey);
86+
});
87+
88+
pendingRequests.set(requestKey, requestPromise);
89+
return await requestPromise;
90+
} catch (error) {
91+
pendingRequests.delete(requestKey);
92+
return {
93+
plotId,
94+
error: error.message || "Unknown error",
95+
hasError: true,
96+
type: plotId.toLowerCase().includes("price") ? "price"
97+
: plotId.toLowerCase().includes("production") ? "production"
98+
: "general",
99+
};
100+
}
101+
};
102+
103+
// Optimized batch fetching with better error handling
104+
const fetchAllData = async (dispatch, organization, fetchConfigs) => {
105+
if (!Array.isArray(fetchConfigs)) {
106+
throw new TypeError("fetchConfigs should be an array");
107+
}
37108

38-
if ((Array.isArray(response) && response.length === 0) || response.success === false) {
109+
// Group configs by type for optimized dispatching
110+
const configsByType = fetchConfigs.reduce((acc, config) => {
111+
const type = config.plotId.toLowerCase().includes("price") ? "price"
112+
: config.plotId.toLowerCase().includes("production") ? "production"
113+
: "general";
114+
115+
if (!acc[type]) acc[type] = [];
116+
acc[type].push(config);
117+
return acc;
118+
}, {});
119+
120+
// Process each type separately for better performance
121+
const processTypeGroup = async (configs, dataType) => {
122+
const promises = configs.map(
123+
({ project, collection, params, plotId, type }) => fetchData(organization, project, collection, params, plotId, type),
124+
);
125+
126+
const results = await Promise.allSettled(promises);
127+
return results.map((result, index) => ({
128+
...result.value,
129+
dataType,
130+
config: configs[index],
131+
}));
132+
};
133+
134+
try {
135+
// Process all types in parallel
136+
const typeResults = await Promise.all([
137+
configsByType.price ? processTypeGroup(configsByType.price, "price") : [],
138+
configsByType.production ? processTypeGroup(configsByType.production, "production") : [],
139+
configsByType.general ? processTypeGroup(configsByType.general, "general") : [],
140+
]);
141+
142+
// Flatten results
143+
const allResults = typeResults.flat();
144+
145+
// Group results by status and type
146+
const successResults = allResults.filter((r) => !r.hasError && !r.isEmpty);
147+
const warningResults = allResults.filter((r) => !r.hasError && r.isEmpty);
148+
const errorResults = allResults.filter((r) => r.hasError);
149+
150+
// Batch dispatch by type
151+
if (successResults.length > 0) {
152+
dispatch({
153+
type: "FETCH_SUCCESS_BATCH",
154+
payload: { results: successResults },
155+
});
156+
}
157+
158+
if (warningResults.length > 0) {
39159
dispatch({
40-
type: "FETCH_WARNING",
41-
payload: { plotId, response },
42-
warning: "Some values may be missing",
160+
type: "FETCH_WARNING_BATCH",
161+
payload: { results: warningResults },
43162
});
44-
console.warn(`Warning: Data fetched for plot ${plotId} is empty.`);
45-
} else {
163+
}
164+
165+
if (errorResults.length > 0) {
46166
dispatch({
47-
type: "FETCH_SUCCESS",
48-
payload: { plotId, response },
167+
type: "FETCH_ERROR_BATCH",
168+
payload: { results: errorResults },
49169
});
50170
}
51171

52-
return { response };
172+
return allResults;
53173
} catch (error) {
54174
dispatch({
55175
type: "FETCH_ERROR",
56-
payload: { plotId, error },
176+
payload: { error: error.message },
57177
});
58178
throw error;
59179
}
60180
};
61181

62-
const fetchAllData = (dispatch, organization, fetchConfigs) => {
63-
if (!Array.isArray(fetchConfigs)) {
64-
throw new TypeError("fetchConfigs should be an array");
65-
}
66-
67-
// Create an array of promises for each fetch operation
68-
const promises = fetchConfigs.map(
69-
({ project, collection, params, plotId, type }) => fetchData(
70-
dispatch,
71-
organization,
72-
project,
73-
collection,
74-
params,
75-
plotId,
76-
type,
77-
),
78-
);
79-
80-
return Promise.all(promises);
81-
};
82-
83182
export default fetchAllData;

src/components/Card.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ const Card = ({
3737
boxSizing: "border-box", // Ensure padding is included in the width
3838
overflow: "hidden", // Prevent content overflow
3939
borderRadius,
40-
// New interactive styles
4140
cursor: clickable ? "pointer" : "default",
4241
transition: "all 0.2s ease-in-out",
4342
boxShadow: transparent ? elevation : null, // Add shadow based on elevation
@@ -60,6 +59,7 @@ const Card = ({
6059
alignItems="center"
6160
boxSizing="border-box" // Ensure padding is included in the width
6261
borderRadius={borderRadius}
62+
sx={{ flexShrink: 0 }} // Prevent title from shrinking
6363
>
6464
{typeof title === "string" ? (
6565
<Typography variant="body" component="h2" sx={{ fontWeight: "bold", fontSize: titleFontSize }}>
@@ -90,6 +90,7 @@ const Card = ({
9090
justifyContent="space-between"
9191
alignItems="center"
9292
boxSizing="border-box" // Ensure padding is included in the width
93+
sx={{ flexShrink: 0 }} // Prevent footer from shrinking
9394
>
9495
{typeof footer === "string" ? (
9596
<Typography variant="h6" component="h2" align="left" fontSize={footerFontSize}>

src/components/Dropdown.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ const Dropdown = ({
231231
...item.options.map((option, optIndex) => (
232232
<MenuItem
233233
key={`item-${index}-${optIndex}`}
234-
value={option}
234+
value={option.value} // Use option.value instead of option
235235
sx={{
236236
pl: 4,
237237
backgroundColor: "rgba(0, 0, 0, 0.02)",
@@ -249,7 +249,7 @@ const Dropdown = ({
249249
/>
250250
)}
251251
<ListItemText
252-
primary={option}
252+
primary={option.text} // Use option.text for display
253253
sx={{
254254
"& .MuiListItemText-primary": {
255255
overflow: "hidden",

src/components/Header.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ const Header = ({ isAuthenticated }) => {
170170
productName: product, // Store the product name
171171
})));
172172

173-
return [...productEntries, ...mapEntries, ...subProductMapEntries, ...labEntries]; //...productionEntries, ...priceEntries,
173+
return [...productEntries, ...mapEntries, ...subProductMapEntries, ...labEntries]; // ...productionEntries, ...priceEntries,
174174
}, []); // Memoize to avoid unnecessary recalculations
175175

176176
// const isMenuOpenServices = Boolean(anchorElServices);
@@ -296,13 +296,26 @@ const Header = ({ isAuthenticated }) => {
296296
let text = capitalize(path);
297297
// eslint-disable-next-line no-continue
298298
if (path === "home") continue;
299-
switch (path) {
300-
case "file-upload": {
301-
text = "File Upload";
302-
break;
303-
}
304299

305-
default:
300+
// Check if the current full path matches any lab path
301+
const currentPath = `../${pathnames.slice(0, ind + 1).join("/")}`;
302+
const matchingLab = labs.find((lab) => lab.path === currentPath);
303+
if (matchingLab) {
304+
text = `Living Labs / ${matchingLab.title}`;
305+
} else {
306+
switch (path) {
307+
case "file-upload": {
308+
text = "File Upload";
309+
break;
310+
}
311+
312+
case "socialimpact": {
313+
text = "Social Impact";
314+
break;
315+
}
316+
317+
default:
318+
}
306319
}
307320

308321
crumps.push(<CrumpLink to={`/${pathnames.slice(0, ind + 1).join("/")}`}>{text}</CrumpLink>);

0 commit comments

Comments
 (0)