From ee053bcbcc8fc97ecfc1bd70b4b1b5d1ccea4ff8 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 4 Dec 2024 16:14:51 +0100 Subject: [PATCH 1/3] wip --- .../nutrition/useRobotoffPredicitions.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/pages/nutrition/useRobotoffPredicitions.ts b/src/pages/nutrition/useRobotoffPredicitions.ts index 8c94dd41f..7828c0268 100644 --- a/src/pages/nutrition/useRobotoffPredicitions.ts +++ b/src/pages/nutrition/useRobotoffPredicitions.ts @@ -1,11 +1,15 @@ import * as React from "react"; import robotoff from "../../robotoff"; import { InsightType } from "./insight.types"; +import axios from "axios"; export function useRobotoffPredicitions() { const [isLoading, setIsLoading] = React.useState(false); const [count, setCount] = React.useState(0); const [insights, setInsights] = React.useState([]); + const [offData, setOffData] = React.useState<{ + [barecode: string]: "loading" | object; + }>({}); const [insightIndex, setInsightIndex] = React.useState(0); React.useEffect(() => { @@ -33,6 +37,23 @@ export function useRobotoffPredicitions() { }; }, [insightIndex, insights]); + React.useEffect(() => { + const barecodeToImport = insights + .slice(insightIndex, insightIndex + 5) + .filter((insight) => offData[insight.barcode] === undefined) + .map((insight) => insight.barcode); + + barecodeToImport.forEach((code) => { + setOffData((prev) => ({ ...prev, [code]: "loading" })); + axios + .get( + `https://world.openfoodfacts.org/api/v2/product/${code}.json?fields=serving_size,nutriments`, + ) + .then(({ data: { product } }) => { + setOffData((prev) => ({ ...prev, [code]: product })); + }); + }); + }, [insightIndex, insights]); const nextItem = React.useCallback(() => { setInsightIndex((p) => p + 1); @@ -41,10 +62,14 @@ export function useRobotoffPredicitions() { const insight = insights[insightIndex]; + console.log(offData); + const product = insight && offData[insight.barcode]; + return { isLoading, insight, nextItem, count, + product, }; } From 07455cbb878cf329c84d9fc594828f8453e2af09 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 6 Dec 2024 01:04:51 +0100 Subject: [PATCH 2/3] vite config --- vite.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vite.config.js b/vite.config.js index 081c8d9f6..654265249 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,4 +3,7 @@ import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], + optimizeDeps: { + exclude: ["js-big-decimal"], + }, }); From c89b09bb55fb604475384d05619a994b1b62cb2b Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 6 Dec 2024 02:15:01 +0100 Subject: [PATCH 3/3] show data --- src/pages/nutrition/NutrimentCell.tsx | 52 +++- src/pages/nutrition/index.tsx | 267 ++++++++++++------ .../nutrition/useRobotoffPredicitions.ts | 47 ++- src/pages/nutrition/utils.ts | 7 + 4 files changed, 261 insertions(+), 112 deletions(-) diff --git a/src/pages/nutrition/NutrimentCell.tsx b/src/pages/nutrition/NutrimentCell.tsx index 88c836faa..696a37261 100644 --- a/src/pages/nutrition/NutrimentCell.tsx +++ b/src/pages/nutrition/NutrimentCell.tsx @@ -8,11 +8,24 @@ interface NutrimentCellProps { tabIndex: number; value?: string; unit?: string; + productValue?: number; + productUnit?: string; + displayOFFValue: boolean; setValues: (object) => void; } export const NutrimentCell = (props: NutrimentCellProps) => { - const { nutrimentId, nutrimentKey, tabIndex, value, unit, setValues } = props; + const { + nutrimentId, + nutrimentKey, + tabIndex, + value, + unit, + setValues, + productValue, + productUnit, + displayOFFValue, + } = props; return ( { element.classList.remove("focused"); }} > - - setValues((p) => ({ - ...p, - [nutrimentKey]: { - ...p[nutrimentKey], - value: event.target.value, - }, - })) - } - /> +
+ + setValues((p) => ({ + ...p, + [nutrimentKey]: { + ...p[nutrimentKey], + value: event.target.value, + }, + })) + } + /> +
+ {displayOFFValue && ( + + {productValue} + {productUnit} + + )} +
{isValidUnit(unit) ? ( - - - {nutrimentsDetected.map((nutrimentId) => { - const key100g = `${nutrimentId}_100g`; - const { value: value100g, unit: unit100g } = - values[key100g] ?? {}; + + + Nutriments + 100g + + serving{" "} +
+ + setValues((p) => ({ + ...p, + serving_size: { + ...p.serving_size, + value: event.target.value, + }, + })) + } + style={{ width: 100 }} + /> +
+ {displayOFFValue && ( + + {product?.serving_size} + + )} +
+ + + + + {nutrimentsDetected.map((nutrimentId) => { + const key100g = `${nutrimentId}_100g`; + const { value: value100g, unit: unit100g } = + values[key100g] ?? {}; + + const keyServing = `${nutrimentId}_serving`; + const { value: valueServing, unit: unitServing } = + values[keyServing] ?? {}; - const keyServing = `${nutrimentId}_serving`; - const { value: valueServing, unit: unitServing } = - values[keyServing] ?? {}; + const product100g = product?.nutriments?.[key100g]; + const productServing = product?.nutriments?.[keyServing]; + const productUnit = + product?.nutriments?.[`${nutrimentId}_unit`]; - return ( - - - {NUTRIMENTS[nutrimentId] ?? nutrimentId} - - + + {NUTRIMENTS[nutrimentId] ?? nutrimentId} + + + + + ); + })} + + + + + + + + + - - - - - + variant="contained" + color="success" + sx={{ ml: 1, mt: 2 }} + onClick={() => { + postRobotoff({ + insightId: insight.id, + data: values, + type: "serving", + }); + nextItem(); + apiRef.current.resetTransform(); + }} + > + Valider (serving) + + + + diff --git a/src/pages/nutrition/useRobotoffPredicitions.ts b/src/pages/nutrition/useRobotoffPredicitions.ts index cc11380b9..a92092c83 100644 --- a/src/pages/nutrition/useRobotoffPredicitions.ts +++ b/src/pages/nutrition/useRobotoffPredicitions.ts @@ -3,15 +3,21 @@ import robotoff from "../../robotoff"; import { InsightType } from "./insight.types"; import axios from "axios"; -export function useRobotoffPredicitions() { +export function useRobotoffPredicitions(partiallyFilled: boolean) { const [isLoading, setIsLoading] = React.useState(false); const [count, setCount] = React.useState(0); const [insights, setInsights] = React.useState([]); const [offData, setOffData] = React.useState<{ - [barecode: string]: "loading" | object; + [barecode: string]: + | "loading" + | { images: Record; serving_size?: any; nutriments?: any }; }>({}); const [insightIndex, setInsightIndex] = React.useState(0); + const campaign = partiallyFilled + ? "incomplete-nutrition" + : "missing-nutrition"; + React.useEffect(() => { if (isLoading || insightIndex < insights.length - 1) { return; @@ -27,13 +33,14 @@ export function useRobotoffPredicitions() { "not_annotated", 1, 25, - "missing-nutrition", + campaign, ) .then(({ data }) => { if (!valid) { return; } + console.log({ campaign }); setCount(data.count); setInsights((prev) => data.insights.length === 0 ? prev : [...prev, ...data.insights], @@ -47,6 +54,35 @@ export function useRobotoffPredicitions() { }; }, [insightIndex, insights]); + React.useEffect(() => { + let valid = true; + setIsLoading(true); + + robotoff + .getInsights( + "", + "nutrient_extraction", + "", + "not_annotated", + 1, + 25, + campaign, + ) + .then(({ data }) => { + if (!valid) { + return; + } + + setCount(data.count); + setInsights(data.insights); + setIsLoading(false); + }); + + return () => { + valid = false; + }; + }, [campaign]); + React.useEffect(() => { const barecodeToImport = insights .slice(insightIndex, insightIndex + 5) @@ -73,13 +109,14 @@ export function useRobotoffPredicitions() { const insight = insights[insightIndex]; console.log(offData); - const product = insight && offData[insight.barcode]; + + const product = insight !== undefined ? offData[insight.barcode] : undefined; return { isLoading, insight, nextItem, count, - product, + product: product === "loading" ? undefined : product, }; } diff --git a/src/pages/nutrition/utils.ts b/src/pages/nutrition/utils.ts index 73367a247..c8ff42652 100644 --- a/src/pages/nutrition/utils.ts +++ b/src/pages/nutrition/utils.ts @@ -93,3 +93,10 @@ export function deleteRobotoff(config: Pick) { { withCredentials: true }, ); } + +/** + * Extract xxx from the pattern 'yyy/yyyy/yyy/xxx.jpg' or 'yyy/yyyy/yyy/xxx.jpg' + */ +export function getImageId(source: string) { + return source.split("/")[source.split("/").length - 1].split(".")[0]; +}