Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions scope_shared/scope/schemas/datetime.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"type": "object",
"properties": {
"date": {
"type": "string",
"pattern": "^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T00:00:00(|.[0]*)Z$",
"$comment": "Allows 'YYYY-MM-DDT00:00:00Z' and 'YYYY-MM-DDT00:00:00.[0]*Z'"
},
"type": "string",
"pattern": "^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$",
"$comment": "Allows 'YYYY-MM-DDT00:00:00Z' and 'YYYY-MM-DDT00:00:00.[0]*Z'"
},
"datetime": {
"type": "string",
"pattern": "^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(|.[0-9]*)Z$",
Expand Down
6 changes: 2 additions & 4 deletions web_registry/src/components/PatientDetail/PatientCard.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import EditIcon from '@mui/icons-material/Edit';
import { Button, Divider, Grid, LinearProgress, Snackbar, Typography } from '@mui/material';
import withTheme from '@mui/styles/withTheme';
import { format } from 'date-fns';
import { action, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import React, { FunctionComponent, useEffect, useState } from 'react';
import { formatDateOnly } from 'shared/time';
import { IPatientProfile, KeyedMap } from 'shared/types';
import LabeledField from 'src/components/common/LabeledField';
import { EditPatientProfileDialog } from 'src/components/PatientDetail/PatientProfileDialog';
Expand Down Expand Up @@ -118,9 +118,7 @@ export const PatientCard: FunctionComponent<IPatientCardProps> = observer((props
<Grid item>
<LabeledField
label="dob"
value={
!!patient.profile.birthdate ? formatDateOnly(patient.profile.birthdate, 'MM/dd/yyyy') : '--'
}
value={!!patient.profile.birthdate ? format(patient.profile.birthdate, 'MM/dd/yyyy') : '--'}
/>
<LabeledField label="age" value={patient.age >= 0 ? patient.age : '--'} />
<LabeledField label="sex" value={patient.profile.sex} />
Expand Down
19 changes: 2 additions & 17 deletions web_registry/src/components/PatientDetail/PatientProfileDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
patientRaceValues,
patientSexValues,
} from 'shared/enums';
import { toLocalDateOnly, toUTCDateOnly } from 'shared/time';
import { IPatientProfile, IProviderIdentity } from 'shared/types';
import { GridDateField, GridDropdownField, GridMultiSelectField, GridTextField } from 'src/components/common/GridField';
import StatefulDialog from 'src/components/common/StatefulDialog';
Expand Down Expand Up @@ -187,28 +186,14 @@ export const AddPatientProfileDialog: FunctionComponent<IAddPatientProfileDialog
export const EditPatientProfileDialog: FunctionComponent<IEditPatientProfileDialogProps> = observer((props) => {
const { profile, open, error, loading, onClose, onSavePatient, careManagers } = props;

const state = useLocalObservable<IPatientProfile>(() => {
const existingProfile = { ...profile };

if (profile.birthdate != undefined) {
existingProfile.birthdate = toLocalDateOnly(profile.birthdate);
}

return existingProfile;
});
const state = useLocalObservable<IPatientProfile>(() => ({ ...profile }));

const onValueChange = action((key: string, value: any) => {
(state as any)[key] = value;
});

const onSave = action(() => {
const updatedProfile = { ...state };

if (state.birthdate != undefined) {
updatedProfile.birthdate = toUTCDateOnly(state.birthdate);
}

onSavePatient(updatedProfile);
onSavePatient({ ...state });
});

const onCareManagerChange = action((name: string) => {
Expand Down
10 changes: 1 addition & 9 deletions web_registry/src/components/PatientDetail/SessionInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
referralStatusValues,
sessionTypeValues,
} from 'shared/enums';
import { toLocalDateOnly, toUTCDateOnly, clearTime } from 'shared/time';
import { clearTime } from 'shared/time';
import { ICaseReview, IReferralStatus, ISession, ISessionOrCaseReview, KeyedMap } from 'shared/types';
import ActionPanel, { IActionButton } from 'src/components/common/ActionPanel';
import {
Expand Down Expand Up @@ -406,9 +406,6 @@ export const SessionInfo: FunctionComponent = observer(() => {
state.open = true;
state.isNew = false;
state.entryType = 'Session';
if (!!session && session.date) {
state.session.date = toLocalDateOnly(session.date);
}

_copySessionToState(session);
});
Expand All @@ -420,9 +417,6 @@ export const SessionInfo: FunctionComponent = observer(() => {
state.open = true;
state.isNew = false;
state.entryType = 'Case Review';
if (!!review && review.date) {
state.review.date = toLocalDateOnly(review.date);
}
});

const onSave = action(async () => {
Expand Down Expand Up @@ -451,7 +445,6 @@ export const SessionInfo: FunctionComponent = observer(() => {
)
.filter((rs) => rs.referralStatus != 'Not Referred');

updatedSession.date = toUTCDateOnly(session.date);
updatedSession.billableMinutes = Number(session.billableMinutes);

if (isNew) {
Expand All @@ -461,7 +454,6 @@ export const SessionInfo: FunctionComponent = observer(() => {
}
} else if (entryType == 'Case Review') {
const updatedReview = { ...review };
updatedReview.date = toUTCDateOnly(review.date);

if (isNew) {
await currentPatient.addCaseReview(updatedReview);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Grid } from '@mui/material';
import withTheme from '@mui/styles/withTheme';
import { GridCellParams, GridColDef, GridColumnHeaderParams, GridRowParams } from '@mui/x-data-grid';
import { format } from 'date-fns';
import React, { FunctionComponent } from 'react';
import { CaseReviewEntryType, SessionType } from 'shared/enums';
import { formatDateOnly } from 'shared/time';
import { ICaseReview, IReferralStatus, ISession, ISessionOrCaseReview, isSession, KeyedMap } from 'shared/types';
import { Table } from 'src/components/common/Table';
import styled from 'styled-components';
Expand Down Expand Up @@ -161,7 +161,7 @@ export const SessionReviewTable: FunctionComponent<ISessionReviewTableProps> = (

const getSessionData = (session: ISession): ISessionTableData => ({
id: session.sessionId || '--',
date: `${formatDateOnly(session.date, 'MM/dd/yy')}`,
date: `${format(session.date, 'MM/dd/yy')}`,
type: session.sessionType,
billableMinutes: session.billableMinutes,
flag: 'TBD',
Expand All @@ -174,7 +174,7 @@ export const SessionReviewTable: FunctionComponent<ISessionReviewTableProps> = (

const getReviewData = (review: ICaseReview): ISessionTableData => ({
id: review.caseReviewId || '--',
date: `${formatDateOnly(review.date, 'MM/dd/yy')}`,
date: `${format(review.date, 'MM/dd/yy')}`,
type: 'Case Review',
billableMinutes: NA,
flag: 'TBD',
Expand Down
9 changes: 5 additions & 4 deletions web_registry/src/components/PatientDetail/TreatmentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const TreatmentInfo: FunctionComponent = observer(() => {
const latestGadScore = getLatestScore(currentPatient?.assessmentLogs, 'gad-7');

const latestSessionDate = currentPatient.latestSession?.date;
const currentMedications = currentPatient.latestSession?.currentMedications;
const currentMedications = currentPatient.latestSession?.currentMedications || '';
const behavioralStrategiesUsedFlags: BehavioralStrategyChecklistFlags = {
'Behavioral Activation': false,
'Motivational Interviewing': false,
Expand Down Expand Up @@ -49,9 +49,10 @@ export const TreatmentInfo: FunctionComponent = observer(() => {

const behavioralStrategiesUsed = behavioralStrategiesUsedList.join('\n');

const referrals = currentPatient.latestSession?.referrals
.map((ref) => `${ref.referralType} - ${ref.referralStatus}`)
.join('\n');
const referrals =
currentPatient.latestSession?.referrals
.map((ref) => `${ref.referralType} - ${ref.referralStatus}`)
.join('\n') || '';


const loading =
Expand Down
1 change: 0 additions & 1 deletion web_shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"amazon-cognito-identity-js": "^5.2.7",
"axios": "^0.21.1",
"date-fns": "^2.17.0",
"date-fns-tz": "^1.2.2",
"lodash": "^4.17.21",
"lorem-ipsum": "^2.0.3",
"mobx": "6.0.x"
Expand Down
9 changes: 6 additions & 3 deletions web_shared/serviceBase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { handleDates } from 'shared/time';
import { handleRequestDates, handleResponseDates } from 'shared/time';
import { KeyedMap } from 'shared/types';

export interface IServiceBase {
Expand Down Expand Up @@ -36,15 +36,15 @@ export class ServiceBase implements IServiceBase {
};

const handleResponse = (response: AxiosResponse) => {
handleDates(response.data);
handleResponseDates(response.data);
handleDocuments(response.data);

return response;
};

const handleError = (error: AxiosError) => {
if (error.response?.status == 409 && error.response != undefined) {
handleDates(error.response?.data);
handleResponseDates(error.response?.data);
handleDocuments(error.response?.data);
}

Expand All @@ -62,6 +62,9 @@ export class ServiceBase implements IServiceBase {
delete request.data[docName]._id;
}
}

handleRequestDates(request.data);

return request;
};

Expand Down
57 changes: 34 additions & 23 deletions web_shared/time.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
import { format, parseISO, setHours, setMilliseconds, setMinutes, setSeconds } from 'date-fns';
import { utcToZonedTime } from 'date-fns-tz';
import { format, parse, parseISO, setHours, setMilliseconds, setMinutes, setSeconds } from 'date-fns';
import { FollowupSchedule } from 'shared/enums';

export const clearTime = (date: Date) => {
return setMilliseconds(setSeconds(setMinutes(setHours(date, 0), 0), 0), 0);
};

// Takes the "date" type from service and converts it to a formatted date only string
export const formatDateOnly = (date: Date, formatter: string = 'MM/dd/yy') => {
return format(toLocalDateOnly(date), formatter);
// TODO: Remove after migration is complete
const isoDateTimeFormat =
/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(|.[0-9]*)$/;
const isoDateTimeFormatNew =
/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(|.[0-9]*)Z$/;
const justDateFormat = /^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
const dateFormat = 'yyyy-MM-dd';
const dateFieldSuffix = 'date';

const isIsoDateTimeString = (value: any): boolean => {
return !!value && typeof value === 'string' && (isoDateTimeFormat.test(value) || isoDateTimeFormatNew.test(value));
};

// Takes the "date" type from service and converts it to a local date only object
export const toLocalDateOnly = (date: Date) => {
return utcToZonedTime(date, '+00');
const isJustDateString = (value: any): boolean => {
return !!value && typeof value === 'string' && justDateFormat.test(value);
};

// Takes the local date only object and converts to service's "date" type
export const toUTCDateOnly = (date: Date) => {
// TODO: Investigate new Intl APIs for extracting current timezones
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0));
const isInstanceOfDate = (value: any): boolean => {
return Object.prototype.toString.call(value) === '[object Date]';
};

// TODO: Remove after migration is complete
const isoDateFormat =
/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(|.[0-9]*)$/;
const isoDateFormatNew =
/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(|.[0-9]*)Z$/;
export const handleResponseDates = (body: any) => {
if (body === null || body === undefined || typeof body !== 'object') {
return body;
}

const isIsoDateString = (value: any): boolean => {
return !!value && typeof value === 'string' && (isoDateFormat.test(value) || isoDateFormatNew.test(value));
for (const key of Object.keys(body)) {
const value = body[key];
if (isIsoDateTimeString(value)) {
body[key] = parseISO(value);
} else if (isJustDateString(value)) {
body[key] = parse(value, dateFormat, new Date(0, 0, 0, 0, 0, 0));
} else if (typeof value === 'object') {
handleResponseDates(value);
}
}
};

export const handleDates = (body: any) => {
export const handleRequestDates = (body: any) => {
if (body === null || body === undefined || typeof body !== 'object') {
return body;
}

for (const key of Object.keys(body)) {
const value = body[key];
if (isIsoDateString(value)) {
body[key] = parseISO(value);
if (isInstanceOfDate(value) && key.toLowerCase().endsWith(dateFieldSuffix)) {
body[key] = format(value, dateFormat);
} else if (typeof value === 'object') {
handleDates(value);
handleRequestDates(value);
}
}
};
Expand Down
5 changes: 0 additions & 5 deletions web_shared/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ crypto-js@^4.1.1:
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==

date-fns-tz@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.2.2.tgz#89432b54ce3fa7d050a2039e997e5b6a96df35dd"
integrity sha512-vWtn44eEqnLbkACb7T5G5gPgKR4nY8NkNMOCyoY49NsRGHrcDmY2aysCyzDeA+u+vcDBn/w6nQqEDyouRs4m8w==

date-fns@^2.17.0:
version "2.27.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.27.0.tgz#e1ff3c3ddbbab8a2eaadbb6106be2929a5a2d92b"
Expand Down