diff --git a/src/App.js b/src/App.js index 41fff3574..a9f6505c1 100644 --- a/src/App.js +++ b/src/App.js @@ -14,27 +14,33 @@ import { Home, } from "./pages"; import ResponsiveAppBar from "./components/ResponsiveAppBar"; +import DevModeContext from "./contexts/devMode"; +import { getIsDevMode } from "./localeStorageManager"; const theme = createTheme({}); export default function App() { + const [devMode, setDevMode] = React.useState(getIsDevMode); + return ( - - - - {/* TODO: put a home page for root url */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - + + + + + {/* TODO: put a home page for root url */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + ); diff --git a/src/components/QuestionFilter/QuestionFilter.jsx b/src/components/QuestionFilter/QuestionFilter.jsx index 84b721822..47d321cf0 100644 --- a/src/components/QuestionFilter/QuestionFilter.jsx +++ b/src/components/QuestionFilter/QuestionFilter.jsx @@ -118,6 +118,7 @@ export const QuestionFilter = ({ size="small" value={filterState?.insightType} onChange={handleInsightTypeChange} + label={t(`questions.insightTypeLabel`)} > {Object.keys(insightTypesNames).map((insightType) => ( diff --git a/src/components/ResponsiveAppBar.jsx b/src/components/ResponsiveAppBar.jsx index 6d2315d43..661c7acd4 100644 --- a/src/components/ResponsiveAppBar.jsx +++ b/src/components/ResponsiveAppBar.jsx @@ -12,6 +12,7 @@ import MenuItem from "@mui/material/MenuItem"; import ListSubheader from "@mui/material/ListSubheader"; import MuiLink from "@mui/material/Link"; +import DevModeContext from "../contexts/devMode"; import logo from "../assets/logo.png"; import { Link } from "react-router-dom"; @@ -40,9 +41,15 @@ const ResponsiveAppBar = () => { setAnchorElNav(null); }; + const { devMode: isDevMode } = React.useContext(DevModeContext); + + const displayedPages = pages.filter( + (page) => page.url !== "insights" || isDevMode + ); + return ( - + {/* Mobile content */} @@ -74,7 +81,7 @@ const ResponsiveAppBar = () => { display: { xs: "block", md: "none" }, }} > - {pages.map((page) => + {displayedPages.map((page) => page.url ? ( { {/* Desktop content */} - - OpenFoodFact logo - - - Hunger Games - - - {pages.map((page) => - page.url ? ( - - ) : null - )} + + OpenFoodFact logo + + + Hunger Games + + + {displayedPages.map((page) => + page.url ? ( + + ) : null + )} + diff --git a/src/contexts/devMode.jsx b/src/contexts/devMode.jsx new file mode 100644 index 000000000..d9864e27f --- /dev/null +++ b/src/contexts/devMode.jsx @@ -0,0 +1,9 @@ +import * as React from "react"; +import { getIsDevMode } from "../localeStorageManager"; + +const DevModeContext = React.createContext({ + devMode: getIsDevMode(), + setDevMode: () => {}, +}); + +export default DevModeContext; diff --git a/src/i18n/common.json b/src/i18n/common.json index be95073e7..2a14dc2d2 100644 --- a/src/i18n/common.json +++ b/src/i18n/common.json @@ -4,10 +4,11 @@ "label": "label", "brand": "brand", "brands": "Brands", + "insightTypeLabel": "Only shows", "ingredients": "Ingredients", "product_weight": "product weight", "popularity_sort": "Sort by popularity", - "see_examples":"See examples of this ", + "see_examples": "See examples of this ", "no": "No", "skip": "Skip", "yes": "Yes", @@ -203,8 +204,8 @@ } }, "notfound": { - "nopage":"Whoops! The page you're looking for can't be found.", - "redirect1":"Want to play some games?", - "redirect2":"Click here" + "nopage": "Whoops! The page you're looking for can't be found.", + "redirect1": "Want to play some games?", + "redirect2": "Click here" } } \ No newline at end of file diff --git a/src/i18n/en.json b/src/i18n/en.json index 6fa9ce4dd..446f347a2 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -4,6 +4,7 @@ "label": "label", "brand": "brand", "brands": "Brands", + "insightTypeLabel": "Only shows", "ingredients": "Ingredients", "product_weight": "product weight", "popularity_sort": "Sort by popularity", diff --git a/src/localeStorageManager.js b/src/localeStorageManager.js index b7086ed04..560b0511f 100644 --- a/src/localeStorageManager.js +++ b/src/localeStorageManager.js @@ -16,6 +16,11 @@ export const localSettings = { }, }; +export const getIsDevMode = () => { + const settings = localSettings.fetch(); + return settings.devMode ?? false; +}; + export const getLang = () => { const settings = localSettings.fetch(); diff --git a/src/pages/insights/FilterInsights.jsx b/src/pages/insights/FilterInsights.jsx index ea5f2f31e..e98a6ffde 100644 --- a/src/pages/insights/FilterInsights.jsx +++ b/src/pages/insights/FilterInsights.jsx @@ -10,6 +10,9 @@ const typeOptions = [ { value: "category", labelKey: "insights.category" }, { value: "expiration_date", labelKey: "insights.expiration_date" }, { value: "packager_code", labelKey: "insights.packager_code" }, + { value: "brand", labelKey: "logos.brand" }, + { value: "packaging", labelKey: "logos.packaging" }, + { value: "qr_code", labelKey: "logos.qr_code" }, ]; const annotationOptions = [ @@ -20,19 +23,29 @@ const annotationOptions = [ { value: "not_annotated", labelKey: "insights.not_annotated" }, ]; +const useControled = (exteriorValue) => { + const [innerValue, setInnerValue] = React.useState(exteriorValue ?? ""); + + React.useEffect(() => { + setInnerValue((v) => (v !== exteriorValue ? exteriorValue : v)); + }, [exteriorValue]); + + return [innerValue, setInnerValue]; +}; + const FilterForm = ({ filterState = {}, setFilterState }) => { const { t } = useTranslation(); - const [innerBarcode, setInnerBarcode] = React.useState( + const [innerBarcode, setInnerBarcode] = useControled( filterState.barcode ?? "" ); - const [innerValueTag, setInnerValueTag] = React.useState( + const [innerValueTag, setInnerValueTag] = useControled( filterState.valueTag ?? "" ); - const [innerInsightType, setInnerInsightType] = React.useState( + const [innerInsightType, setInnerInsightType] = useControled( filterState.insightType ?? "" ); - const [innerAnnotationStatus, setInnerAnnotationStatus] = React.useState( + const [innerAnnotationStatus, setInnerAnnotationStatus] = useControled( filterState.annotationStatus ?? "" ); diff --git a/src/pages/insights/InsightsGrid.jsx b/src/pages/insights/InsightsGrid.jsx index cea09a377..289358f24 100644 --- a/src/pages/insights/InsightsGrid.jsx +++ b/src/pages/insights/InsightsGrid.jsx @@ -8,6 +8,8 @@ import VisibilityIcon from "@mui/icons-material/Visibility"; import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; import CancelOutlinedIcon from "@mui/icons-material/CancelOutlined"; import QuestionMarkOutlinedIcon from "@mui/icons-material/QuestionMarkOutlined"; +import FaceIcon from "@mui/icons-material/Face"; +import SmartToyIcon from "@mui/icons-material/SmartToy"; import Tooltip from "@mui/material/Tooltip"; import Link from "@mui/material/Link"; @@ -17,6 +19,15 @@ import offService from "../../off"; import { useTranslation } from "react-i18next"; import { Typography } from "@mui/material"; +const RenderLink = (props) => { + const { value, ...other } = props; + return ( + + {props.value} + + ); +}; + const getProductUrl = (code) => { if (!code) { return ""; @@ -46,6 +57,9 @@ const typeKeyToTranslationKey = { category: "insights.category", expiration_date: "insights.expiration_date", packager_code: "insights.packager_code", + brand: "logos.brand", + packaging: "logos.packaging", + qr_code: "logos.qr_code", }; const annotationValueToTranslationKey = { @@ -100,7 +114,7 @@ const dateTimeColumn = { valueGetter: (params) => (params.value ? new Date(params.value) : null), }; -const columns = [ +const staticColumnsDef = [ { field: "actions", type: "actions", @@ -108,9 +122,9 @@ const columns = [ + } @@ -118,9 +132,9 @@ const columns = [ + } @@ -141,14 +155,16 @@ const columns = [ renderCell: ({ row }) => row.type && row.value_tag ? ( - {row.value_tag} + {row.value_tag} : {row.value} ) : ( - row.value_tag + + {row.value_tag} : {row.value} + ), }, { field: "barcode", minWidth: 180, flex: 1, maxWidth: 200 }, - { field: "id" }, + { field: "id", flex: 1 }, { field: "timestamp", ...dateTimeColumn }, { field: "completed_at", @@ -168,18 +184,50 @@ const columns = [ minWidth: 70, flex: 1, maxWidth: 110, + renderCell: ({ value }) => + value ? ( + + + + ) : ( + + + + ), }, ].map((col) => ({ ...col, sortable: false })); const PAGE_SIZE = 25; -const InsightGrid = ({ filterState = {} }) => { +const InsightGrid = ({ filterState = {}, setFilterState }) => { const { t } = useTranslation(); const [pageState, setPageState] = React.useState({ page: 1, rowCount: 0 }); const [isLoading, setIsLoading] = React.useState(false); const [rows, setRows] = React.useState([]); + const columns = React.useMemo(() => { + return staticColumnsDef.map((col) => { + if (col.field !== "barcode") { + return col; + } + return { + ...col, + renderCell: ({ value, tabIndex }) => { + return ( + { + setFilterState((f) => ({ ...f, barcode: value })); + }} + /> + ); + }, + }; + }); + }, [setFilterState]); + React.useEffect(() => { setIsLoading(true); let isValid = true; diff --git a/src/pages/insights/index.jsx b/src/pages/insights/index.jsx index 555c012c6..382225aba 100644 --- a/src/pages/insights/index.jsx +++ b/src/pages/insights/index.jsx @@ -26,7 +26,10 @@ export default function Insights() { />
- +
); diff --git a/src/pages/questions/ProductInformation.jsx b/src/pages/questions/ProductInformation.jsx index 82c6f1d81..757c11661 100644 --- a/src/pages/questions/ProductInformation.jsx +++ b/src/pages/questions/ProductInformation.jsx @@ -10,6 +10,9 @@ import Stack from "@mui/material/Stack"; import Zoom from "react-medium-image-zoom"; import "react-medium-image-zoom/dist/styles.css"; +import EditIcon from "@mui/icons-material/Edit"; +import VisibilityIcon from "@mui/icons-material/Visibility"; + import { useTranslation } from "react-i18next"; import { NO_QUESTION_LEFT } from "../../const"; import offService from "../../off"; @@ -71,20 +74,27 @@ const ProductInformation = ({ question }) => { {/* Main information about the product */} {productData?.productName} - + {/* Image display section */} { )} {valueTagExamplesURL && ( - +
{`${t("questions.see_examples")} ${question.insight_type}`}
-
+ )} diff --git a/src/pages/settings/index.jsx b/src/pages/settings/index.jsx index dbc993512..e1d9efc67 100644 --- a/src/pages/settings/index.jsx +++ b/src/pages/settings/index.jsx @@ -4,9 +4,12 @@ import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; import Typography from "@mui/material/Typography"; import Stack from "@mui/material/Stack"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; import { useTranslation } from "react-i18next"; +import DevModeContext from "../../contexts/devMode"; import { localSettings } from "../../localeStorageManager"; export default function Settings() { @@ -14,14 +17,21 @@ export default function Settings() { const [language, setLanguage] = React.useState(i18n.language); - const handleChange = (e) => { + const { devMode, setDevMode } = React.useContext(DevModeContext); + + const handleDevModeChange = (event) => { + localSettings.update("devMode", event.target.checked); + setDevMode(event.target.checked); + }; + + const handleLangChange = (e) => { localSettings.update("lang", e.target.value); i18n.changeLanguage(e.target.value); setLanguage(e.target.value); }; return ( - + Settings @@ -30,7 +40,7 @@ export default function Settings() { sx={{ minWidth: 150 }} label={t("settings.language")} value={language} - onChange={handleChange} + onChange={handleLangChange} > {Object.keys(messages).map((lang) => ( @@ -38,6 +48,17 @@ export default function Settings() { ))} + +
+

To explore the database content, activate the dev mode

+ } + label="Dev Mode" + labelPlacement="end" + /> +
); }