Skip to content

Commit

Permalink
Some glucose related component updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
greinard committed Sep 18, 2024
1 parent a911b4b commit 3ec7af2
Show file tree
Hide file tree
Showing 30 changed files with 590 additions and 271 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@careevolution/mydatahelps-ui",
"version": "2.36.0",
"version": "2.36.1-GlucoseUpdates.0",
"description": "MyDataHelps UI Library",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ export interface GlucoseChartPreviewData {
sleepMinutes: number | undefined;
}

export const previewData = (previewState: GlucoseChartPreviewState, date: Date): GlucoseChartPreviewData => {
export const previewData = async (previewState: GlucoseChartPreviewState, date: Date): Promise<GlucoseChartPreviewData> => {
if (previewState === 'no data') {
return {
glucose: [],
steps: [],
sleepMinutes: undefined
};
} else if (previewState === 'with data') {
}
if (previewState === 'with data') {
return {
glucose: generateGlucose(date),
glucose: await generateGlucose(date),
steps: generateSteps(date),
sleepMinutes: 385 + (Math.random() * 60)
};
Expand Down
11 changes: 6 additions & 5 deletions src/components/container/GlucoseChart/GlucoseChart.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import GlucoseChart, { GlucoseChartProps } from './GlucoseChart';
import { Card, DateRangeCoordinator, Layout } from '../../presentational';
import { Card, DateRangeTitle, Layout } from '../../presentational';
import { GlucoseChartPreviewState } from './GlucoseChart.previewData';
import { MealCoordinator } from '../../container';
import { GlucoseDayCoordinator, MealCoordinator } from '../../container';

export default {
title: 'Container/GlucoseChart',
Expand All @@ -18,7 +18,8 @@ interface GlucoseChartStoryArgs extends GlucoseChartProps {

const render = (args: GlucoseChartStoryArgs) => {
return <Layout colorScheme={args.colorScheme}>
<DateRangeCoordinator intervalType='Day' variant='rounded'>
<GlucoseDayCoordinator previewState={args.state !== 'live' ? 'all data' : undefined}>
<DateRangeTitle defaultMargin />
{!args.withMeals &&
<Card>
<GlucoseChart {...args} previewState={args.state !== 'live' ? args.state as GlucoseChartPreviewState : undefined} />
Expand All @@ -31,7 +32,7 @@ const render = (args: GlucoseChartStoryArgs) => {
</Card>
</MealCoordinator>
}
</DateRangeCoordinator>
</GlucoseDayCoordinator>
</Layout>;
};

Expand All @@ -52,7 +53,7 @@ export const Default = {
state: {
name: 'state',
control: 'radio',
options: ['loading', 'no data', 'with data']
options: ['loading', 'no data', 'with data', 'live']
},
withMeals: {
name: 'with meals'
Expand Down
36 changes: 18 additions & 18 deletions src/components/container/GlucoseChart/GlucoseChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import React, { useContext, useState } from 'react';
import './GlucoseChart.css';
import { ColorDefinition, computeBestFitGlucoseValue, getColorFromAssortment, getGlucoseReadings, getSleepMinutes, getSteps, language, Reading, resolveColor, useInitializeView } from '../../../helpers';
import { GlucoseChartPreviewState, previewData } from './GlucoseChart.previewData';
import { DateRangeContext, LayoutContext, LoadingIndicator, TimeSeriesChart } from '../../presentational';
import { add, compareAsc, format, startOfToday } from 'date-fns';
import { DateRangeContext, GlucoseStats, LayoutContext, LoadingIndicator, TimeSeriesChart } from '../../presentational';
import { add, compareAsc, format, isSameDay, startOfToday } from 'date-fns';
import { Bar, ReferenceLine } from 'recharts';
import GlucoseStats from '../../presentational/GlucoseStats';
import { FontAwesomeSvgIcon } from 'react-fontawesome-svg-icon';
import { faShoePrints } from '@fortawesome/free-solid-svg-icons';
import { MealContext } from '../../container';
import { GlucoseContext, MealContext } from '../../container';

export interface GlucoseChartProps {
previewState?: 'loading' | GlucoseChartPreviewState;
Expand All @@ -19,6 +18,7 @@ export interface GlucoseChartProps {

export default function (props: GlucoseChartProps) {
const layoutContext = useContext(LayoutContext);
const glucoseContext = useContext(GlucoseContext);
const dateRangeContext = useContext(DateRangeContext);
const mealContext = useContext(MealContext);

Expand All @@ -31,6 +31,10 @@ export default function (props: GlucoseChartProps) {
let meals = mealContext?.meals ?? [];
let selectedMeal = mealContext?.selectedMeal;

const getGlucoseReadingsFromContext = () => {
return Promise.resolve(glucoseContext?.readings?.filter(reading => isSameDay(selectedDate, reading.timestamp)) ?? []);
};

useInitializeView(() => {
setLoading(true);

Expand All @@ -39,14 +43,16 @@ export default function (props: GlucoseChartProps) {
}

if (props.previewState) {
setGlucose(previewData(props.previewState, selectedDate).glucose)
setSteps(previewData(props.previewState, selectedDate).steps);
setSleepMinutes(previewData(props.previewState, selectedDate).sleepMinutes);
setLoading(false);
previewData(props.previewState, selectedDate).then(({ glucose, steps, sleepMinutes }) => {
setGlucose(glucose)
setSteps(steps);
setSleepMinutes(sleepMinutes);
setLoading(false);
});
return;
}

let glucoseReadingLoader = getGlucoseReadings(selectedDate);
let glucoseReadingLoader = glucoseContext?.readings ? getGlucoseReadingsFromContext() : getGlucoseReadings(selectedDate);
let stepsLoader = getSteps(selectedDate);
let sleepMinutesLoader = getSleepMinutes(selectedDate);
Promise.all([glucoseReadingLoader, stepsLoader, sleepMinutesLoader]).then(results => {
Expand All @@ -67,12 +73,6 @@ export default function (props: GlucoseChartProps) {
return reading.timestamp >= minDate && reading.timestamp <= maxDate;
}) ?? [];

let avgGlucose: number | undefined;
if (filteredGlucose.length > 0) {
let glucoseValues = filteredGlucose.map(reading => reading.value);
avgGlucose = glucoseValues.reduce((s, a) => s + a, 0) / glucoseValues.length;
}

let filteredMeals = meals.filter(meal => {
if (filteredGlucose.length === 0) return false;
if (!selectedMeal) return true;
Expand Down Expand Up @@ -182,13 +182,13 @@ export default function (props: GlucoseChartProps) {
}
}}
>
{avgGlucose &&
{glucoseContext?.recentAverage !== undefined &&
<ReferenceLine
y={avgGlucose}
y={glucoseContext.recentAverage}
stroke={resolveColor(layoutContext.colorScheme, props.averageGlucoseLineColor) ?? 'var(--mdhui-color-primary)'}
strokeWidth={1.5}
label={{
value: avgGlucose.toFixed(0),
value: glucoseContext.recentAverage.toFixed(0),
fill: resolveColor(layoutContext.colorScheme, props.averageGlucoseLineColor) ?? 'var(--mdhui-color-primary)',
fontSize: 9,
position: 'insideTopRight',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { generateGlucose, getDayKey, predictableRandomNumber, Reading } from '../../../helpers';
import { add, isSameDay } from 'date-fns';

export type GlucoseDayCoordinatorPreviewState = 'no data' | 'some data' | 'all data';

export const previewData = async (previewState: GlucoseDayCoordinatorPreviewState, startDate: Date, endDate: Date): Promise<Reading[]> => {
if (previewState === 'no data') {
return [];
}
if (previewState === 'some data') {
let readings = await generateGlucose(startDate, endDate);

let currentDate = startDate;
while (currentDate <= endDate) {
if (await predictableRandomNumber(getDayKey(currentDate)) % 5 === 0) {
readings = readings.filter(reading => !isSameDay(currentDate, reading.timestamp));
}
currentDate = add(currentDate, { days: 1 });
}

return readings;
}
if (previewState === 'all data') {
return generateGlucose(startDate, endDate);
}
return [];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Meta, StoryObj } from '@storybook/react';
import React from 'react'
import { DateRangeTitle, Layout } from '../../presentational'
import GlucoseDayCoordinator from './GlucoseDayCoordinator';
import { GlucoseDayCoordinatorPreviewState } from "./GlucoseDayCoordinator.previewData";

type GlucoseDayCoordinatorStoryArgs = React.ComponentProps<typeof GlucoseDayCoordinator> & {
colorScheme: 'auto' | 'light' | 'dark'
state: GlucoseDayCoordinatorPreviewState | 'live';
};

const meta: Meta<GlucoseDayCoordinatorStoryArgs> = {
title: 'Container/GlucoseDayCoordinator',
component: GlucoseDayCoordinator,
parameters: {
layout: 'fullscreen'
},
render: args => {
return <Layout colorScheme={args.colorScheme}>
<GlucoseDayCoordinator {...args} previewState={args.state !== 'live' ? args.state as GlucoseDayCoordinatorPreviewState : undefined}>
<DateRangeTitle defaultMargin />
</GlucoseDayCoordinator>
</Layout>;
}
};
export default meta;

type Story = StoryObj<GlucoseDayCoordinatorStoryArgs>;

export const Default: Story = {
args: {
colorScheme: 'auto',
state: 'all data'
},
argTypes: {
colorScheme: {
name: 'color scheme',
control: 'radio',
options: ['auto', 'light', 'dark']
},
state: {
name: 'state',
control: 'radio',
options: ['no data', 'some data', 'all data', 'live']
}
}
};

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { createContext, DependencyList, useContext, useState } from 'react';
import { computeGlucoseReadingRanges, computeGlucoseReadingRecentAverage, getDayKey, getGlucoseReadings, Reading, ReadingRange } from '../../../helpers';
import { add, startOfToday } from 'date-fns';
import { DateRangeContext, DateRangeCoordinator, SparkRangeChart, SparkRangeChartRange } from '../../presentational';
import { WeeklyDayNavigator } from '../../container';
import { GlucoseDayCoordinatorPreviewState, previewData } from './GlucoseDayCoordinator.previewData';

export interface GlucoseDayCoordinatorProps {
previewState?: GlucoseDayCoordinatorPreviewState;
children?: React.ReactNode;
innerRef?: React.Ref<HTMLDivElement>;
}

export interface GlucoseContext {
readings?: Reading[];
recentAverage?: number;
}

export const GlucoseContext = createContext<GlucoseContext | null>(null);

export default function (props: GlucoseDayCoordinatorProps) {
const [glucoseReadings, setGlucoseReadings] = useState<Reading[]>();
const [glucoseRanges, setGlucoseRanges] = useState<ReadingRange[]>();
const [recentAverage, setRecentAverage] = useState<number>();

const loadData = async (startDate: Date, endDate: Date): Promise<void> => {
const readings = props.previewState ?
await previewData(props.previewState, startDate, endDate) :
await getGlucoseReadings(startDate, endDate);

setGlucoseReadings(readings);
setGlucoseRanges(computeGlucoseReadingRanges(readings));
setRecentAverage(computeGlucoseReadingRecentAverage(readings, add(startDate, { days: 14 })));
};

const glucoseRangeLookup = glucoseRanges?.reduce((lookup, range) => {
const dayKey = getDayKey(range.date);
lookup[dayKey] = range;
return lookup;
}, {} as { [key: string]: ReadingRange });

const dayRenderer = (dayKey: string): React.JSX.Element | null => {
let sparkRanges: SparkRangeChartRange[] = [];
if (glucoseRangeLookup?.hasOwnProperty(dayKey)) {
sparkRanges.push({ ...(glucoseRangeLookup[dayKey]) });
}
return <div style={{ paddingTop: '4px' }}>
<SparkRangeChart domain={[0, 240]} ranges={sparkRanges} reference={recentAverage} />
</div>;
};

return <div ref={props.innerRef}>
<GlucoseContext.Provider value={{ readings: glucoseReadings, recentAverage: recentAverage }}>
<DateRangeCoordinator initialIntervalStart={startOfToday()} intervalType="Day" useCustomNavigator={true}>
<CustomNavigator
loadData={loadData}
dayRenderer={dayRenderer}
dependencies={[props.previewState]}
/>
{glucoseRanges && props.children}
</DateRangeCoordinator>
</GlucoseContext.Provider>
</div>;
}

interface CustomNavigatorProps {
loadData: (startDate: Date, endDate: Date) => Promise<void>;
dayRenderer: (dayKey: string) => React.JSX.Element | null;
dependencies?: DependencyList;
}

function CustomNavigator(props: CustomNavigatorProps) {
const dateRangeContext = useContext(DateRangeContext)

return <WeeklyDayNavigator
selectedDate={dateRangeContext!.intervalStart}
loadData={props.loadData}
dayRenderer={props.dayRenderer}
dependencies={props.dependencies}
/>;
}
2 changes: 2 additions & 0 deletions src/components/container/GlucoseDayCoordinator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default, GlucoseContext } from './GlucoseDayCoordinator';
export { GlucoseDayCoordinatorPreviewState } from './GlucoseDayCoordinator.previewData';
Loading

0 comments on commit 3ec7af2

Please sign in to comment.