From 4465640f199c25bd6adc6d66788e8e608a102022 Mon Sep 17 00:00:00 2001 From: Riad Jamal Date: Fri, 5 Jul 2024 22:21:44 +0300 Subject: [PATCH] Added filter functionality to the weight chart and the weight card on the dashboard --- .../BodyWeight/WeightFilter/WeightFilter.tsx | 48 ++++ .../BodyWeight/WeightFilter/index.tsx | 3 + src/components/BodyWeight/index.tsx | 92 +++++-- src/components/Dashboard/WeightCard.tsx | 141 +++++++---- src/locales/en/translation.json | 224 +++++++++++++++++- yarn.lock | 5 + 6 files changed, 436 insertions(+), 77 deletions(-) create mode 100644 src/components/BodyWeight/WeightFilter/WeightFilter.tsx create mode 100644 src/components/BodyWeight/WeightFilter/index.tsx diff --git a/src/components/BodyWeight/WeightFilter/WeightFilter.tsx b/src/components/BodyWeight/WeightFilter/WeightFilter.tsx new file mode 100644 index 00000000..30d6c47c --- /dev/null +++ b/src/components/BodyWeight/WeightFilter/WeightFilter.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import { Button, ButtonGroup, Box } from '@mui/material'; + +interface WeightFilterProps { + onFilter: (filter: string) => void; +} + +const WeightFilter: React.FC = ({ onFilter }) => { + const [activeFilter, setActiveFilter] = useState('all'); + + const handleFilterClick = (filter: string) => { + setActiveFilter(filter); + onFilter(filter); + }; + + return ( + + + + + + + + + ); +}; + +export default WeightFilter; diff --git a/src/components/BodyWeight/WeightFilter/index.tsx b/src/components/BodyWeight/WeightFilter/index.tsx new file mode 100644 index 00000000..8960ad1d --- /dev/null +++ b/src/components/BodyWeight/WeightFilter/index.tsx @@ -0,0 +1,3 @@ +import WeightFilter from './WeightFilter'; + +export default WeightFilter; diff --git a/src/components/BodyWeight/index.tsx b/src/components/BodyWeight/index.tsx index 782d0679..4ffe1aad 100644 --- a/src/components/BodyWeight/index.tsx +++ b/src/components/BodyWeight/index.tsx @@ -1,28 +1,70 @@ -import { Box, Stack } from "@mui/material"; -import { useBodyWeightQuery } from "components/BodyWeight/queries"; -import { WeightTable } from "components/BodyWeight/Table"; -import { WeightChart } from "components/BodyWeight/WeightChart"; -import { AddBodyWeightEntryFab } from "components/BodyWeight/widgets/fab"; -import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget"; -import { WgerContainerRightSidebar } from "components/Core/Widgets/Container"; -import { OverviewEmpty } from "components/Core/Widgets/OverviewEmpty"; -import { useTranslation } from "react-i18next"; +import React, { useState, useEffect } from 'react'; +import { Box, Stack } from '@mui/material'; +import { useBodyWeightQuery } from 'components/BodyWeight/queries'; +import { WeightTable } from 'components/BodyWeight/Table'; +import { WeightChart } from 'components/BodyWeight/WeightChart'; +import WeightFilter from 'components/BodyWeight/WeightFilter'; +import { AddBodyWeightEntryFab } from 'components/BodyWeight/widgets/fab'; +import { LoadingPlaceholder } from 'components/Core/LoadingWidget/LoadingWidget'; +import { WgerContainerRightSidebar } from 'components/Core/Widgets/Container'; +import { OverviewEmpty } from 'components/Core/Widgets/OverviewEmpty'; +import { useTranslation } from 'react-i18next'; +import { WeightEntry } from 'components/BodyWeight/model'; export const BodyWeight = () => { - const [t] = useTranslation(); - const weightyQuery = useBodyWeightQuery(); + const [t] = useTranslation(); + const weightyQuery = useBodyWeightQuery(); + const [filteredData, setFilteredData] = useState([]); - return weightyQuery.isLoading - ? - : - {weightyQuery.data!.length === 0 && } - - - - - } - fab={} - />; -}; \ No newline at end of file + useEffect(() => { + if (weightyQuery.data) { + setFilteredData(weightyQuery.data); + } + }, [weightyQuery.data]); + + const handleFilter = (filter: string) => { + const now = new Date(); + let filteredWeights: WeightEntry[] = []; + if (weightyQuery.data) { + switch (filter) { + case 'year': + filteredWeights = weightyQuery.data.filter((entry) => + new Date(entry.date) >= new Date(now.setFullYear(now.getFullYear() - 1)) + ); + break; + case 'sixMonths': + filteredWeights = weightyQuery.data.filter((entry) => + new Date(entry.date) >= new Date(now.setMonth(now.getMonth() - 6)) + ); + break; + case 'month': + filteredWeights = weightyQuery.data.filter((entry) => + new Date(entry.date) >= new Date(now.setMonth(now.getMonth() - 1)) + ); + break; + case 'all': + default: + filteredWeights = weightyQuery.data; + } + setFilteredData(filteredWeights); + } + }; + + return weightyQuery.isLoading ? ( + + ) : ( + + + {filteredData.length === 0 && } + + + + + } + fab={} + /> + ); +}; diff --git a/src/components/Dashboard/WeightCard.tsx b/src/components/Dashboard/WeightCard.tsx index 5a9d0ff4..a1d8ca48 100644 --- a/src/components/Dashboard/WeightCard.tsx +++ b/src/components/Dashboard/WeightCard.tsx @@ -1,70 +1,109 @@ -import AddIcon from "@mui/icons-material/Add"; -import { Box, Button, Card, CardActions, CardContent, CardHeader, IconButton, } from '@mui/material'; +import React, { useState, useEffect } from 'react'; +import { Box, Button, Card, CardActions, CardContent, CardHeader, IconButton } from '@mui/material'; import Tooltip from "@mui/material/Tooltip"; +import AddIcon from "@mui/icons-material/Add"; import { WeightForm } from "components/BodyWeight/Form/WeightForm"; import { WeightEntry } from "components/BodyWeight/model"; -import { useBodyWeightQuery } from "components/BodyWeight/queries"; import { WeightTableDashboard } from "components/BodyWeight/TableDashboard/TableDashboard"; import { WeightChart } from "components/BodyWeight/WeightChart"; -import { LoadingPlaceholder } from "components/Core/LoadingWidget/LoadingWidget"; import { WgerModal } from "components/Core/Modals/WgerModal"; -import { EmptyCard } from "components/Dashboard/EmptyCard"; -import React from 'react'; import { useTranslation } from "react-i18next"; import { makeLink, WgerLink } from "utils/url"; +import WeightFilter from 'components/BodyWeight/WeightFilter'; +import { useBodyWeightQuery } from 'components/BodyWeight/queries'; +import { LoadingPlaceholder } from 'components/Core/LoadingWidget/LoadingWidget'; +import { EmptyCard } from 'components/Dashboard/EmptyCard'; export const WeightCard = () => { - const [t] = useTranslation(); const weightyQuery = useBodyWeightQuery(); - return (<>{weightyQuery.isLoading - ? - : <>{weightyQuery.data?.length !== undefined && weightyQuery.data?.length > 0 - ? - : } - />} - } - ); + return ( + <> + {weightyQuery.isLoading + ? + : <> + {weightyQuery.data?.length !== undefined && weightyQuery.data?.length > 0 + ? + : } + />} + + } + + ); }; -export const WeightCardContent = (props: { entries: WeightEntry[] }) => { - const [openModal, setOpenModal] = React.useState(false); +export const WeightCardContent = (props: { entries: WeightEntry[] }) => { + const [openModal, setOpenModal] = useState(false); const handleOpenModal = () => setOpenModal(true); const handleCloseModal = () => setOpenModal(false); const [t, i18n] = useTranslation(); + const [filteredData, setFilteredData] = useState(props.entries); + + useEffect(() => { + setFilteredData(props.entries); + }, [props.entries]); - return (<> - - - - - - - - - - - - - - - - - - - - - ); -}; \ No newline at end of file + const handleFilter = (filter: string) => { + const now = new Date(); + let filteredWeights: WeightEntry[] = []; + switch (filter) { + case 'year': + filteredWeights = props.entries.filter((entry) => + new Date(entry.date) >= new Date(now.setFullYear(now.getFullYear() - 1)) + ); + break; + case 'sixMonths': + filteredWeights = props.entries.filter((entry) => + new Date(entry.date) >= new Date(now.setMonth(now.getMonth() - 6)) + ); + break; + case 'month': + filteredWeights = props.entries.filter((entry) => + new Date(entry.date) >= new Date(now.setMonth(now.getMonth() - 1)) + ); + break; + case 'all': + default: + filteredWeights = props.entries; + } + setFilteredData(filteredWeights); + }; + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 935ed5a0..cab0810e 120000 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -1 +1,223 @@ -../../../public/locales/en/translation.json \ No newline at end of file +{ + "weight": "Weight", + "date": "Date", + "timeOfDay": "Time of day", + "submit": "Submit", + "edit": "Edit", + "delete": "Delete", + "deleteConfirmation": "Are you sure you want to delete \"{{name}}\"?", + "add": "Add", + "close": "Close", + "difference": "Difference", + "days": "Days", + "licenses": { + "authors": "Author(s)", + "authorProfile": "Link to author website or profile, if available", + "derivativeSourceUrl": "Link to the original source, if this is a derivative work", + "derivativeSourceUrlHelper": "Note that a derivative work is one which is not only based on a previous work, but which also contains sufficient new, creative content to entitle it to its own copyright.", + "originalObjectUrl": "Link to the source website, if available", + "originalTitle": "Title" + }, + "loading": "Loading...", + "nutritionalPlan": "Nutritional plan", + "addEntry": "Add entry", + "currentWeight": "Current weight", + "workout": "Workout", + "seeDetails": "See details", + "actions": "Actions", + "nothingHereYet": "Nothing here yet...", + "nothingHereYetAction": "Press the action button to begin", + "notes": "Notes", + "value": "Value", + "unit": "Unit", + "alsoSearchEnglish": "Also search for names in English", + "copyToClipboard": "Copy to clipboard", + "filters": "Filters", + "exercises": { + "replacements": "Replacements", + "replacementsInfoText": "Optionally, you can also select an exercise that should replace this one (e.g. because it was submitted twice, or similar). This will replace the exercise in routines as well as training logs, instead of just deleting it. These changes will also propagate to any instance that syncs the exercises from this one.", + "replacementsSearch": "Search for an exercise or copy and paste a known ID into the field and click on the \"load\" button.", + "noReplacementSelected": " No exercise selected for replacement", + "contributeExercise": "Contribute an exercise", + "step1HeaderBasics": "Basics in English", + "variations": "Variations", + "notEnoughRightsHeader": "You can't contribute exercises", + "notEnoughRights": "You can only contribute exercises if your account is older than {{days}} days and have verified your email", + "muscles": "Muscles", + "secondaryMuscles": "Secondary muscles", + "whatVariationsExist": "Which variations of this exercise exist, if any?", + "filterVariations": "Enter exercise name to filter variations", + "identicalExercise": "Avoid duplicate exercises", + "identicalExercisePleaseDiscard": "If you notice an exercise that is identical to the one you're adding, please discard your draft and edit that exercise instead.", + "translateExerciseNow": "Translate this exercise now", + "compatibleImagesCC": "Images must be compatible with the CC BY SA license. If in doubt, upload only photos you've taken yourself.", + "alternativeNames": "Alternative names", + "notes": "Notes", + "equipment": "Equipment", + "checkInformationBeforeSubmitting": "Please check that the information you entered is correct before submitting the exercise", + "cacheWarning": "Due to caching it might take some time till the changes are visible throughout the application.", + "submitExercise": "Submit exercise", + "successfullyUpdated": "The exercise was successfully updated. Due to caching it might take some time till the changes are visible throughout the application.", + "description": "Description", + "basics": "Basics", + "exerciseNotTranslated": "No translation available", + "exerciseNotTranslatedBody": "This exercise is currently not available in the currently selected language. Do you want to contribute a translation?", + "alsoKnownAs": "Also known as:", + "primaryMuscles": "Primary muscles", + "deleteExerciseBody": "Do you want to delete the exercise \"{{name}}\"? You can either delete the current {{language}} translation or the complete exercise with all translations, images, etc.", + "deleteTranslation": "Delete translation", + "deleteExerciseFull": "Delete full exercise", + "deleteExerciseReplace": "Delete and replace", + "exercises": "Exercises", + "changeExerciseLanguage": "Change this exercise's language", + "noEquipment": "No equipment", + "missingExercise": "Missing a certain exercise?", + "missingExerciseDescription": "Help out the community by contributing it!", + "searchExerciseName": "Search by exercise name", + "newNote": "New note", + "notesHelpText": "Notes are short comments on how to perform the exercise such as \"keep your body straight\"", + "imageStylePhoto": "Photo", + "imageStyle3D": "3D", + "imageStyleLine": "Line", + "imageStyleLowPoly": "Low-Poly", + "imageStyleOther": "Other", + "imageDetails": "Image details" + }, + "nutrition": { + "plans": "Nutritional plans", + "copyPlan": "Make a copy of this plan", + "plan": "Nutritional plan", + "onlyLoggingHelpText": "Only track calories. Check the box if you only want to log your calories and don't want to setup a detailed nutritional plan with specific meals", + "goalsTitle": "Goals", + "useGoalsHelpText": "Add goals to this plan", + "useGoalsHelpTextLong": "This allows you to set general goals for energy, protein, carbohydrates or fat for the plan. Note that if you setup a detailed meal plan, these values will take precedence.", + "goalEnergy": "Energy goal", + "goalProtein": "Protein goal", + "goalCarbohydrates": "Carbohydrates goal", + "goalFiber": "Fiber goal", + "goalFat": "Fat goal", + "addNutritionalDiary": "Add nutrition diary entry", + "meal": "Meal", + "addMeal": "Add meal", + "addMealItem": "Add ingredient to meal", + "nutritionalDiary": "Nutrition diary", + "gramShort": "g", + "kcal": "kcal", + "valueEnergyKcal": "{{value}} kcal", + "valueEnergyKcalKj": "{{kcal}} kcal / {{kj}} kJ", + "searchIngredientName": "Search by ingredient name", + "macronutrient": "Macronutrient", + "percentEnergy": "Percent of energy", + "gPerBodyKg": "g per body-kg", + "planned": "Planned", + "logged": "Logged", + "loggedToday": "Logged today", + "difference": "Difference", + "today": "Today", + "7dayAvg": "7-day average", + "energy": "Energy", + "protein": "Protein", + "carbohydrates": "Carbohydrates", + "sugar": "Sugar", + "ofWhichSugars": "of which sugars", + "fat": "Fat", + "ofWhichSaturated": "of which saturated", + "saturatedFat": "Saturated fat", + "pseudoMealTitle": "Other logs", + "others": "Others", + "fibres": "Fibres", + "sodium": "Sodium", + "planDeleteInfo": "This will delete all nutrition diary entries as well", + "mealDeleteInfo": "Nutrition diary entries to this meal will not be deleted and will appear under \"other logs\"", + "diaryEntrySaved": "Diary entry successfully saved", + "logThisMeal": "Log this meal as-is to the nutrition diary", + "logThisMealItem": "Log this ingredient as-is to the nutrition diary", + "valueRemaining": "remaining", + "valueTooMany": "too many" + }, + "downloadAsPdf": "Download as PDF", + "total": "Total", + "description": "Description", + "translation": "Translation", + "images": "Images", + "overview": "Overview", + "preferences": "Preferences", + "continue": "Continue", + "goBack": "Back", + "language": "Language", + "forms": { + "supportedImageFormats": "Only JPEG, PNG and WEBP files below 20Mb are supported", + "valueTooShort": "The value is too short", + "valueTooLong": "The value is too long", + "fieldRequired": "This field is required", + "maxLength": "Please enter less than {{chars}} characters", + "minLength": "Please enter more than {{chars}} characters", + "minValue": "The value for this field has to be higher than {{value}}", + "maxValue": "The value for this field has to be less than {{value}}" + }, + "name": "Name", + "category": "Category", + "success": "Success!", + "English": "English", + "save": "Save", + "videos": "Videos", + "cannotBeUndone": "This action can't be undone.", + "cancel": "Cancel", + "noResults": "No results", + "noResultsDescription": "No results found for this query, consider reducing the number of filters.", + "routines": { + "addDay": "Add training day", + "addWeightLog": "Add training log", + "logsHeader": "Training log for workout", + "logsFilterNote": "Note that only entries with a weight unit of kg or lb and repetitions are charted, other combinations such as time or until failure are ignored here", + "addLogToDay": "Add log to this day", + "routine": "Routine", + "routines": "Routines", + "rir": "RiR" + }, + "measurements": { + "measurements": "Measurements", + "unitFormHelpText": "The unit in which the category will be measured, such as cm or %", + "deleteInfo": "This will delete the category as well as all its entries" + }, + "server": { + "abs": "Abs", + "arms": "Arms", + "back": "Back", + "barbell": "Barbell", + "bench": "Bench", + "biceps": "Biceps", + "body_weight": "Body weight", + "calves": "Calves", + "cardio": "Cardio", + "chest": "Chest", + "dumbbell": "Dumbbell", + "glutes": "Glutes", + "gym_mat": "Gym mat", + "hamstrings": "Hamstrings", + "incline_bench": "Incline bench", + "kettlebell": "Kettlebell", + "kilometers": "Kilometers", + "kilometers_per_hour": "Kilometers per hour", + "lats": "Lats", + "legs": "Legs", + "max_reps": "Max reps", + "miles": "Miles", + "miles_per_hour": "Miles per hour", + "minutes": "Minutes", + "plates": "Plates", + "pull_up_bar": "Pull up bar", + "quads": "Quads", + "repetitions": "Repetitions", + "sz_bar": "SZ bar", + "seconds": "Seconds", + "shoulders": "Shoulders", + "swiss_ball": "Swiss ball", + "triceps": "Triceps", + "until_failure": "Until failure", + "kg": "kg", + "lb": "lb", + "none__bodyweight_exercise_": "none (bodyweight exercise)" + } + } + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 48c0c45d..0ac88b2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7261,6 +7261,11 @@ jsesc@~0.5.0: resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-loader@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + integrity sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w== + json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz"