Skip to content

Instantly share code, notes, and snippets.

@Platekun
Created December 3, 2019 18:31
Show Gist options
  • Save Platekun/8572f01cdbfe15a6aa652942a531e148 to your computer and use it in GitHub Desktop.
Save Platekun/8572f01cdbfe15a6aa652942a531e148 to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
function cuid() {}
function sweetAlert() {}
function moment() {}
function domToImage() {}
function api() {}
function createProgressFormDraft() {}
function adjustMonthYearDate() {}
function formatIntoValidServerDate () {}
function mapReferralSource() {}
function setIdIfOnTest() {}
function formatDateToMMDDDD() {}
function validateCurrentMedicationNameAndDosage() {}
function validateCurrentMedicationName() {}
function validateCurrentMedicationDosage() {}
function validateSurgeryTableName() {}
function validateSurgeryTableDate() {}
function validateSurgeryTableNameAndDate() {}
/** ******************************************************************
* CONSTANTS *
******************************************************************* */
const TODAY = new Date();
const REDIRECT_LOCATION = 'https://www.getluna.com/';
const TIME_TO_WAIT_UNTIL_DRAFT_IS_SAVED = 6000;
const ACCEPTANCES = [
"I acknowledge that I have received, read, understand, and agree to the above Terms of Use and I am the patient, the patient's legal representative, or am duly authorized by the patient as the patient's general agent to execute this document and accept and agree to its terms, including but not limited to consent to rehabilitation procedures, financial agreement, assignment of insurance benefits, cancellation and no show policy, disclosure of health information, and consent to share medical and/or billing information with family members.",
'I acknowledge that I have received, read, understand, and agree to the above Privacy Policy.',
'I authorize the release of any medical or other information as necessary to process medical insurance claims. I authorize payment of medical benefits directly to Luna for services rendered.',
'I authorize Luna and/or my physical therapist to notify my physician, if any, that he/she is treating me in the case that plan of care approval or prescription is required for continued care.',
'I consent to be contacted by email and/or text message, and authorize the use of my information by Luna for purposes of patient communication and/or marketing related services (the latter of which can be opted out at any time).',
'In the case that I have a guardian, I authorize Luna to speak with my guardian regarding my care and billing. In the case that this care is for a minor, I certify that I am the parent or legal guardian of the patient and hereby authorize Luna to provide physical therapy treatment to the minor patient without my presence.',
'I acknowledge and certify my patient history intake form is accurate and complete.'
];
const BLANK_INTAKE_FORM_ATTRIBUTES = {
primary_care_physician_name: null,
last_exam_date_at: null,
has_infections: null,
infection_description: null,
care_physician_referral_source: null,
referred_care_physician_name: null,
is_allergic_to_latex: null,
quality_of_life: null,
pain_scale: null,
medications_attributes: [],
surgeries_attributes: [],
pain_spot_ids: [],
disease_ids: []
};
/** ******************************************************************
* VALIDATION UTILS *
******************************************************************* */
function isString(value) {
return value instanceof String || typeof value === 'string';
}
function isNotEmpty(value) {
return value !== '' && value !== null && value !== undefined;
}
function isDate(value) {
return value instanceof Date && !Number.isNaN(value.getTime());
}
function beforeDate(date, maxDate) {
return date instanceof Date && date.getTime() <= maxDate.getTime();
}
function filterPainScaleQuestion(question) {
return !question.content.toLowerCase().includes('scale');
}
/** ******************************************************************
* LOGIC HELPERS *
******************************************************************* */
/**
* TODO: Refactor once getInvalidControlsForSegment is refactored. This method is not needed
*/
function Controls(ctx) {
const {
primaryCarePhysician,
dateOfLastExam,
hasInfectionsOrPrecautions,
pleaseExplain,
referralSource,
pleaseExplainAnotherPhysician,
currentMedicationList,
surgicalHistory,
overallQualityOfLife,
bodyChartPoints,
functionalLimitations,
aggravatingActivities
} = ctx;
return {
primaryCarePhysician,
dateOfLastExam,
hasInfectionsOrPrecautions,
pleaseExplain,
referralSource,
pleaseExplainAnotherPhysician,
currentMedicationList,
surgicalHistory,
overallQualityOfLife,
bodyChartPoints,
functionalLimitations,
aggravatingActivities
};
}
function referredDoctor(referralSource) {
if (referralSource === 'primary') {
return 'referralSourcePrimaryCarePhysician';
}
if (referralSource === 'custom') {
return 'referralSourceAnotherPhysician';
}
return 'doNotHaveAReferral';
}
function draftExpired(draftResponse) {
return draftResponse.form.persist_draft_until === 0;
}
function hasPreviousFormData(previousIntakeFormResponse) {
return !!previousIntakeFormResponse.form;
}
function validateActivity(activityValue) {
return (
isString(activityValue) &&
isNotEmpty(activityValue) &&
isNotEmpty(activityValue.trim())
);
}
function sortByOrder(x, y) {
return x.order - y.order;
}
function findAnswerIndexByLabel({ label, collection }) {
return collection.findIndex(x => x.label === label);
}
function shouldIgnoreFormValidation(validationDialogActor) {
return validationDialogActor === null;
}
/** ******************************************************************
* MODELS *
******************************************************************* */
function BlankMedication(idx) {
return {
id: cuid(),
valid: true,
destroyed: false,
controls: {
name: {
id: cuid(),
value: '',
label: `Medication ListName (${idx})`,
valid: true
},
dosage: {
id: cuid(),
value: '',
label: `Medication List Dosage (${idx})`,
valid: true
}
}
};
}
function Medication({ medication, idx }) {
return {
id: cuid(),
backupId: medication.id,
valid: validateCurrentMedicationNameAndDosage(
medication.name,
medication.dosage
),
controls: {
name: {
id: cuid(),
value: medication.name,
label: `Medication List Name (${idx + 1})`,
valid: validateCurrentMedicationName(medication.name, medication.dosage)
},
dosage: {
id: cuid(),
value: medication.dosage,
label: `Medication List Dosage (${idx + 1})`,
valid: validateCurrentMedicationDosage(
medication.dosage,
medication.dosage
)
}
}
};
}
function BlankSurgery(idx) {
return {
id: cuid(),
valid: true,
destroyed: false,
controls: {
name: {
id: cuid(),
value: '',
label: `Surgical History Name (${idx})`,
valid: true
},
date: {
id: cuid(),
value: '',
label: `Surgical History Date (${idx})`,
valid: true
}
}
};
}
function Disease(disease, idx) {
return {
id: setIdIfOnTest(idx),
diseaseId: disease.id,
label: disease.name,
value: false
};
}
function Surgery({ surgery, idx }) {
return {
id: cuid(),
backupId: surgery.id,
valid: validateSurgeryTableNameAndDate(
surgery.name,
formatDateToMMDDDD(surgery.date)
),
controls: {
name: {
id: cuid(),
value: surgery.name,
label: `Surgical History Name (${idx + 1})`,
valid: validateSurgeryTableName(
surgery.name,
formatDateToMMDDDD(surgery.date)
)
},
date: {
id: cuid(),
value: formatDateToMMDDDD(surgery.date),
label: `Surgical History Date (${idx + 1})`,
valid: validateSurgeryTableDate(
surgery.name,
formatDateToMMDDDD(surgery.date)
)
}
}
};
}
function PainSpot({ point, painSpotsIds }) {
return painSpotsIds.includes(point.painSpotId)
? { ...point, selected: true, valid: true }
: point;
}
function AggravatingActivity({ activity, idx }) {
return {
id: cuid(),
backupId: activity.id,
valid: validateActivity(activity.name),
label: `Aggravating Activities (${idx + 1})`,
controls: {
activity: {
id: cuid(),
value: activity.name
},
ability: {
id: cuid(),
value: activity.ability
}
}
};
}
function BlankAggravatingActivity(idx) {
return {
id: cuid(),
valid: false,
label: `Aggravating Activities (${idx})`,
controls: {
activity: {
id: cuid(),
value: ''
},
ability: {
id: cuid(),
value: 0
}
}
};
}
function Acceptance(label) {
return {
id: cuid(),
label,
value: false,
valid: false
};
}
function AsesLefsQuestion(question, idx) {
return {
id: setIdIfOnTest(idx),
label: `Functional limitations (${idx + 1})`,
questionId: question.id,
question: question.content,
control: {
value: '',
valid: false
}
};
}
function BlankPainScaleQuestion(collection) {
const question = collection.find(x =>
x.content.toLowerCase().includes('scale')
);
return {
id: setIdIfOnTest('ases-pain-scale-question'),
questionId: question.id,
value: 5
};
}
function PainScaleQuestion(question) {
return {
id: setIdIfOnTest('ases-pain-scale-question'),
questionId: question.question_id,
backupId: question.id,
value: question.value
};
}
function OdiQuestion(question, idx) {
return {
id: setIdIfOnTest(idx),
label: `Functional limitations (${idx + 1})`,
questionId: question.id,
question: question.content,
control: {
value: '',
valid: false
},
options: OdiOptionsAndScoreEnum[question.order]
};
}
function NdiQuestion(question, idx) {
return {
id: setIdIfOnTest(idx),
label: `Functional limitations (${idx + 1})`,
questionId: question.id,
question: question.content,
control: {
value: '',
valid: false
},
options: NdiOptionsAndScoreEnum[question.order]
};
}
function AsesLefsAnswerAttribute(question, formId) {
return {
id: question.backupId,
form_id: formId,
question_id: question.questionId,
content: question.control.value.toString(),
value: question.score || 0
};
}
function AsesLefsAnswerAttributes(opts) {
const {
functionalLimitations,
painValueInLastWeeks,
typeName,
formId
} = opts;
return [
...functionalLimitations.questions.map(question =>
AsesLefsAnswerAttribute(question, formId)
),
typeName === 'ases'
? {
id: painValueInLastWeeks.backupId,
form_id: formId,
question_id: painValueInLastWeeks.questionId,
content: painValueInLastWeeks.value.toString(),
value: painValueInLastWeeks.value
}
: undefined
].filter(Boolean);
}
function NdiOdiAnswerAttribute(question, formId) {
return {
form_id: formId,
question_id: question.questionId,
id: question.backupId,
content: question.selectedQuestionLabel,
value: question.selectedScore || 0
};
}
function NdiOdiAnswerAttributes(opts) {
const { functionalLimitations, formId } = opts;
return functionalLimitations.questions.map(question =>
NdiOdiAnswerAttribute(question, formId)
);
}
function AsesLefsFunctionalLimitationQuestion({
current,
update,
replace,
typeName
}) {
let score = null;
if (update.content) {
if (typeName === 'ases') {
score = AsesFormDropdownScoresEnum[update.content];
} else {
score = LefsFormDropdownScoresEnum[update.content];
}
}
return {
...current,
backupId: update.id,
control: {
...current.control,
value: replace ? update.content : current.control.value, // or make a map to get the correct label given the answer
valid: replace ? !!update.content : current.control.valid
},
score
};
}
function NdiOdiFunctionalLimitationQuestion({ current, update, replace }) {
const selectedQuestionLabel = replace
? update.content
: current.selectedQuestionLabel;
const selectedScore = replace ? update.value : current.selectedScore;
const selectedControl = replace
? current.questionId === update.question_id && !!update.content
? current.options[
findAnswerIndexByLabel({
label: update.content,
collection: current.options
})
].id
: ''
: current.control.selected;
return {
...current,
backupId: update.id,
control: {
...current.control,
selected: selectedControl,
valid: !!selectedControl
},
selectedQuestionLabel,
selectedScore
};
}
function OnboardingFormDraft(opts) {
const {
primaryCarePhysician,
dateOfLastExam,
hasInfectionsOrPrecautions,
pleaseExplain,
referralSource,
pleaseExplainAnotherPhysician,
allergicToLatex,
currentMedicationList,
pastMedicalHistory,
surgicalHistory,
overallQualityOfLife,
bodyChartPoints,
painValueInLastSevenDays,
aggravatingActivities,
functionalLimitations,
painValueInLastWeeks,
signature,
patientInfo: {
form: {
id: formId,
type_name: typeName,
exclude_medical_information: excludeMedicalInfo,
terms_and_conditions: hasSeenTermsAndConditions
}
}
} = opts;
const painScale = painValueInLastSevenDays && painValueInLastSevenDays.value;
const patientAttributes = {
accept_terms_and_conditions: hasSeenTermsAndConditions,
signature: signature.value
};
const aggravatingActivitiesAttributes = aggravatingActivities.entries.map(
activity => ({
id: activity.backupId,
name: activity.controls.activity.value,
ability: activity.controls.ability.value
})
);
let answersAttributes;
if (typeName === 'ases' || typeName === 'lefs') {
answersAttributes = AsesLefsAnswerAttributes({
functionalLimitations,
painValueInLastWeeks,
typeName,
formId
});
} else if (typeName === 'ndi' || typeName === 'odi') {
answersAttributes = NdiOdiAnswerAttributes({
functionalLimitations,
formId
});
} else {
answersAttributes = [];
}
let intakeFormAttributes;
if (excludeMedicalInfo) {
intakeFormAttributes = BLANK_INTAKE_FORM_ATTRIBUTES;
} else {
const lastExamDateAt = formatIntoValidServerDate(dateOfLastExam.value);
const carePhysicianReferralSource = mapReferralSource(referralSource.value);
const referredCarePhysicianName = carePhysicianReferralSource
? pleaseExplainAnotherPhysician.value
: null;
const medicationAttributes = currentMedicationList.entries.map(
medication => ({
id: medication.backupId,
name: medication.controls.name.value,
dosage: medication.controls.dosage.value,
_destroy: medication.destroyed
})
);
const surgeriesAttributes = surgicalHistory.entries.map(surgery => ({
id: surgery.backupId,
name: surgery.controls.name.value,
date: formatIntoValidServerDate(
adjustMonthYearDate(surgery.controls.date.value)
),
_destroy: surgery.destroyed
}));
const painSpotIds = [
...new Set([
...bodyChartPoints.front.filter(x => x.selected).map(x => x.painSpotId),
...bodyChartPoints.back.filter(x => x.selected).map(x => x.painSpotId)
])
];
const diseaseIds = pastMedicalHistory.entries
.filter(x => x.value)
.map(x => x.diseaseId);
intakeFormAttributes = {
primary_care_physician_name: primaryCarePhysician.value,
last_exam_date_at: lastExamDateAt,
has_infections: hasInfectionsOrPrecautions.value,
infection_description: pleaseExplain.value || null,
care_physician_referral_source: carePhysicianReferralSource,
referred_care_physician_name: referredCarePhysicianName,
is_allergic_to_latex: allergicToLatex.value,
quality_of_life: overallQualityOfLife.value,
pain_scale: painValueInLastSevenDays.value,
medications_attributes: medicationAttributes,
surgeries_attributes: surgeriesAttributes,
pain_spot_ids: painSpotIds,
disease_ids: diseaseIds
};
}
return {
onboarding_form: {
pain_scale: painScale,
patient_attributes: patientAttributes,
intake_form_attributes: intakeFormAttributes,
aggravating_activities_attributes: aggravatingActivitiesAttributes,
answers_attributes: answersAttributes
}
};
}
async function OnboardingForm(ctx) {
const {
primaryCarePhysician,
dateOfLastExam,
hasInfectionsOrPrecautions,
pleaseExplain,
referralSource,
pleaseExplainAnotherPhysician,
allergicToLatex,
currentMedicationList,
pastMedicalHistory,
surgicalHistory,
overallQualityOfLife,
bodyChartPoints,
painValueInLastSevenDays,
aggravatingActivities,
functionalLimitations,
painValueInLastWeeks,
signature,
patientInfo: {
form: {
id: formId,
type_name: typeName,
exclude_medical_information: excludeMedicalInfo,
terms_and_conditions: hasSeenTermsAndConditions
}
}
} = ctx;
const signatureNode = window.document.getElementById(
'user__electronic-signature'
);
const painScale = painValueInLastSevenDays && painValueInLastSevenDays.value;
const patientAttributes = {
accept_terms_and_conditions: true,
signature: !hasSeenTermsAndConditions
? await domToImage.toPng(signatureNode)
: signature
};
const aggravatingActivitiesAttributes = aggravatingActivities.entries
.filter(activity => activity.controls.activity.value)
.map(activity => ({
id: activity.backupId,
name: activity.controls.activity.value,
ability: activity.controls.ability.value
}));
let answersAttributes;
if (typeName === 'ases' || typeName === 'lefs') {
answersAttributes = AsesLefsAnswerAttributes({
functionalLimitations,
painValueInLastWeeks,
typeName,
formId
});
} else if (typeName === 'ndi' || typeName === 'odi') {
answersAttributes = NdiOdiAnswerAttributes({
functionalLimitations,
formId
});
} else {
answersAttributes = [];
}
let intakeFormAttributes;
if (excludeMedicalInfo) {
intakeFormAttributes = BLANK_INTAKE_FORM_ATTRIBUTES;
} else {
const lastExamDateAt = formatIntoValidServerDate(dateOfLastExam.value);
const carePhysicianReferralSource = mapReferralSource(referralSource.value);
const referredCarePhysicianName =
mapReferralSource(referralSource.value) === 'custom'
? pleaseExplainAnotherPhysician.value
: null;
const medicationAttributes = currentMedicationList.entries
.map(medication => ({
id: medication.backupId,
name: medication.controls.name.value,
dosage: medication.controls.dosage.value.trim() || '0'
}))
.filter(medication => medication.name && medication.dosage);
const surgeriesAttributes = surgicalHistory.entries
.filter(
surgery => surgery.controls.name.value && surgery.controls.date.value
)
.filter(surgery => !surgery.controls.date.value.includes('_'))
.map(surgery => ({
id: surgery.backupId,
name: surgery.controls.name.value,
date: formatIntoValidServerDate(
adjustMonthYearDate(surgery.controls.date.value)
)
}));
const painSpotsIds = [
...new Set([
...bodyChartPoints.front
.filter(point => point.selected)
.map(point => point.painSpotId),
...bodyChartPoints.back
.filter(point => point.selected)
.map(point => point.painSpotId)
])
];
const diseaseIds = pastMedicalHistory.entries
.filter(disease => disease.value)
.map(disease => disease.diseaseId);
intakeFormAttributes = {
primary_care_physician_name: primaryCarePhysician.value,
last_exam_date_at: lastExamDateAt,
has_infections: hasInfectionsOrPrecautions.value,
infection_description: pleaseExplain.value || null,
care_physician_referral_source: carePhysicianReferralSource,
referred_care_physician_name: referredCarePhysicianName,
is_allergic_to_latex: allergicToLatex.value,
quality_of_life: overallQualityOfLife.value,
pain_scale: painValueInLastSevenDays.value,
medications_attributes: medicationAttributes,
surgeries_attributes: surgeriesAttributes,
pain_spot_ids: painSpotsIds,
disease_ids: diseaseIds
};
}
return {
onboarding_form: {
pain_scale: painScale,
patient_attributes: patientAttributes,
intake_form_attributes: intakeFormAttributes,
aggravating_activities_attributes: aggravatingActivitiesAttributes,
answers_attributes: answersAttributes
}
};
}
function ProgressFormDraft(opts) {
const {
aggravatingActivities,
functionalLimitations,
painValueInLastWeeks,
painValueInLastSevenDays,
patientInfo: {
form: { id: formId, type_name: typeName }
}
} = opts;
const aggravatingActivitiesAttributes = aggravatingActivities.entries.map(
activity => ({
id: activity.backupId,
name: activity.controls.activity.value,
ability: activity.controls.ability.value
})
);
let answersAttributes;
if (typeName === 'ases' || typeName === 'lefs') {
answersAttributes = AsesLefsAnswerAttributes({
functionalLimitations,
painValueInLastWeeks,
typeName,
formId
});
} else if (typeName === 'ndi' || typeName === 'odi') {
answersAttributes = NdiOdiAnswerAttributes({
functionalLimitations,
formId
});
} else {
answersAttributes = [];
}
const painScale = painValueInLastSevenDays && painValueInLastSevenDays.value;
return {
ongoing_form: {
aggravating_activities_attributes: aggravatingActivitiesAttributes,
answers_attributes: answersAttributes,
pain_scale: painScale
}
};
}
function ProgressForm(ctx) {
const {
aggravatingActivities,
functionalLimitations,
painValueInLastWeeks,
painValueInLastSevenDays,
patientInfo: {
form: { id: formId, type_name: typeName }
}
} = ctx;
const aggravatingActivitiesAttributes = aggravatingActivities.entries
.filter(activity => activity.controls.activity.value)
.map(activity => ({
id: activity.backupId,
name: activity.controls.activity.value,
ability: activity.controls.ability.value
}));
let answerAttributes;
if (typeName === 'ases' || typeName === 'lefs') {
answerAttributes = AsesLefsAnswerAttributes({
functionalLimitations,
painValueInLastWeeks,
typeName,
formId
});
} else if (typeName === 'ndi' || typeName === 'odi') {
answerAttributes = NdiOdiAnswerAttributes({
functionalLimitations,
formId
});
} else {
answerAttributes = [];
}
const painScale = painValueInLastSevenDays && painValueInLastSevenDays.value;
return {
ongoing_form: {
aggravating_activities_attributes: aggravatingActivitiesAttributes,
answers_attributes: answerAttributes,
pain_scale: painScale
}
};
}
/** ******************************************************************
* DRAFT SAVER MACHINE *
******************************************************************* */
async function saveOnboardingFormDraft({ uuid, draft }) {
const { data } = await api.post(`/forms/${uuid}/onboarding_drafts`, draft);
return data;
}
async function saveProgressFormDraft({ uuid, draft }) {
const { data } = await api.post(`/forms/${uuid}/ongoing_drafts`, draft);
return data;
}
function saveDraft({ formMachineContext: { uuid, patientInfo, ...rest } }) {
return new Promise(async resolve => {
if (patientInfo.form.progress_type === 'onboarding') {
resolve(
await saveOnboardingFormDraft({
uuid,
draft: OnboardingFormDraft({ uuid, patientInfo, ...rest })
})
);
} else if (patientInfo.form.progress_type === 'ongoing') {
resolve(
await saveProgressFormDraft({
uuid,
draft: ProgressFormDraft(rest)
})
);
}
});
}
const WaitingToBeFiredState = '@WaitingToBeFired';
const SavingDraftState = '@SaveDraft/Request';
const DraftSavedState = '@SaveDraft/Successful';
const DraftFailedToSaveState = '@SaveDraft/Failure';
const DraftSavedEvent = 'DRAFT_SAVED';
const saveDraftAction = 'saveDraft';
const sendDraftToFormAction = 'sendDraftToForm';
const draftSaverMachine = Machine(
{
id: 'Draft Saver Machine',
initial: WaitingToBeFiredState,
context: {
draft: null
},
states: {
[WaitingToBeFiredState]: {
after: {
[TIME_TO_WAIT_UNTIL_DRAFT_IS_SAVED]: SavingDraftState
}
},
[SavingDraftState]: {
invoke: {
id: 'saveDraft',
src: saveDraft,
onDone: {
target: DraftSavedState,
actions: [saveDraftAction]
},
onError: {
target: DraftFailedToSaveState
}
}
},
[DraftSavedState]: {
entry: sendDraftToFormAction,
type: 'final'
},
[DraftFailedToSaveState]: {
type: 'final'
}
}
},
{
actions: {
[saveDraftAction]: assign({
draft: (_, e) => e.data
}),
[sendDraftToFormAction]: sendParent(DraftSavedEvent)
}
}
);
/** ******************************************************************
* PATIENT FORMS MACHINE *
******************************************************************* */
/** ******************************************************************
* PATIENT FORMS MACHINE STATES
******************************************************************* */
const InitialState = '@Initial';
const FetchingFormRequiredDataState = '@FetchingRequiredData';
const FetchingPreviousDataState = '@FetchingPreviousData';
const FormState = '@Form';
const IdleState = '@Idle';
const FirstPageState = '@FirstPage';
const SecondPageState = '@SecondPage';
const CompletedFormState = '@Completed';
const FetchingFailureState = '@FetchingFailure';
const FetchingResultsRequiredDataState = '@FetchingResultsRequiredData';
const ResultsState = '@Results';
const NotFoundState = '@NotFound';
const BadRequestState = '@BadRequest';
const UnprocessableEntityState = '@UnprocessableEntity';
const InternalServerErrorState = '@InternalServerError';
const NetworkFailureState = '@NetworkFailure';
const UnknownErrorState = '@UnknownError';
const SubmittingState = '@Submitting';
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (GENERAL)
******************************************************************* */
const FormPageAccessedEvent = 'FORM_PAGE_ACCESSED';
const ResultsPageAccessedEvent = 'RESULTS_PAGE_ACCESSED';
const NotFoundPageAccessedEvent = 'UNKNOWN_PAGE_ACCESSED';
const SubmitEvent = 'SUBMIT';
const NextEvent = 'NEXT';
const PreviousEvent = 'PREVIOUS';
const InvalidControlSelectedEvent = 'INVALID_CONTROL_SELECTED';
const savePatientIdAction = 'savePatientId';
const saveUUIDAction = 'saveUUID';
const updateFormWithDraftDataAction = 'updateFormWithDraftData';
const saveRequiredDataAction = 'savedRequiredData';
const hydrateOnboardingFormAction = 'hydrateOnboardingForm';
const invokeDraftSaverActorAction = 'invokeDraftSaverActor';
const showValidationDialogAction = 'showValidationDialog';
const setSelectedInvalidControlAction = 'setSelectedInvalidControl';
const removeValidationRelatedStateAction = 'removeValidationRelatedState';
const setFetchingFailureErrorAction = 'setFetchingFailureError';
function CurrentlyInvalidControl({ controlNameOrObject, controls }) {
if (typeof controlNameOrObject === 'string') {
return {
name: controlNameOrObject,
control: controls[controlNameOrObject]
};
}
const { invalidControlName, invalidControlIds } = controlNameOrObject;
// eslint-disable-next-line default-case
switch (invalidControlName) {
case 'currentMedicationList':
return {
name: invalidControlName,
controls: controls[invalidControlName].entries
.filter(currentMedication => !currentMedication.destroyed)
.filter(y => invalidControlIds.includes(y.id))
};
case 'surgicalHistory':
return {
name: invalidControlName,
controls: controls[invalidControlName].entries
.filter(currentSurgery => !currentSurgery.destroyed)
.filter(y => invalidControlIds.includes(y.id))
};
case 'functionalLimitations':
return {
name: invalidControlName,
controls: controls[invalidControlName].questions.filter(y =>
invalidControlIds.includes(y.id)
)
};
case 'aggravatingActivities':
return {
name: invalidControlName,
controls: controls[invalidControlName].entries.filter(y =>
invalidControlIds.includes(y.id)
)
};
}
}
/**
* Returns the controls' name which are currently invalid for a given context.
*/
function getCurrentInvalidControlsSnapshot(ctx) {
const {
patientInfo: {
form: {
progress_type: progressType,
exclude_medical_information: excludeMedicalInfo
}
}
} = ctx;
/**
* TODO: Refactor getInvalidControlsForSegment. It's too complex
*/
return getInvalidControlsForSegment({
controls: Controls(ctx),
segments: {
onboarding: progressType === 'onboarding',
progressOngoing: progressType === 'ongoing'
},
shouldExcludeMedicalInfo: excludeMedicalInfo
});
}
function getControlsThatAreBeingCurrentlyValidated(ctx) {
const controls = Controls(ctx);
let invalidControlNames = ctx.invalidControls;
/**
* TODO: Refactor getInvalidControlsForSegment. It's too complex
*/
if (typeof invalidControlNames.length === 'undefined') {
invalidControlNames = [];
}
return invalidControlNames.map(controlNameOrObject =>
CurrentlyInvalidControl({ controlNameOrObject, controls })
);
}
const setFormAsInvalid = 'setFormAsInvalid';
const showSubmitSuccessAction = 'showSubmitSuccess';
const showSubmitFailureAction = 'showSubmitFailure';
const redirectAfterSubmitAction = 'redirectAfterSubmit';
const saveResultsRequiredDataAction = 'saveResultsRequiredData';
const firstPageIsValidGuard = 'firstPageIsValid';
function firstPageIsValid(ctx) {
const {
patientInfo: {
form: {
progress_type: progressType,
type_name: typeName,
exclude_medical_information: excludeMedicalInfo
}
},
primaryCarePhysician,
dateOfLastExam,
hasInfectionsOrPrecautions,
pleaseExplain,
referralSource,
pleaseExplainAnotherPhysician,
currentMedicationList,
surgicalHistory,
overallQualityOfLife,
bodyChartPoints,
functionalLimitations,
aggravatingActivities
} = ctx;
const hasFunctionalLimitations = typeName !== 'psfs';
let validations = [];
if (progressType === 'onboarding' && !excludeMedicalInfo) {
validations = [
primaryCarePhysician.valid,
dateOfLastExam.valid,
referralSource.value === 'referralSourceAnotherPhysician'
? pleaseExplainAnotherPhysician.valid
: true,
hasInfectionsOrPrecautions.value ? pleaseExplain.valid : true,
currentMedicationList.entries.reduce((acc, y) => acc && y.valid, true),
surgicalHistory.entries.reduce((acc, y) => acc && y.valid, true),
overallQualityOfLife.valid,
bodyChartPoints.valid,
aggravatingActivities.entries.reduce((acc, y) => acc && y.valid, true),
hasFunctionalLimitations ? functionalLimitations.valid : true
];
} else if (progressType === 'ongoing' && excludeMedicalInfo) {
validations = [
aggravatingActivities.entries.reduce((acc, y) => acc && y.valid, true),
hasFunctionalLimitations ? functionalLimitations.valid : true
];
} else if (progressType === 'ongoing') {
validations = [
hasFunctionalLimitations ? functionalLimitations.valid : true,
aggravatingActivities.entries.reduce((acc, y) => acc && y.valid, true)
];
}
return validations.every(validation => validation);
}
function termsAndConditionsCanBeSubmitted(ctx) {
const { acceptances, signature } = ctx;
const allAcceptancesAreAccepted = acceptances.entries.every(
acceptance => acceptance.value
);
return allAcceptancesAreAccepted && signature.valid;
}
const badRequestGuard = 'badRequest';
const unprocessableEntityGuard = 'unprocessableEntity';
const resourceWasNotFoundGuard = 'resourceWasNotFound';
const internalServerErrorGuard = 'internalServerError';
const networkFailedGuard = 'networkFailed';
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (CURRENT MEDICAL HISTORY)
******************************************************************* */
const SetPrimaryCarePhysicianNameEvent = 'PRIMARY_CARE_PHYSICIAN_NAME_SET';
const SetDateOfLastExamEvent = 'DATE_OF_LAST_EXAM_SET';
const SetHasInfectionsOrPrecautionsEvent = 'HAS_INFECTIONS_OR_PRECAUTIONS_SET';
const SetPleaseExplainEvent = 'PLEASE_EXPLAIN_SET';
const SetPleaseExplainAnotherPhysician = 'PLEASE_EXPLAIN_ANOTHER_PHYSICIAN';
const SetReferralSourceEvent = 'REFERRAL_SOURCE_SET';
const SetAllergicToLatexEvent = 'ALLERGIES_TO_LATEX_SET';
const setPrimaryCarePhysicianNameAction = 'setPrimaryCarePhysicianName';
function setPrimaryCarePhysicianNameEffect(ctx, e) {
return {
...ctx.primaryCarePhysician,
value: e.value,
valid:
isString(e.value) && isNotEmpty(e.value) && isNotEmpty(e.value.trim())
};
}
const setDateOfLastExamAction = 'setDateOfLastExam';
function setDateOfLastExamEffect(ctx, e) {
const parsedDate = new Date(e.value);
const valid =
isString(e.value) &&
isNotEmpty(e.value) &&
isNotEmpty(e.value.trim()) &&
isDate(parsedDate) &&
beforeDate(parsedDate, TODAY);
return {
...ctx.dateOfLastExam,
value: e.value,
valid
};
}
const setReferralSourceAction = 'setReferralSource';
function setReferralSourceEffect(ctx, e) {
return {
...ctx.referralSource,
value: e.value
};
}
const setHasInfectionsOrPrecautionsAction = 'setHasInfectionsOrPrecautions';
function setHasInfectionsOrPrecautionsEffect(ctx, e) {
return {
...ctx.hasInfectionsOrPrecautions,
value: e.value
};
}
const setPleaseExplainAction = 'setPleaseExplain';
function setPleaseExplainEffect(ctx, e) {
let valid;
if (ctx.hasInfectionsOrPrecautions.value === true) {
valid =
isString(e.value) && isNotEmpty(e.value) && isNotEmpty(e.value.trim());
} else {
valid = true;
}
return {
...ctx.pleaseExplain,
value: e.value,
valid
};
}
const setPleaseExplainAnotherPhysicianAction =
'setPleaseExplainAnotherPhysician';
function setPleaseExplainAnotherPhysicianEffect(ctx, e) {
let valid;
if (ctx.referralSource.value === 'referralSourceAnotherPhysician') {
valid =
isString(e.value) && isNotEmpty(e.value) && isNotEmpty(e.value.trim());
} else {
valid = true;
}
return {
...ctx.pleaseExplainAnotherPhysician,
value: e.value,
valid
};
}
const setAllergicToLatexAction = 'setAllergicToLatex';
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (CURRENT MEDICATION LIST)
******************************************************************* */
const SetCurrentMedicationNameEvent = 'CURRENT_MEDICATION_NAME_SET';
const SetCurrentMedicationDosageEvent = 'CURRENT_MEDICATION_DOSAGE_SET';
const AddMedicationEvent = 'MEDICATION_ADDED';
const RemoveMedicationEvent = 'MEDICATION_REMOVED';
const setCurrentMedicationNameAction = 'setCurrentMedicationName';
function setCurrentMedicationNameEffect(ctx, e) {
const matchesMedicationId = medication => medication.id === e.id;
const currentMedication = ctx.currentMedicationList.entries.find(
matchesMedicationId
);
const controlsTuple = [e.value, currentMedication.controls.dosage.value];
/**
* Calculate Controls Validity
*/
const medicationNameControl = {
...currentMedication.controls.name,
value: e.value,
valid: validateCurrentMedicationName(...controlsTuple)
};
const medicationDosageControl = {
...currentMedication.controls.dosage,
valid: validateCurrentMedicationDosage(...controlsTuple)
};
/**
* Table and Row Validation
*/
const currentMedicationWithUpdatedControls = {
...currentMedication,
controls: {
name: medicationNameControl,
dosage: medicationDosageControl
}
};
/**
* Row Validation
*/
const rowIsValid = validateCurrentMedicationNameAndDosage(
currentMedicationWithUpdatedControls.controls.name.value,
currentMedicationWithUpdatedControls.controls.dosage.value
);
/**
* Table Validation
*/
const medicationsWithUpdatedMedication = ctx.currentMedicationList.entries.map(
medication =>
matchesMedicationId(medication)
? currentMedicationWithUpdatedControls
: medication
);
const groupIsValid = medicationsWithUpdatedMedication.every(medication =>
validateCurrentMedicationNameAndDosage(
medication.controls.name.value,
medication.controls.dosage.value
)
);
return {
...ctx.currentMedicationList,
valid: groupIsValid,
entries: ctx.currentMedicationList.entries.map(
medication =>
matchesMedicationId(medication)
? {
...currentMedicationWithUpdatedControls,
valid: rowIsValid
}
: medication
)
};
}
const setCurrentMedicationDosageAction = 'setCurrentMedicationDosage';
function setCurrentMedicationDosageEffect(ctx, e) {
const matchesMedicationId = medication => medication.id === e.id;
const currentMedication = ctx.currentMedicationList.entries.find(
matchesMedicationId
);
const controlsTuple = [currentMedication.controls.name.value, e.value];
/**
* Calculate Controls Validity
*/
const medicationNameControl = {
...currentMedication.controls.name,
valid: validateCurrentMedicationName(...controlsTuple)
};
const medicationDosageControl = {
...currentMedication.controls.dosage,
value: e.value,
valid: validateCurrentMedicationDosage(...controlsTuple)
};
/**
* Table and Row Validation
*/
const currentMedicationWithUpdatedControls = {
...currentMedication,
controls: {
name: medicationNameControl,
dosage: medicationDosageControl
}
};
/**
* Row Validation
*/
const rowIsValid = validateCurrentMedicationNameAndDosage(
currentMedicationWithUpdatedControls.controls.name.value,
currentMedicationWithUpdatedControls.controls.dosage.value
);
/**
* Table Validation
*/
const medicationsWithUpdatedMedication = ctx.currentMedicationList.entries.map(
medication =>
matchesMedicationId(medication)
? currentMedicationWithUpdatedControls
: medication
);
const groupIsValid = medicationsWithUpdatedMedication.every(medication =>
validateCurrentMedicationNameAndDosage(
medication.controls.name.value,
medication.controls.dosage.value
)
);
return {
...ctx.currentMedicationList,
valid: groupIsValid,
entries: ctx.currentMedicationList.entries.map(
medication =>
matchesMedicationId(medication)
? {
...currentMedicationWithUpdatedControls,
valid: rowIsValid
}
: medication
)
};
}
const addMedicationAction = 'addMedication';
const removeMedicationAction = 'removeMedication';
function removeMedicationEffect(ctx, e) {
return {
...ctx.currentMedicationList,
entries: ctx.currentMedicationList.entries.map(
medication =>
medication.id === e.id ? { ...medication, destroyed: true } : medication
)
};
}
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (PAST MEDICAL HISTORY)
******************************************************************* */
const AddDiseaseEvent = 'DISEASE_ADDED';
const RemoveDiseaseEvent = 'DISEASE_REMOVED';
const addDiseaseAction = 'addDisease';
const removeDiseaseAction = 'removeDisease';
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (SURGICAL HISTORY)
******************************************************************* */
const SetSurgeryNameEvent = 'SURGERY_NAME_SET';
const SetSurgeryDateEvent = 'SURGERY_DATE_SET';
const AddSurgeryEvent = 'SURGERY_ADDED';
const RemoveSurgeryEvent = 'SURGERY_REMOVED';
const setSurgeryNameAction = 'setSurgeryName';
function setSurgeryNameEffect(ctx, e) {
const matchesSurgeryId = surgery => surgery.id === e.id;
const targetSurgery = ctx.surgicalHistory.entries.find(matchesSurgeryId);
const controlsTuple = [e.value, targetSurgery.controls.date.value];
/**
* Calculate Controls Validity
*/
const surgeryNameControl = {
...targetSurgery.controls.name,
value: e.value,
valid: validateSurgeryTableName(...controlsTuple)
};
const surgeryDateControl = {
...targetSurgery.controls.date,
valid: validateSurgeryTableDate(...controlsTuple)
};
/**
* Table and Row Validation
*/
const targetSurgeryWithUpdatedControls = {
...targetSurgery,
controls: {
name: surgeryNameControl,
date: surgeryDateControl
}
};
/**
* Row Validation
*/
const rowIsValid = validateSurgeryTableNameAndDate(
targetSurgeryWithUpdatedControls.controls.name.value,
targetSurgeryWithUpdatedControls.controls.date.value
);
/**
* Table Validation
*/
const surgeriesWithUpdatedSurgery = ctx.surgicalHistory.entries.map(
surgery =>
matchesSurgeryId(surgery) ? targetSurgeryWithUpdatedControls : surgery
);
const groupIsValid = surgeriesWithUpdatedSurgery.every(surgery =>
validateSurgeryTableNameAndDate(
surgery.controls.name.value,
surgery.controls.date.value
)
);
return {
...ctx.surgicalHistory,
valid: groupIsValid,
entries: ctx.surgicalHistory.entries.map(
surgery =>
matchesSurgeryId(surgery)
? {
...targetSurgeryWithUpdatedControls,
valid: rowIsValid
}
: surgery
)
};
}
const setSurgeryDateAction = 'setSurgeryDate';
function setSurgeryDateEffect(ctx, e) {
const matchesSurgeryId = surgery => surgery.id === e.id;
const targetSurgery = ctx.surgicalHistory.entries.find(matchesSurgeryId);
const controlsTuple = [targetSurgery.controls.name.value, e.value];
/**
* Calculate Controls Validity
*/
const surgeryNameControl = {
...targetSurgery.controls.name,
valid: validateSurgeryTableName(...controlsTuple)
};
const surgeryDateControl = {
...targetSurgery.controls.date,
value: e.value,
valid: validateSurgeryTableDate(...controlsTuple)
};
/**
* Table and Row Validation
*/
const targetSurgeryWithUpdatedControls = {
...targetSurgery,
controls: {
name: surgeryNameControl,
date: surgeryDateControl
}
};
/**
* Row Validation
*/
const rowIsValid = validateSurgeryTableNameAndDate(
targetSurgeryWithUpdatedControls.controls.name.value,
targetSurgeryWithUpdatedControls.controls.date.value
);
/**
* Table Validation
*/
const surgeriesWithUpdatedSurgery = ctx.surgicalHistory.entries.map(
surgery =>
matchesSurgeryId(surgery) ? targetSurgeryWithUpdatedControls : surgery
);
const groupIsValid = surgeriesWithUpdatedSurgery.every(surgery =>
validateSurgeryTableNameAndDate(
surgery.controls.name.value,
surgery.controls.date.value
)
);
return {
...ctx.surgicalHistory,
valid: groupIsValid,
entries: ctx.surgicalHistory.entries.map(
surgery =>
matchesSurgeryId(surgery)
? {
...targetSurgeryWithUpdatedControls,
valid: rowIsValid
}
: surgery
)
};
}
const addSurgeryAction = 'addSurgery';
const removeSurgeryAction = 'removeSurgery';
function removeSurgeryEffect(ctx, e) {
return {
...ctx.surgicalHistory,
entries: ctx.surgicalHistory.entries.map(
surgery =>
surgery.id === e.id ? { ...surgery, destroyed: true } : surgery
)
};
}
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (OVERALL QUALITY OF LIFE)
******************************************************************* */
const SetOverallQualityOfLifeEvent = 'OVERALL_LIFE_QUALITY_SET';
const setOverallQualityOfLifeAction = 'setOverallQualityOfLife';
function setOverallQualityOfLifeEffect(ctx, e) {
return {
...ctx.overallQualityOfLife,
valid: true,
value: e.value
};
}
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (BODY CHART)
******************************************************************* */
const PainPointSelectedEvent = 'PAIN_POINT_SELECTED';
const handlePainPointSelectionAction = 'handlePainPointSelection';
function handlePainPointSelectionEffect(ctx, e) {
const targetChartWithUpdatedPoint = ctx.bodyChartPoints[
e.whichChartIsItFrom
].map(
point =>
point.id === e.pointId
? {
...point,
selected: !point.selected
}
: point
);
const { front, back } = ctx.bodyChartPoints;
/**
* Validate Charts
*/
let valid;
if (e.whichChartIsItFrom === 'front') {
valid =
targetChartWithUpdatedPoint.some(point => point.selected) ||
back.some(point => point.selected);
} else if (e.whichChartIsItFrom === 'back') {
valid =
front.some(point => point.selected) ||
targetChartWithUpdatedPoint.some(point => point.selected);
}
return {
...ctx.bodyChartPoints,
valid,
[e.whichChartIsItFrom]: ctx.bodyChartPoints[e.whichChartIsItFrom].map(
point =>
point.id === e.pointId
? {
...point,
selected: !point.selected
}
: point
)
};
}
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (PAIN SCALE)
******************************************************************* */
const SetPainScaleEvent = 'PAIN_SCALE_VALUE_SET';
const setPainScaleAction = 'setPainScale';
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (AGGRAVATING ACTIVITIES)
******************************************************************* */
const SetAggravatingActivityName = 'AGGRAVATING_ACTIVITY_NAME_SET';
const SetAggravatingActivityScore = 'AGGRAVATING_ACTIVITY_SCORE_SET';
const setAggravatingActivityNameAction = 'setAggravatingActivityName';
function setAggravatingActivityNameEffect(ctx, e) {
const matchesActivityId = activity => activity.id === e.id;
const targetActivity = ctx.aggravatingActivities.entries.find(
matchesActivityId
);
/**
* Table and Row Validation
*/
const targetActivityWithUpdatedControls = {
...targetActivity,
controls: {
...targetActivity.controls,
activity: {
...targetActivity.controls.activity,
value: e.value
}
}
};
/**
* Row Validation
*/
const rowIsValid = validateActivity(e.value);
/**
* Table Validation
*/
const activitiesWithUpdatedActivity = ctx.aggravatingActivities.entries.map(
activity =>
matchesActivityId(activity) ? targetActivityWithUpdatedControls : activity
);
const groupIsValid = activitiesWithUpdatedActivity.every(activity =>
validateActivity(activity.controls.activity.value)
);
return {
...ctx.aggravatingActivities,
valid: groupIsValid,
entries: ctx.aggravatingActivities.entries.map(
activity =>
matchesActivityId(activity)
? {
...targetActivityWithUpdatedControls,
valid: rowIsValid
}
: activity
)
};
}
const setAggravatingActivityScoreAction = 'setAggravatingActivityScore';
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (FUNCTIONAL LIMITATIONS)
******************************************************************* */
const SetPainInLastWeeksEvent = 'PAIN_IN_LAST_WEEKS_SET';
const SetFunctionalLimitationsQuestionEvent =
'FUNCTIONAL_LIMITATIONS_QUESTION_SET';
const setPainInLastWeeksAction = 'setPainInLastWeeks';
const setFunctionalLimitationsQuestionAction =
'setFunctionalLimitationsQuestion';
function setFunctionalLimitationsQuestionEffect(ctx, e) {
const matchesQuestionId = question => question.id === e.id;
const questions = ctx.functionalLimitations.questions.map(
question =>
matchesQuestionId(question)
? {
...question,
control: {
...question.control,
value: e.value,
valid: true
},
score: e.score
}
: question
);
const valid = questions.every(question => question.control.valid);
return {
valid,
questions
};
}
/** ******************************************************************
* PATIENT FORMS MACHINE EVENTS AND ACTIONS (TERMS AND CONDITIONS)
******************************************************************* */
const ToggleAcceptanceEvent = 'ACCEPTANCE_TOGGLED';
const SignatureFieldClickedEvent = 'SIGNATURE_FIELD_CLICKED';
const SignatureSetEvent = 'SIGNATURE_SET';
const toggleAcceptanceAction = 'toggleAcceptanceAction';
function toggleAcceptanceEffect(ctx, e) {
return {
...ctx.acceptances,
entries: ctx.acceptances.entries.map(
acceptance =>
acceptance.id === e.id
? {
...acceptance,
value: e.value,
valid: e.value
}
: acceptance
)
};
}
const setSignatureAction = 'setSignature';
function setSignatureEffect(ctx, e) {
return {
...ctx.signature,
value: e.value,
valid:
isString(e.value) && isNotEmpty(e.value) && isNotEmpty(e.value.trim())
};
}
const validateTermsAndConditionsAction = 'validateTermsAndConditions';
/** ******************************************************************
* DRAFT SAVER MACHINE *
******************************************************************* */
const VisibleState = '@Visible';
const HiddenState = '@Hidden';
const SubmitDialogEvent = 'VALIDATION_DIALOG_SUBMITTED';
const ToggleDialogEvent = 'VALIDATION_DIALOG_TOGGLED';
const submitParentAction = 'submitParent';
const validationDialogMachine = Machine({
id: 'Validation Dialog Machine',
initial: InitialState,
states: {
[InitialState]: {
on: {
[ToggleDialogEvent]: HiddenState,
[SubmitDialogEvent]: {
actions: [submitParentAction]
}
}
},
[VisibleState]: {
on: {
[ToggleDialogEvent]: HiddenState,
[SubmitDialogEvent]: {
actions: [submitParentAction]
}
}
},
[HiddenState]: {
on: {
[ToggleDialogEvent]: VisibleState
}
}
},
actions: {
[submitParentAction]: sendParent({ type: SubmitEvent })
}
});
/** ******************************************************************
* PATIENT FORMS MACHINE FORM STATES
******************************************************************* */
async function saveOnboardingForm({ uuid, form }) {
return api.post(`/forms/${uuid}/onboarding_forms`, form);
}
async function saveProgressForm({ uuid, form }) {
return api.post(`/forms/${uuid}/ongoing_forms`, form);
}
async function saveForm(ctx) {
const {
uuid,
patientInfo: {
form: { progress_type: progressType }
}
} = ctx;
if (progressType === 'onboarding') {
return saveOnboardingForm({
uuid,
form: await OnboardingForm(ctx)
});
} else if (progressType === 'ongoing') {
return saveProgressForm({
uuid,
form: ProgressForm(ctx)
});
}
}
const commonFormStates = {
[SubmittingState]: {
invoke: {
id: 'saveForm',
src: saveForm,
onDone: {
target: CompletedFormState,
actions: [showSubmitSuccessAction, redirectAfterSubmitAction]
},
onError: {
target: IdleState,
actions: [showSubmitFailureAction]
}
}
},
[CompletedFormState]: {
type: 'final'
}
};
const formStates = {
states: {
[FirstPageState]: {
initial: IdleState,
on: {
[InvalidControlSelectedEvent]: {
actions: [setSelectedInvalidControlAction]
}
},
states: {
[IdleState]: {
on: {
/** ******************************************************************
* GENERAL
******************************************************************* */
[SubmitEvent]: {
target: SubmittingState,
cond: firstPageIsValidGuard
},
[DraftSavedEvent]: {
actions: [updateFormWithDraftDataAction]
},
/** ******************************************************************
* CURRENT MEDICAL HISTORY
******************************************************************* */
[SetPrimaryCarePhysicianNameEvent]: {
actions: [
setPrimaryCarePhysicianNameAction,
invokeDraftSaverActorAction
]
},
[SetDateOfLastExamEvent]: {
actions: [setDateOfLastExamAction, invokeDraftSaverActorAction]
},
[SetHasInfectionsOrPrecautionsEvent]: {
actions: [
setHasInfectionsOrPrecautionsAction,
invokeDraftSaverActorAction
]
},
[SetPleaseExplainEvent]: {
actions: [setPleaseExplainAction, invokeDraftSaverActorAction]
},
[SetPleaseExplainAnotherPhysician]: {
actions: [
setPleaseExplainAnotherPhysicianAction,
invokeDraftSaverActorAction
]
},
[SetAllergicToLatexEvent]: {
actions: [setAllergicToLatexAction, invokeDraftSaverActorAction]
},
[SetReferralSourceEvent]: {
actions: [setReferralSourceAction, invokeDraftSaverActorAction]
},
/** ******************************************************************
* CURRENT MEDICATION LIST
******************************************************************* */
[SetCurrentMedicationNameEvent]: {
actions: [
setCurrentMedicationNameAction,
invokeDraftSaverActorAction
]
},
[SetCurrentMedicationDosageEvent]: {
actions: [
setCurrentMedicationDosageAction,
invokeDraftSaverActorAction
]
},
[AddMedicationEvent]: {
actions: [addMedicationAction, invokeDraftSaverActorAction]
},
[RemoveMedicationEvent]: {
actions: [removeMedicationAction, invokeDraftSaverActorAction]
},
/** ******************************************************************
* DISEASES
******************************************************************* */
[AddDiseaseEvent]: {
actions: [addDiseaseAction, invokeDraftSaverActorAction]
},
[RemoveDiseaseEvent]: {
actions: [removeDiseaseAction, invokeDraftSaverActorAction]
},
/** ******************************************************************
* SURGERIES
******************************************************************* */
[SetSurgeryNameEvent]: {
actions: [setSurgeryNameAction, invokeDraftSaverActorAction]
},
[SetSurgeryDateEvent]: {
actions: [setSurgeryDateAction, invokeDraftSaverActorAction]
},
[AddSurgeryEvent]: {
actions: [addSurgeryAction, invokeDraftSaverActorAction]
},
[RemoveSurgeryEvent]: {
actions: [removeSurgeryAction, invokeDraftSaverActorAction]
},
/** ******************************************************************
* OVERALL QUALITY OF LIFE
******************************************************************* */
[SetOverallQualityOfLifeEvent]: {
actions: [
setOverallQualityOfLifeAction,
invokeDraftSaverActorAction
]
},
/** ******************************************************************
* BODY CHART
******************************************************************* */
[PainPointSelectedEvent]: {
actions: [
handlePainPointSelectionAction,
invokeDraftSaverActorAction
]
},
/** ******************************************************************
* PAIN SCALE
******************************************************************* */
[SetPainScaleEvent]: {
actions: [setPainScaleAction, invokeDraftSaverActorAction]
},
/** ******************************************************************
* AGGRAVATING ACTIVITIES
******************************************************************* */
[SetAggravatingActivityName]: {
actions: [
setAggravatingActivityNameAction,
invokeDraftSaverActorAction
]
},
[SetAggravatingActivityScore]: {
actions: [
setAggravatingActivityScoreAction,
invokeDraftSaverActorAction
]
},
/** ******************************************************************
* FUNCTIONAL LIMITATIONS
******************************************************************* */
[SetPainInLastWeeksEvent]: {
actions: [setPainInLastWeeksAction, invokeDraftSaverActorAction]
},
[SetFunctionalLimitationsQuestionEvent]: {
actions: [
setFunctionalLimitationsQuestionAction,
invokeDraftSaverActorAction
]
}
}
},
...commonFormStates
}
},
[SecondPageState]: {
initial: IdleState,
states: {
[IdleState]: {
on: {
/** ******************************************************************
* GENERAL
******************************************************************* */
[SubmitEvent]: {
target: SubmittingState,
cond: termsAndConditionsCanBeSubmitted
},
[DraftSavedEvent]: {
actions: [updateFormWithDraftDataAction]
},
/** ******************************************************************
* TERMS AND CONDITIONS
******************************************************************* */
[ToggleAcceptanceEvent]: {
actions: [toggleAcceptanceAction]
},
[SignatureSetEvent]: {
actions: [setSignatureAction, invokeDraftSaverActorAction]
},
[SignatureFieldClickedEvent]: {
actions: [validateTermsAndConditionsAction]
}
}
},
...commonFormStates
}
}
}
};
async function fetchPatientInfo({ patientId, uuid }) {
const { data } = await api({
method: 'GET',
url: `/patients/${patientId}/forms/${uuid}`
});
return data;
}
async function fetchFunctionalLimitationsQuestions(typeName) {
const { data } = await api({
method: 'GET',
url: `/form_types/${typeName}/questions`
});
return data;
}
async function fetchDiseases() {
const { data } = await api({
method: 'GET',
url: '/diseases'
});
return data;
}
async function fetchPainSpots() {
const { data } = await api({
method: 'GET',
url: '/pain_spots'
});
return data;
}
async function fetchRequiredData({ patientId, uuid }) {
const patientInfo = await fetchPatientInfo({ patientId, uuid });
const {
form: { type_name: typeName }
} = patientInfo;
const [
diseases,
painSpots,
functionalLimitationQuestions
] = await Promise.all([
fetchDiseases(),
fetchPainSpots(),
patientInfo.typeName === 'psfs'
? Promise.resolve([])
: fetchFunctionalLimitationsQuestions(typeName)
]);
return {
patientInfo,
diseases,
painSpots,
functionalLimitationQuestions
};
}
async function fetchProgressDraft(uuid) {
const { data } = await api({
method: 'GET',
url: `/forms/${uuid}/previous_intake_forms`
});
return data;
}
async function fetchOnboardingDraft(uuid) {
const { data } = await api({
method: 'GET',
url: `/forms/${uuid}/onboarding_drafts`
});
return data;
}
async function fetchPreviousIntakeFormInfo(uuid) {
const { data } = await api({
method: 'GET',
url: `/forms/${uuid}/previous_intake_forms`
});
return data;
}
async function fetchPreviousData({
uuid,
patientInfo: {
form: { progress_type: progressType }
}
}) {
if (progressType === 'ongoing') {
return fetchProgressDraft(uuid);
}
const [onboardingDraft, previousIntakeForm] = await Promise.all([
fetchOnboardingDraft(uuid),
fetchPreviousIntakeFormInfo(uuid)
]);
return {
onboardingDraft,
previousIntakeForm
};
}
const formMachine = Machine(
{
id: 'Luna Form Machine',
initial: InitialState,
context: {
/** ******************************************************************
* FORM QUERY PARAMS
******************************************************************* */
patientId: null,
uuid: null,
fetchingFailureError: null,
/** ******************************************************************
* FORM STATE
******************************************************************* */
patientInfo: null,
draftActor: null,
canEditAggravatingActivities: null,
/** ******************************************************************
* VALIDATION STATE
******************************************************************* */
valid: null,
validationDialogActor: null,
invalidControls: [],
selectedInvalidControlId: null,
validateTermsAndConditions: false,
termsAndConditionsValid: null,
/** ******************************************************************
* FORM CONTROLS
******************************************************************* */
primaryCarePhysician: {
id: cuid(),
label: 'Primary care physician',
value: '',
valid: false
},
dateOfLastExam: {
id: cuid(),
label: 'Date of last exam',
value: '',
valid: false
},
referralSource: {
id: cuid(),
value: 'doNotHaveAReferral'
},
pleaseExplainAnotherPhysician: {
id: cuid(),
label: 'Referred physician name',
value: '',
valid: true
},
hasInfectionsOrPrecautions: {
id: cuid(),
value: false
},
pleaseExplain: {
id: cuid(),
label: 'Please explain',
value: '',
valid: false
},
allergicToLatex: {
id: cuid(),
value: false
},
currentMedicationList: {
id: cuid(),
valid: true,
entries: [BlankMedication(1), BlankMedication(2), BlankMedication(3)]
},
pastMedicalHistory: {
entries: []
},
surgicalHistory: {
id: cuid(),
valid: true,
entries: [BlankSurgery(1), BlankSurgery(2), BlankSurgery(3)]
},
overallQualityOfLife: {
id: cuid(),
label: 'Overall quality of life',
value: '',
valid: false
},
bodyChartPoints: {
id: cuid(),
label: 'Body chart points',
front: [],
back: [],
valid: false
},
painValueInLastSevenDays: {
id: cuid(),
value: 5
},
aggravatingActivities: {
id: cuid(),
valid: false,
entries: [
BlankAggravatingActivity(1),
BlankAggravatingActivity(2),
BlankAggravatingActivity(3)
]
},
painValueInLastWeeks: undefined,
functionalLimitations: {
id: cuid(),
questions: [],
valid: false
},
acceptances: {
entries: ACCEPTANCES.map(Acceptance)
},
signature: {
value: '',
valid: false
}
},
states: {
[InitialState]: {
on: {
[FormPageAccessedEvent]: {
target: FetchingFormRequiredDataState,
actions: [savePatientIdAction, saveUUIDAction]
},
[ResultsPageAccessedEvent]: FetchingResultsRequiredDataState,
[NotFoundPageAccessedEvent]: NotFoundState
}
},
[FetchingFormRequiredDataState]: {
invoke: {
id: 'fetchRequiredData',
src: fetchRequiredData,
onDone: {
target: FetchingPreviousDataState,
actions: [saveRequiredDataAction]
},
onError: {
target: FetchingFailureState,
actions: [setFetchingFailureErrorAction]
}
}
},
[FetchingPreviousDataState]: {
invoke: {
id: 'fetchPreviousData',
src: fetchPreviousData,
onDone: {
target: FormState,
actions: [hydrateOnboardingFormAction]
},
onError: {
target: FetchingFailureState,
actions: [setFetchingFailureErrorAction]
}
}
},
[FetchingFailureState]: {
on: {
'': [
{
target: UnprocessableEntityState,
cond: unprocessableEntityGuard
},
{ target: BadRequestState, cond: badRequestGuard },
{ target: NotFoundState, cond: resourceWasNotFoundGuard },
{
target: InternalServerErrorState,
cond: internalServerErrorGuard
},
{ target: NetworkFailureState, cond: networkFailedGuard },
{ target: UnknownErrorState }
]
}
},
[BadRequestState]: { type: 'final' },
[NotFoundState]: { type: 'final' },
[UnprocessableEntityState]: { type: 'final' },
[InternalServerErrorState]: { type: 'final' },
[UnknownErrorState]: { type: 'final' },
[NetworkFailureState]: { type: 'final' },
[FormState]: {
initial: FirstPageState,
on: {
[NextEvent]: [
{
target: `.${SecondPageState}`,
actions: [removeValidationRelatedStateAction],
cond: firstPageIsValidGuard
},
{
actions: [showValidationDialogAction, setFormAsInvalid]
}
],
[PreviousEvent]: `.${FirstPageState}`
},
...formStates
},
[FetchingResultsRequiredDataState]: {
invoke: {
id: 'Load Results Required Data',
src: () => Promise.resolve(),
onDone: {
target: ResultsState,
actions: [saveResultsRequiredDataAction]
},
onError: {
target: FetchingFailureState
}
}
},
[ResultsState]: {
on: {}
}
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment