diff --git a/src/components/container/MealEditor/MealEditor.tsx b/src/components/container/MealEditor/MealEditor.tsx index 31b7b1fd6..f2356e308 100644 --- a/src/components/container/MealEditor/MealEditor.tsx +++ b/src/components/container/MealEditor/MealEditor.tsx @@ -1,6 +1,6 @@ import React, { CSSProperties, useContext, useEffect, useRef, useState } from 'react'; import './MealEditor.css'; -import { getMealImageUrls, getMeals, getMealToEdit, getMealTypeDisplayText, language, Meal, resolveColor, saveMeals, timestampSortAsc, uploadMealImageFile } from '../../../helpers'; +import { getMealImageUrls, getMeals, getMealToEdit, getMealTypeDisplayText, language, Meal, resolveColor, saveMeals, timestampSortAsc, uploadMealImageFile, logMealEvent } from '../../../helpers'; import { Button, Card, LayoutContext, LoadingIndicator, UnstyledButton } from '../../presentational'; import { format, parse, startOfDay } from 'date-fns'; import { createPreviewData, MealEditorPreviewState } from './MealEditor.previewData'; @@ -9,6 +9,7 @@ import { faEdit, faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons'; import { getFullDayAndDateString } from '../../../helpers/date-helpers'; import { combineItemsWithAnalysisItems, itemSortByNameAsc } from '../../../helpers/glucose-and-meals/meals'; import MealAnalysis from '../../presentational/MealAnalysis'; +import MyDataHelps, { DeviceInfo } from "@careevolution/mydatahelps-js"; export interface MealEditorProps { previewState?: 'loading' | MealEditorPreviewState; @@ -36,9 +37,15 @@ export default function MealEditor(props: MealEditorProps) { const [newImageFile, setNewImageFile] = useState(); const [imageTypeError, setImageTypeError] = useState(false); const [imageUploadError, setImageUploadError] = useState(false); + const [deviceInfo, setDeviceInfo] = useState(); const itemsToAddInputRef = useRef(null); + const onError = (message: string) => { + logMealEvent("meal-error", mealToEdit, deviceInfo, message); + props.onError(); + } + useEffect(() => { setLoading(true); setAllMeals([]); @@ -65,28 +72,33 @@ export default function MealEditor(props: MealEditorProps) { return; } - getMealToEdit().then(mealReference => { - if (mealReference) { - getMeals(mealReference.date).then(async allMeals => { - const activeMeals = allMeals.filter(meal => !meal.archiveTimestamp); - const referencedMeal = activeMeals.find(meal => meal.id === mealReference.id); - if (referencedMeal) { - const imageUrls = await getMealImageUrls([referencedMeal]); - const imageUrl = imageUrls[referencedMeal.id.toString()]; - - setLoading(false); - setAllMeals(allMeals); - setActiveMeals(activeMeals); - setMealToEdit(referencedMeal); - setImageUrl(imageUrl); - setImageLoading(!!imageUrl); - } else { - props.onError(); - } - }); - } else { - props.onError(); - } + MyDataHelps.getDeviceInfo().then(info => { + setDeviceInfo(info); + getMealToEdit().then(mealReference => { + if (mealReference) { + logMealEvent("editing-started", undefined, info, `Editing meal id=${mealReference.id}`); + + getMeals(mealReference.date).then(async allMeals => { + const activeMeals = allMeals.filter(meal => !meal.archiveTimestamp); + const referencedMeal = activeMeals.find(meal => meal.id === mealReference.id); + if (referencedMeal) { + const imageUrls = await getMealImageUrls([referencedMeal]); + const imageUrl = imageUrls[referencedMeal.id.toString()]; + + setLoading(false); + setAllMeals(allMeals); + setActiveMeals(activeMeals); + setMealToEdit(referencedMeal); + setImageUrl(imageUrl); + setImageLoading(!!imageUrl); + } else { + onError(`Can't find meal id=${mealReference.id}`); + } + }); + } else { + onError("No meal reference provided."); + } + }); }); }, [props.previewState]); @@ -102,6 +114,8 @@ export default function MealEditor(props: MealEditorProps) { return; } + logMealEvent("delete", mealToEdit, deviceInfo); + setLoading(true); mealToEdit.archiveTimestamp = new Date(); @@ -118,14 +132,19 @@ export default function MealEditor(props: MealEditorProps) { return; } + logMealEvent("save", mealToEdit, deviceInfo); + setLoading(true); setImageTypeError(false); setImageUploadError(false); if (newImageFile) { + logMealEvent("uploading-image", mealToEdit, deviceInfo, { name: newImageFile.name, size: newImageFile.size }); try { await uploadMealImageFile(mealToEdit, newImageFile); - } catch { + logMealEvent("image-uploaded", mealToEdit, deviceInfo, { name: newImageFile.name, size: newImageFile.size }); + } catch (err) { + logMealEvent("image-upload-error", mealToEdit, deviceInfo, { name: newImageFile.name, size: newImageFile.size, error: err }); setLoading(false); setImageUploadError(true); return; @@ -143,6 +162,8 @@ export default function MealEditor(props: MealEditorProps) { const otherMeals = allMeals.filter(meal => meal.id !== mealToEdit.id); const updatedMeals = [...otherMeals, mealToEdit].sort(timestampSortAsc); + logMealEvent("saving-meals", mealToEdit, deviceInfo); + saveMeals(startOfDay(mealToEdit.timestamp), updatedMeals).then(props.onSave); }; @@ -162,6 +183,8 @@ export default function MealEditor(props: MealEditorProps) { }; const onFileChanged = (file: File | undefined) => { + logMealEvent("change-file", mealToEdit, deviceInfo); + if (file) { const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp', 'image/webp']; if (allowedTypes.includes(file.type)) { @@ -170,13 +193,17 @@ export default function MealEditor(props: MealEditorProps) { setImageUrl(URL.createObjectURL(file)); setImageTypeError(false); setImageUploadError(false); + logMealEvent("image-added", mealToEdit, deviceInfo, `Image type=${file.type} size=${file.size}`); } else { + logMealEvent("image-type-error", mealToEdit, deviceInfo, `Invalid image type ${file.type}`); setImageTypeError(true); } } }; const onRemoveImage = () => { + logMealEvent("remove-image", mealToEdit, deviceInfo); + setMealToEdit({ ...mealToEdit, hasImage: false }); setNewImageFile(undefined); setImageUrl(undefined); @@ -211,6 +238,11 @@ export default function MealEditor(props: MealEditorProps) { setMealToEdit({ ...mealToEdit, items: updatedItems }); }; + const onCancel = () => { + logMealEvent("cancel", mealToEdit, deviceInfo); + props.onCancel(); + }; + const colorStyles = { '--mdhui-meal-editor-image-action-background-color': resolveColor(layoutContext.colorScheme, { lightMode: 'var(--mdhui-meal-editor-image-action-background-color-light)', @@ -341,7 +373,7 @@ export default function MealEditor(props: MealEditorProps) { {hasDuplicateTimestamp() &&
{language('meal-editor-duplicate-timestamp-error')}
} {imageUploadError &&
{language('meal-editor-image-upload-error')}
}
- +
diff --git a/src/helpers/glucose-and-meals/index.ts b/src/helpers/glucose-and-meals/index.ts index 80aef4994..11ddeb9cd 100644 --- a/src/helpers/glucose-and-meals/index.ts +++ b/src/helpers/glucose-and-meals/index.ts @@ -1,5 +1,5 @@ export { getGlucoseReadings, computeBestFitGlucoseValue, computeGlucoseReadingRanges, computeGlucoseReadingRecentAverage } from './glucose'; -export { getMeals, saveMeals, prepareMealForEditing, getMealToEdit, uploadMealImageFile, getMealImageUrls } from './meals'; +export { getMeals, saveMeals, prepareMealForEditing, getMealToEdit, uploadMealImageFile, getMealImageUrls, logMealEvent } from './meals'; export { generateGlucose, generateSteps, generateSleep } from './sample-data'; export { getSleepMinutes } from './sleep'; export { getSteps } from './steps'; diff --git a/src/helpers/glucose-and-meals/meals.ts b/src/helpers/glucose-and-meals/meals.ts index c8b72f573..22c8df5af 100644 --- a/src/helpers/glucose-and-meals/meals.ts +++ b/src/helpers/glucose-and-meals/meals.ts @@ -1,4 +1,4 @@ -import MyDataHelps, { PersistableDeviceDataPoint } from '@careevolution/mydatahelps-js'; +import MyDataHelps, { PersistableDeviceDataPoint, DeviceInfo } from '@careevolution/mydatahelps-js'; import { add, compareDesc, endOfDay, parseISO, startOfDay } from 'date-fns'; import { Meal, MealItem, MealReference } from './types'; import { timestampSortAsc } from './util'; @@ -135,4 +135,17 @@ export function itemSortByNameAsc(item1: MealItem, item2: MealItem): number { export function itemSortByConfidenceDesc(item1: MealItem, item2: MealItem): number { return (item2.confidenceScore ?? 0) - (item1.confidenceScore ?? 0); +} + +export function logMealEvent(type: string, meal: Meal | undefined, deviceInfo: DeviceInfo | undefined, data: unknown = {}) { + MyDataHelps.trackCustomEvent({ + eventType: "meal-event", + properties: { + type: type, + data: data, + meal: meal?.id, + platform: deviceInfo?.platform, + deviceId: deviceInfo?.identifier + }, + }); } \ No newline at end of file