Vitals module
Vitals
API URL
/api/patients/{patientId}/vitalsigns
GET response
{ author: "Robert Tweed", dateCreated: 1558703797000, diastolicBP: 92, heartRate: 92, levelOfConsciousness: "Pain", newsScore: 13, oxygenSaturation: 77, oxygenSupplemental: "true", respirationRate: 7, source: "ethercis", sourceId: "ethercis-d2ee07eb-e568-44f7-9c76-45c3e677b8b0", systolicBP: 92, temperature: 38, }
Component structure
import React from "react"; import { DateField, TextField } from "react-admin"; import ListTemplate from "../../../core/common/ResourseTemplates/ListTemplate"; import VitalsCreate from "./VitalsCreate"; import VitalsEdit from "./VitalsEdit"; import VitalsShow from "./VitalsShow"; import VitalsChart from "./VitalsChart"; import VitalsDatagridRow from "./fragments/DatagridRow"; const VitalsList = ({ classes, ...rest }) => ( <ListTemplate create={VitalsCreate} edit={VitalsEdit} show={VitalsShow} chartBlock={VitalsChart} CustomRow={VitalsDatagridRow} resourceUrl="vitalsigns" title="Vitals" hasChart={true} isCustomDatagrid={true} {...rest} > <DateField label="#" source="number" /> <DateField label="Date" source="dateCreate" /> <TextField label="NEWS Score" source="newsScore" /> <TextField label="Source" source="source" /> </ListTemplate> ); export default VitalsList;
Vitals Detail
API URL
/api/patients/{patientId}/vitalsigns/{sourceId}
GET response
{ author: "Robert Tweed", dateCreated: 1560155742000, diastolicBP: 92, heartRate: 92, levelOfConsciousness: "Voice", newsScore: 11, oxygenSaturation: 95, oxygenSupplemental: true, respirationRate: 11, source: "ethercis", sourceId: "ethercis-f9e52b2f-a5b9-471b-af1f-f75dcb3dd237", systolicBP: 92, temperature: 37, }
Component structure
import React from "react"; import ShowTemplate from "../../../core/common/ResourseTemplates/ShowTemplate"; import Form from "./fragments/Form"; const VitalsShow = ({ classes, ...rest }) => ( <ShowTemplate pageTitle="Vitals" {...rest}> <Form isDetailsPage={true} {...rest} /> </ShowTemplate> ); export default VitalsShow;
Vitals Detail Edit Form
API URL
/api/patients/{patientId}/vitalsigns
PUT data
{ author: "Robert Tweed", dateCreate: 1560428887, diastolicBP: 92, heartRate: 92, id: "ethercis-f9e52b2f-a5b9-471b-af1f-f75dcb3dd237", levelOfConsciousness: "Voice", newsScore: "11", oxygenSaturation: 95, oxygenSupplemental: true, respirationRate: 11, source: "ethercis", systolicBP: 92, temperature: 37, userId: "9999999801", }
Component structure
Vitals Create Form
API URL
/api/patients/{patientId}/vitalsigns
POST data
{ author: "Robert Tweed", dateCreate: 1560429237, diastolicBP: "98", heartRate: "98", levelOfConsciousness: "Voice", newsScore: "11", oxygenSaturation: "95", oxygenSupplemental: true, respirationRate: "13", systolicBP: "98", temperature: "37", userId: "9999999801", }
Component structure
import React from "react"; import CreateTemplate from "../../../core/common/ResourseTemplates/CreateTemplate"; import Form from "./fragments/Form"; const VitalsCreate = props => ( <CreateTemplate isCustom={true} blockTitle="Vitals" {...props}> <Form isCreate={true} {...props} /> </CreateTemplate> ); export default VitalsCreate;
Vitals Chart
import React, { Component } from "react"; import get from "lodash/get"; import moment from "moment"; import { ResponsiveContainer, LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line } from "recharts"; import { connect } from 'react-redux'; import { Toolbar } from "react-admin"; import { withStyles } from "@material-ui/core/styles"; import Typography from '@material-ui/core/Typography'; import CreateButton from "../../../core/common/Buttons/CreateButton"; const styles = { chartBlock: { width: '100%', height: 500, }, toolbar: { display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', } }; function getTooltipFormatter(value, name) { const row = get(name, 'props.children', null); if (!row) { return null; } const nameArray = row.split(', '); const parameter = nameArray[0]; const units = nameArray[1]; return ( <span> <Typography variant="h1">{parameter}:</Typography> <Typography> {value}, {units}</Typography> </span> ); } class VitalsChart extends Component { state = { disabledLines: [], }; toggleLine = e => { const { disabledLines } = this.state; const dataKey = e.dataKey; let newDisabledLinesArray = []; if (disabledLines.indexOf(dataKey) !== -1) { newDisabledLinesArray = disabledLines.filter(item => item !== dataKey); } else { newDisabledLinesArray = disabledLines.concat(dataKey); } this.setState({ disabledLines: newDisabledLinesArray }) }; onPointClick = e => { const { history } = this.props; const sourceId = get(e, 'payload.sourceId', null); const detailsUrl = '/vitalsigns/' + sourceId; history.push(detailsUrl); }; render() { const { classes, vitalsList, history, createUrl } = this.props; const { disabledLines } = this.state; const vitalsListArray = Object.values(vitalsList); let chartData = []; for (let i = 0, n = vitalsListArray.length; i < n; i++) { let item = vitalsListArray[i]; chartData.push({ name: moment(item.dateCreate).format('MM-DD-YYYY'), diastolicBP: item.diastolicBP, heartRate: item.heartRate, oxygenSaturation: item.oxygenSaturation, respirationRate: item.respirationRate, systolicBP: item.systolicBP, temperature: item.temperature, sourceId: item.sourceId, }); } const DOT_RADIUS = 8; const STROKE_WIDTH = 4; const linesArray = [ { dataKey: "respirationRate", color: "#99C78C", label: <Typography>Respiration Rate, resps/min</Typography> }, { dataKey: "oxygenSaturation", color: "#E18FC0", label: <Typography>Oxygen Saturation, %</Typography> }, { dataKey: "heartRate", color: "#ACC2D7", label: <Typography>Heart Rate, bpm</Typography> }, { dataKey: "systolicBP", color: "#EABA97", label: <Typography>Systolic BP, mmHg</Typography> }, { dataKey: "diastolicBP", color: "#99D9DE", label: <Typography>Diastolic BP, mmHg</Typography> }, { dataKey: "temperature", color: "#E3A08F", label: <Typography>Temperature, C</Typography> }, ]; return ( <div className={classes.chartBlock}> <ResponsiveContainer width={'99%'}> <LineChart data={chartData} margin=> <XAxis dataKey="name" tick= /> <YAxis tick= /> <Tooltip formatter={(value, name) => getTooltipFormatter(value, name)} labelFormatter={function(value) { return ( <Typography>Date: {value}</Typography> ); }} /> <Legend payload={linesArray.map(item => ({ dataKey: item.dataKey, color: item.color, value: item.label, }))} onClick={e => this.toggleLine(e)} /> <CartesianGrid stroke="#e5e5e5" strokeDasharray="5 5"/> { linesArray.map((item, key) => { if (disabledLines.indexOf(item.dataKey) !== -1) { return null; } return ( <Line key={key} type="monotone" name={item.label} dataKey={item.dataKey} stroke={item.color} activeDot= strokeWidth={STROKE_WIDTH} /> ) }) } </LineChart> </ResponsiveContainer> <Toolbar className={classes.toolbar}> <CreateButton history={history} redirectPath={createUrl} /> </Toolbar> </div> ); } }; const mapStateToProps = state => { return { vitalsList: get(state, 'admin.resources.vitalsigns.data', []), } }; export default connect(mapStateToProps, null)(withStyles(styles)(VitalsChart));
Vitals Popover
import React from "react"; import get from "lodash/get"; import { withStyles } from '@material-ui/core/styles'; import Popover from "@material-ui/core/Popover"; import Typography from "@material-ui/core/Typography"; import rangeLine from "../../../images/range-line.jpeg"; import { rangeLineSettings } from "./settings"; const styles = theme => ({ popover: { pointerEvents: 'none', }, paper: { display: "block", width: 490, padding: 0, margin: 0, borderRadius: 0, }, blockTitle: { display: "flex", alignItems: "center", height: 49, color: theme.palette.paperColor, backgroundColor: theme.palette.secondaryMainColor, paddingLeft: 15, }, title: { color: theme.palette.paperColor, fontSize: 18, fontWeight: 700, }, rangeAxisItemTop: { display: "flex", flexDirection: "column", justifyContent: "flex-start", width: 70, height: 65, float: "left" }, rangeAxisItemBottom: { display: "flex", flexDirection: "column", justifyContent: "flex-end", width: 70, height: 65, float: "left" }, content: { margin: 10, height: 60, }, axis: { display: "flex", flexDirection: "row", justifyContent: "space-between", backgroundImage: `url(${rangeLine})`, backgroundRepeat: "no-repeat", backgroundPosition: "center", backgroundSize: '490px 10px', }, }); const RangeLinePopover = ({ classes, anchorEl, open, handleClose, label, model }) => { const rangeLineValues = get(rangeLineSettings, model, null); return ( <Popover open={open} className={classes.popover} classes= anchorEl={anchorEl} onClose={handleClose} anchorOrigin= transformOrigin= > <div> <div className={classes.blockTitle}> <Typography className={classes.title}>{label}</Typography> </div> <div className={classes.content}> <div className={classes.axis}> { rangeLineValues && rangeLineValues.map(item => { return ( <div className={classes[item.position]}> <Typography variant="body2">{item.label}</Typography> </div> ); }) } </div> </div> </div> </Popover> ); }; export default withStyles(styles)(RangeLinePopover);