Skip to content

Commit

Permalink
Merge branch 'master' into show-as-aselected
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfauquette authored Dec 12, 2024
2 parents 886296e + d26cc5e commit 8466397
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/assets/countries.json
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,7 @@
"id": "en:world",
"label": "World",
"languageCode": "en",
"countryCode": "world"
"countryCode": ""
},
{
"id": "en:yemen",
Expand Down
25 changes: 13 additions & 12 deletions src/components/ResponsiveAppBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,21 +413,22 @@ const ResponsiveAppBar = () => {
}}
>
<Select
value={country}
onChange={(event) => setCountry(event.target.value, "global")}
value={country || "world"}
onChange={(event) =>
setCountry(
event.target.value === "world" ? "" : event.target.value,
"global",
)
}
variant="outlined"
sx={{ fieldset: { border: "none" } }}
>
{countryNames
.filter((country) => country.countryCode)
.map((country) => (
<MenuItem
value={country.countryCode}
key={country.countryCode}
>
{country.label} ({country.countryCode})
</MenuItem>
))}
{countryNames.map(({ label, countryCode }) => (
<MenuItem value={countryCode || "world"} key={countryCode}>
{label}
{countryCode && ` (${countryCode})`}
</MenuItem>
))}
</Select>
<IconButton
color="inherit"
Expand Down
15 changes: 11 additions & 4 deletions src/contexts/CountryProvider/CountryProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import countries from "../../assets/countries.json";
const ValidCountryCodes = new Set(countries.map((c) => c.countryCode));

export function CountryProvider({ children }) {
const [country, setCountry] = useLocalStorageState("country", "world");
const [country, setCountry] = useLocalStorageState("country", "");
const [searchParams, setSearchParams] = useSearchParams();

const searchParamsCountry = searchParams.get("country")?.toLowerCase();
Expand All @@ -26,9 +26,16 @@ export function CountryProvider({ children }) {
);

const value = React.useMemo(() => {
const lowercasedCountry = (
ValidCountryCodes.has(searchParamsCountry) ? searchParamsCountry : country
)?.toLocaleLowerCase();
// Try from:
// - searchParams
// - localStorage
// - empty

const lowercasedCountry = ValidCountryCodes.has(searchParamsCountry)
? searchParamsCountry
: ValidCountryCodes.has(country?.toLocaleLowerCase())
? country?.toLocaleLowerCase()
: "";

return {
country: lowercasedCountry,
Expand Down
99 changes: 99 additions & 0 deletions src/pages/nutrition/Instructions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable react/no-unescaped-entities */
import * as React from "react";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import Typography from "@mui/material/Typography";

export default function Instructions() {
const [open, setOpen] = React.useState(false);

const handleClickOpen = () => {
setOpen(true);
};

const handleClose = () => {
setOpen(false);
};

return (
<React.Fragment>
<Button
size="small"
variant="outlined"
onClick={handleClickOpen}
sx={{ margin: 2 }}
>
Instructions
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
Comment utiliser cette interface
</DialogTitle>
<DialogContent sx={{ "& p": { marginBottom: 2 } }}>
<DialogContentText component="div" id="alert-dialog-description">
<Typography>
Avec les checkbox du haut, vous pouvez choisir entre traiter des
tableux de produit sans tableau nutritionel. Ou traiter des
produits pour lequels robotoof à trouver des nutriments en plus.
</Typography>
<Typography>
La date de la photo est affichée au dessus de la photo.
</Typography>
<Typography>
Les nutriments déjà associé au produit sont affiché en petit sous
les inputs.
<ol>
<li>
En <span style={{ color: "green" }}>vert</span> pour ceux qui
matchent avec la valeur du champ
</li>
<li>
En <span style={{ color: "orange" }}>orange</span> si le champ
est vide
</li>
<li>
En <span style={{ color: "red" }}>rouge</span> si le champ est
différent de la valeur connue.
</li>
</ol>
La valeur "-" indique une valeur abscente du tableau nutritionel.
Particulierement utile pour les fibres qui sont souvent abscente.
</Typography>
<Typography>
Quand une colone a été vérifiée (celle pour 100g ou celle par
portion) il ne vous reste plus qu'à la valider pour passer à la
suite. Innutil de remplir les deux colones. Une seul des deux peut
être enregistrée par OFF.
</Typography>

<ul>
<li>
Le bouton "skip" passe à la suite. quelqu'un d'autre s'en
chargera.
</li>
<li>
Le bouton "invalid image" indique que la photo ne correspond pas
à un tableau nutritionel, et supprime la question pour tout le
monde.
</li>
</ul>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} autoFocus>
Fermer
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
55 changes: 50 additions & 5 deletions src/pages/nutrition/NutrimentCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,35 @@ interface NutrimentCellProps {
setValues: (object) => void;
}

/**
* Returns the string value of the input without any space.
*/
function clean(input: undefined | string | null | number): string {
if (input == undefined) {
return "";
}
return `${input}`.replaceAll(" ", "");
}
function getLegendColor(product, prediction, nutrimentId) {
const cleanProduct = clean(product);
const cleanPrediction = clean(prediction);

console.log({ nutrimentId, cleanProduct, cleanPrediction });

if (cleanProduct === cleanPrediction) {
return "green";
}

if (cleanProduct === "" || cleanPrediction === "") {
return "orange";
}

if (cleanProduct !== cleanPrediction) {
return "red";
}
return undefined;
}

export const NutrimentCell = (props: NutrimentCellProps) => {
const {
nutrimentId,
Expand Down Expand Up @@ -64,17 +93,33 @@ export const NutrimentCell = (props: NutrimentCellProps) => {
/>
<br />
{displayOFFValue && (
<legend style={{ fontSize: 13, textAlign: "end" }}>
{productValue}
{productUnit}
<legend
style={{
fontSize: 13,
textAlign: "end",
}}
>
<span
style={{
color: getLegendColor(productValue, value, nutrimentId),
}}
>
{productValue}
</span>
<span
style={{
color: getLegendColor(productUnit, unit, nutrimentId),
}}
>
{productUnit}
</span>
</legend>
)}
</div>

{isValidUnit(unit) ? (
<select
style={{ width: 55 }}
value={unit}
value={unit || ""}
tabIndex={tabIndex}
onChange={(event) => {
setValues((p) => ({
Expand Down
18 changes: 5 additions & 13 deletions src/pages/nutrition/config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
export const UNITS = ["", "g", "mg", "µg"];

export const OFF_NUTRIMENTS_TO_IGNORE = [
"serving",
"energy", // Already available with energy kj and kcal.
"fruits-vegetables-legumes-estimate-from-ingredients",
"fruits-vegetables-nuts-estimate-from-ingredients",
"nova-group",
"nutrition-score-fr",
];
export const NUTRIMENTS_ORDER = [
export const KNOWN_NUTRIMENTS = [
// Energy
"energy-kj",
"energy-kcal",
Expand Down Expand Up @@ -104,10 +96,10 @@ export const NUTRIMENTS_ORDER = [
"pantothenic-acid",
"silica",
"bicarbonate",
"Sulphate",
"Nitrate",
"Hydrogencarbonate",
"Nitrite",
"sulphate",
"nitrate",
"nydrogencarbonate",
"nitrite",
"potassium",
"chloride",
"calcium",
Expand Down
19 changes: 12 additions & 7 deletions src/pages/nutrition/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ import { ErrorBoundary } from "../taxonomyWalk/Error";
import LinksToProduct from "./LinksToProduct";
import { NutrimentCell } from "./NutrimentCell";
import PictureSection from "./PictureSection";
import { NUTRIMENTS_ORDER } from "./config";
import { KNOWN_NUTRIMENTS } from "./config";
import Instructions from "./Instructions";

export default function Nutrition() {
const [partiallyFilled, setPartiallyFilled] = React.useState(false);
const [displayOFFValue, setDisplayOFFValue] = React.useState(false);
const handlePartiallyFilled = (_, checked) => setPartiallyFilled(checked);
const handlePartiallyFilled = (_, checked) => {
setPartiallyFilled(checked);
setDisplayOFFValue(checked);
};
const handleDisplayOFFValue = (_, checked) => setDisplayOFFValue(checked);

const [additionalIds, setAdditionalIds] = React.useState([]);
Expand Down Expand Up @@ -65,18 +69,19 @@ export default function Nutrition() {
}));
}, [insight]);

const nutrimentsDetected = React.useMemo(
const nutrimentsDisplayed = React.useMemo(
() => structurePredictions(values, product, additionalIds),
[values, product, additionalIds],
);

const notUsedNutriments = React.useMemo(
() => NUTRIMENTS_ORDER.filter((id) => !nutrimentsDetected.includes(id)),
[nutrimentsDetected],
() => KNOWN_NUTRIMENTS.filter((id) => !nutrimentsDisplayed.includes(id)),
[nutrimentsDisplayed],
);
return (
<React.Suspense>
<ErrorBoundary>
<Instructions />
<Stack direction="row">
<Box sx={{ width: "50%" }}>
<PictureSection
Expand Down Expand Up @@ -157,7 +162,7 @@ export default function Nutrition() {
</tr>
</thead>
<tbody>
{nutrimentsDetected.map((nutrimentId) => {
{nutrimentsDisplayed.map((nutrimentId) => {
const key100g = `${nutrimentId}_100g`;
const { value: value100g, unit: unit100g } =
values[key100g] ?? {};
Expand Down Expand Up @@ -215,7 +220,7 @@ export default function Nutrition() {
setAdditionalIds((p) => [...p, event.target.value]);
}}
>
<option disabled selected value="">
<option disabled value="">
-- add nutriment --
</option>
{notUsedNutriments.map((nutriId) => (
Expand Down
6 changes: 3 additions & 3 deletions src/pages/nutrition/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from "axios";
import { OFF_NUTRIMENTS_TO_IGNORE, UNITS } from "./config";
import { KNOWN_NUTRIMENTS, UNITS } from "./config";
import { NutrimentPrediction } from "./insight.types";
import { ROBOTOFF_API_URL } from "../../const";

Expand Down Expand Up @@ -32,7 +32,7 @@ export function structurePredictions(
Object.keys(predictions).forEach((key) => {
const id = key.split("_")[0]; // split 'energy-kj_100g' to only get 'energy-kj'

if (OFF_NUTRIMENTS_TO_IGNORE.includes(id)) {
if (!KNOWN_NUTRIMENTS.includes(id)) {
return;
}
if (!nurimentsIds.includes(id)) {
Expand All @@ -43,7 +43,7 @@ export function structurePredictions(
Object.keys(productValue?.nutriments ?? {}).forEach((key) => {
const id = key.split("_")[0]; // split 'energy-kj_100g' to only get 'energy-kj'

if (OFF_NUTRIMENTS_TO_IGNORE.includes(id)) {
if (!KNOWN_NUTRIMENTS.includes(id)) {
return;
}
if (!nurimentsIds.includes(id)) {
Expand Down
Loading

0 comments on commit 8466397

Please sign in to comment.