dsi-icl/optimise

View on GitHub
packages/optimise-ui/src/components/patientProfile/patientProfile.jsx

Summary

Maintainability
F
1 wk
Test Coverage
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import moment from 'moment';
import Icon from '../icon';
import { PickDate } from '../createMedicalElements/datepicker';
import { PatientProfileSectionScaffold, DeleteButton, EditButton } from './sharedComponents';
import { formatRow } from './patientChart';
import store from '../../redux/store';
import { createImmunisationAPICall, deleteImmunisationAPICall, editImmunisationAPICall } from '../../redux/actions/demographicData';
import { erasePatientAPICall, erasePatientReset } from '../../redux/actions/erasePatient';
import { getPatientPii, changePatientId } from '../../redux/actions/patientProfile';
import { updateConsentAPICall, updateParticipationAPICall } from '../../redux/actions/consent';
import { addAlert } from '../../redux/actions/alert';
import style from './patientProfile.module.css';

@connect(state => ({
    fetching: state.patientProfile.fetching,
    erasePatient: state.erasePatient,
    patientProfile: state.patientProfile
}))
class Section extends Component {
    componentWillUnmount() {
        store.dispatch(erasePatientReset());
    }

    render() {
        const { fetching, erasePatient } = this.props;

        if (fetching) {
            return <span></span>;
        } else {
            if (erasePatient.success) {
                return <Redirect to='/searchPatient/from/deletionSuccessful' />;
            } else {
                return (
                    <>
                        <div className={style.ariane}>
                            <h2>Overview</h2>
                        </div>
                        <div className={`${style.panel} ${style.patientInfo}`}>
                            <DemographicSection patientId={this.props.match.params.patientId} />
                            <PrimaryDiagnosis patientId={this.props.match.params.patientId} />
                            {!this.props.patientProfile.data.pregnancySubStudyConsent ? <Pregnancy /> : null}

                            <ImmunisationSection patientId={this.props.match.params.patientId} />
                            <ConsentSection match={this.props.match} />
                            <DeletePatient match={this.props.match} />
                        </div>
                    </>
                );
            }
        }
    }
}

export { Section };

@connect(state => ({
    data: state.patientProfile.data ? state.patientProfile.data : {},
    pii: state.patientProfile.pii,
    fields: state.availableFields.demoFields[0]
}))
class DemographicSection extends Component {

    constructor() {
        super();
        this.state = { showPii: false, showEditAliasId: false, editAliasIdInput: '' };
        this._hidePii = this._hidePii.bind(this);
        this._queryPatientData = this._queryPatientData.bind(this);
        this._hideEditId = this._hideEditId.bind(this);
        this._showEditId = this._showEditId.bind(this);
        this._onChangeEditID = this._onChangeEditID.bind(this);
        this._submitEditId = this._submitEditId.bind(this);
    }

    _queryPatientData(ev) {
        ev.preventDefault();
        const body = {
            patient: this.props.data.id
        };
        this.setState({
            showPii: true
        });
        if (!this.props.pii)
            store.dispatch(getPatientPii(body));
    }

    _hidePii() {
        this.setState({
            showPii: false
        });
    }

    _hideEditId() {
        this.setState({
            showEditAliasId: false,
            editAliasIdInput: ''
        });
    }

    _showEditId() {
        this.setState({
            showEditAliasId: true
        });
    }

    _onChangeEditID(e) {
        this.setState({
            editAliasIdInput: e.target.value
        });
    }

    _submitEditId() {
        store.dispatch(changePatientId({
            data: {
                id: this.props.data.id,
                aliasId: this.state.editAliasIdInput
            },
            to: '/searchPatient'
        }));
    }


    render() {
        const { data: { demographicData }, pii, fields } = this.props;
        const { showEditAliasId, editAliasIdInput } = this.state;
        if (demographicData) {
            let { DOB, countryOfOrigin, dominantHand, ethnicity, gender } = demographicData;
            countryOfOrigin = fields['countries'].filter(el => el.id === countryOfOrigin)[0].value;
            dominantHand = fields['dominant_hands'].filter(el => el.id === dominantHand)[0].value;
            ethnicity = fields['ethnicities'].filter(el => el.id === ethnicity)[0].value;
            gender = fields['genders'].filter(el => el.id === gender)[0].value;

            return (
                <PatientProfileSectionScaffold sectionName='Profile' actions={
                    <EditButton to={`/patientProfile/${this.props.patientId}/edit/demographic/data`} />
                }>
                    <label>Date of birth:</label> {new Date(parseInt(DOB, 10)).toDateString()}<br />
                    <label>Gender:</label> <span>{gender}</span> <br />
                    <label>Dominant hand:</label> <span>{dominantHand}</span> <br />
                    <label>Ethnicity:</label> <span>{ethnicity}</span> <br />
                    <label>Country of origin:</label> <span>{countryOfOrigin}</span> <br />
                    <div onMouseLeave={this._hidePii} className={`${style.closePii} ${pii && this.state.showPii ? style.openPii : ''}`}>
                        <span onClick={this._queryPatientData} className={style.piiUncover}>Show Personally Identifiable Information</span>
                        {pii ?
                            <>
                                <br />
                                <label>First name:</label> <span>{pii.firstName}</span> <br />
                                <label>Surname:</label> <span>{pii.surname}</span> <br />
                                <label>Address:</label> <span>{pii.fullAddress}</span> <br />
                                <label>Postcode:</label> <span>{pii.postcode}</span>
                            </>
                            : null}
                    </div>
                    <br />
                    {showEditAliasId ?
                        <div className={style.editPatientIdDiv}>
                            <b>Edit Patient ID</b>
                            <br /><br />
                            <input onChange={this._onChangeEditID} value={editAliasIdInput} />
                            <br /><br />
                            <button onClick={this._submitEditId}>Submit</button>
                            <br /><br />
                            <button onClick={this._hideEditId}>Cancel</button>
                            <p>Note: after changing patient ID you will be redirected to search tab.</p>
                        </div>
                        :
                        <span onClick={this._showEditId} className={style.piiUncover}>Edit Patient ID</span>
                    }
                </PatientProfileSectionScaffold>
            );
        } else {
            return <div>Please add demographic data via the API.</div>;
        }
    }
}

@connect(state => ({
    data: state.patientProfile.data
}))
class ImmunisationSection extends Component {
    constructor() {
        super();
        this.state = { addMore: false, newDate: moment(), newName: '' };
        this._handleClickingAdd = this._handleClickingAdd.bind(this);
        this._handleInput = this._handleInput.bind(this);
        this._handleDateChange = this._handleDateChange.bind(this);
        this._handleSubmit = this._handleSubmit.bind(this);
    }

    _handleClickingAdd() {
        this.setState(prevState => ({
            addMore: !prevState.addMore,
            newDate: moment(),
            newName: '',
            error: false
        }));
    }

    _handleInput(ev) {
        this.setState({
            newName: ev.target.value,
            error: false
        });
    }

    _handleDateChange(date) {
        this.setState({
            newDate: date,
            error: false
        });
    }

    _handleSubmit(ev) {

        ev.preventDefault();
        if (this.state.lastSubmit && (new Date()).getTime() - this.state.lastSubmit < 500 ? true : false)
            return;

        if (this.state.newName === undefined || this.state.newName === null || this.state.newName === '') {
            return this.setState({
                error: 'Please indicate the vaccine name'
            });
        }

        if (!this.state.newDate || !this.state.newDate.isValid()) {
            return this.setState({
                error: 'Please indicated the immunisation date'
            });
        }

        if (this.currenttlySumbitting === this.state.newName)
            return;

        const data = this.props.data;
        const body = {
            patientId: data.patientId,
            data: {
                patient: data.id,
                vaccineName: this.state.newName,
                immunisationDate: this.state.newDate.toISOString()
            }
        };
        this.setState(prevState => ({
            newName: prevState.newName,
            lastSubmit: (new Date()).getTime(),
            error: false
        }), () => {
            store.dispatch(createImmunisationAPICall(body));
            this.setState({
                newName: '',
                addMore: false
            });
        });
    }

    render() {
        const { data } = this.props;
        return (
            <PatientProfileSectionScaffold sectionName='Immunisations' active={this.state.addMore}>
                <table cellSpacing={'1em'}>
                    {this.state.addMore || data.immunisations.length !== 0 ? <thead>
                        <tr><th>Vaccine name</th><th>Date</th></tr>
                    </thead> : null}
                    <tbody>
                        {data.immunisations.map(el => <OneImmunisation data={el} patientId={data.patientId} />)}
                        {!this.state.addMore ? null : <tr className={style.immunisationNewItem}>
                            <td><input value={this.state.newName} onChange={this._handleInput} placeholder='vaccine name' name='vaccineName' type='text' /></td>
                            <td colSpan='2'><PickDate startDate={this.state.newDate} handleChange={this._handleDateChange} /></td>
                        </tr>}
                    </tbody>
                </table>
                {!this.state.addMore ?
                    <>
                        <br />
                        <button onClick={this._handleClickingAdd}>Add immunisation</button>
                    </> :
                    <>
                        <br /><br />
                        {this.state.error ? <><div className={style.error}>{this.state.error}</div><br /></> : null}
                        <button onClick={this._handleSubmit}>Submit</button><br /><br />
                        <button onClick={this._handleClickingAdd}>Cancel</button><br />
                    </>}
            </PatientProfileSectionScaffold>
        );
    }
}

class OneImmunisation extends Component {
    /* this.props.data  this.props.patientId */
    constructor(props) {
        super(props);
        this.state = {
            editing: false,
            newDate: moment(parseInt(this.props.data.immunisationDate)),
            error: undefined
        };
        this._handleClickDelete = this._handleClickDelete.bind(this);
        this._deleteFunction = this._deleteFunction.bind(this);
        this._handleDateChange = this._handleDateChange.bind(this);
        this._handleSave = this._handleSave.bind(this);
    }

    _handleDateChange(date) {
        this.setState({
            newDate: date,
            error: undefined
        });
    }

    _handleClickDelete(el) {
        store.dispatch(addAlert({ alert: 'Are you sure you want to delete this immunisation record?', handler: this._deleteFunction(el.id) }));
    }

    _deleteFunction(id) {
        const that = this;
        return () => {
            const body = {
                patientId: that.props.patientId,
                data: {
                    id: id
                }
            };

            store.dispatch(deleteImmunisationAPICall(body));
        };
    }

    _handleSave() {
        if (!this.state.newDate || !this.state.newDate.isValid()) {
            this.setState({ error: 'Invalid date' });
        }
        const { data, patientId } = this.props;
        const body = {
            patientId,
            data: {
                id: data.id,
                immunisationDate: this.state.newDate.toISOString()
            }
        };
        store.dispatch(editImmunisationAPICall(body));
        this.setState({ editing: false });
    }

    render() {
        const el = this.props.data;

        if (this.state.editing) {
            return (
                <>
                    <tr key={el.vaccineName} className={style.immunisationItem}>
                        {formatRow([
                            el.vaccineName,
                            <PickDate startDate={this.state.newDate} handleChange={this._handleDateChange} />,
                            <button onClick={this._handleSave}>Save</button>
                        ])}
                    </tr>
                    {
                        this.state.error
                            ?
                            <tr><span>{this.state.error}</span></tr>
                            :
                            null
                    }
                </>
            );
        } else {
            return (
                <tr key={el.vaccineName} className={style.immunisationItem}>
                    {formatRow([
                        el.vaccineName,
                        new Date(parseInt(el.immunisationDate, 10)).toDateString(),
                        <div className={style.editButton} style={{ cursor: 'pointer' }}>
                            <div onClick={() => { this.setState({ editing: true }); }}>
                                <span title='Edit' className={style.dataEdit}><Icon symbol='edit' /></span>
                            </div>
                        </div>,
                        <DeleteButton clickhandler={() => this._handleClickDelete(el)} />
                    ])}
                </tr>
            );
        }

    }
}

@connect(state => ({
    data: state.patientProfile.data,
    fields: state.availableFields.diagnoses
}))
class PrimaryDiagnosis extends Component {
    render() {
        if (this.props.data.diagnosis.length === 0) {
            return (
                <PatientProfileSectionScaffold sectionName='Last Primary MS Diagnosis' actions={
                    <EditButton to={`/patientProfile/${this.props.patientId}/edit/diagnosis/data`} />
                }>
                    <i>No recorded diagnosis</i>
                </PatientProfileSectionScaffold>
            );
        }

        const diagnosis = this.props.data.diagnosis.sort((a, b) => parseInt(b.diagnosisDate) - parseInt(a.diagnosisDate))[0];
        if (!diagnosis) {
            return null;
        }

        const diagnosisName = this.props.fields.filter(el => el.id === diagnosis.diagnosis)[0];
        if (!diagnosisName) {
            return null;
        }

        return (
            <PatientProfileSectionScaffold sectionName='Last Primary MS Diagnosis' actions={
                <EditButton to={`/patientProfile/${this.props.patientId}/edit/diagnosis/data`} />
            }>
                <>
                    <label>Date of diagnosis: </label> {new Date(parseInt(diagnosis.diagnosisDate, 10)).toDateString()} < br />
                    <label>Diagnosis: </label> {diagnosisName.value}
                </>
            </PatientProfileSectionScaffold>
        );
    }
}


@connect(state => ({
    outcomeHash: state.availableFields.pregnancyOutcomes_Hash[0],
    data: state.patientProfile.data,
    meddra_Hash: state.availableFields.meddra_Hash[0]
}))
class Pregnancy extends Component {
    render() {

        if (this.props.data.demographicData) {
            if (this.props.data.demographicData.gender === 1)
                return null;
        }

        if (this.props.data.demographicData) {
            if (this.props.data.pregnancy.length === 0) {
                return (
                    <PatientProfileSectionScaffold sectionName='Pregnancies' actions={
                        <EditButton to={`/patientProfile/${this.props.data.patientId}/edit/pregnancy/data`} />
                    }>
                        No pregnancies recorded

                    </PatientProfileSectionScaffold>
                );
            }
        }
        const pregnancy = this.props.data.pregnancy.sort((a, b) => parseInt(b.startDate) - parseInt(a.startDate))[0];
        if (!pregnancy) {
            return null;
        }

        const outcomeName = this.props.outcomeHash[pregnancy.outcome];
        const MedDRAName = this.props.meddra_Hash[pregnancy.meddra];

        return (
            <PatientProfileSectionScaffold sectionName='Last Pregnancy' actions={
                <EditButton to={`/patientProfile/${this.props.data.patientId}/edit/pregnancy/data`} />

            }>
                <>
                    <label>Start date: </label> {moment(pregnancy.startDate, 'x')._d.toDateString()}
                    {pregnancy.outcomeDate && pregnancy.outcomeDate !== '' ? <> <br /><label>End date: </label> {moment(pregnancy.outcomeDate, 'x')._d.toDateString()}</> : null}
                    {outcomeName ? <> <br /><label>Outcome: </label> {outcomeName}</> : null}
                    {MedDRAName ? <> <br /><label>MedDRA: </label> {MedDRAName.name}</> : null}
                </>

            </PatientProfileSectionScaffold>
        );
    }
}


/**
 * @prop {Object} this.props.match
 */
@connect(state => ({
    data: state.patientProfile.data,
    adminPriv: state.login.adminPriv
}))
class ConsentSection extends Component {
    constructor(props) {
        super();
        this.state = {
            selectedConsentDate: props.data.optimiseConsent ? moment(props.data.optimiseConsent) : undefined,
            selectedPregnancyConsentDate: props.data.pregnancySubStudyConsent ? moment(props.data.pregnancySubStudyConsent) : undefined
        };
        this._handleClickWithdrawConsent = this._handleClickWithdrawConsent.bind(this);
        this._handleClickGivesConsent = this._handleClickGivesConsent.bind(this);
        this._handleClickWithdrawParticipation = this._handleClickWithdrawParticipation.bind(this);
        this._handleConsentDateChange = this._handleConsentDateChange.bind(this);
        this._handleClickWithdrawPregnancyConsent = this._handleClickWithdrawPregnancyConsent.bind(this);
        this._handleClickGivesPregnancyConsent = this._handleClickGivesPregnancyConsent.bind(this);
        this._handlePregnancyConsentDateChange = this._handlePregnancyConsentDateChange.bind(this);

    }

    _handleConsentDateChange(date) {
        this.setState({
            selectedConsentDate: date
        });
    }

    _handleClickWithdrawConsent() {
        const { id } = this.props.data;
        const body = {
            patientId: this.props.match.params.patientId,
            data: {
                optimiseConsent: null,
                id: id
            }
        };
        store.dispatch(updateConsentAPICall(body));
    }

    _handleClickGivesConsent() {
        const { id } = this.props.data;
        const body = {
            patientId: this.props.match.params.patientId,
            data: {
                optimiseConsent: this.state.selectedConsentDate ? this.state.selectedConsentDate.toISOString() : null,
                id: id
            }
        };
        store.dispatch(updateConsentAPICall(body));
    }

    _handlePregnancyConsentDateChange(date) {
        this.setState({
            selectedPregnancyConsentDate: date
        });
    }

    _handleClickWithdrawPregnancyConsent() {
        const { id } = this.props.data;
        const body = {
            patientId: this.props.match.params.patientId,
            data: {
                pregnancySubStudyConsent: null,
                id: id
            }
        };
        store.dispatch(updateConsentAPICall(body));
    }

    _handleClickGivesPregnancyConsent() {
        const { id } = this.props.data;
        const body = {
            patientId: this.props.match.params.patientId,
            data: {
                pregnancySubStudyConsent: this.state.selectedPregnancyConsentDate ? this.state.selectedPregnancyConsentDate.toISOString() : null,
                id: id
            }
        };
        store.dispatch(updateConsentAPICall(body));
    }

    _handleClickWithdrawParticipation() {
        const { participation, id } = this.props.data;
        const body = {
            patientId: this.props.match.params.patientId,
            data: {
                participation: !participation,
                id: id
            }
        };
        store.dispatch(updateParticipationAPICall(body));
    }


    render() {
        const { participation, optimiseConsent, pregnancySubStudyConsent } = this.props.data;
        const femalePatient = this.props.data.demographicData && this.props.data.demographicData.gender !== 1;

        return <>
            <PatientProfileSectionScaffold sectionName='Study participation'>
                <button onClick={this._handleClickWithdrawParticipation} >{participation ? 'This patient withdraws from the study' : 'This patient re-enrolls in the study'}</button>
            </PatientProfileSectionScaffold>
            <PatientProfileSectionScaffold sectionName='Consent'>

                {
                    optimiseConsent ?
                        <div>
                            <span><b>Consent date</b>: {new Date(optimiseConsent).toLocaleDateString()}</span>
                            <button onClick={this._handleClickWithdrawConsent} >This patient withdraws consent</button>
                            <br /> <br />
                            <span>Select date of consent:</span>
                            <PickDate startDate={this.state.selectedConsentDate} handleChange={this._handleConsentDateChange} />
                            <button disabled={this.state.selectedConsentDate === undefined} onClick={this._handleClickGivesConsent}>Change consent date</button>
                        </div>
                        :
                        <div>
                            <span>Select date of consent:</span>
                            <PickDate handleChange={this._handleConsentDateChange} />
                            <button disabled={this.state.selectedConsentDate === undefined} onClick={this._handleClickGivesConsent}>Patient gives consent</button>
                        </div>
                }



            </PatientProfileSectionScaffold>
            {femalePatient
                ? <PatientProfileSectionScaffold sectionName='Pregnancy sub study consent'>
                    {
                        pregnancySubStudyConsent ?
                            <div>
                                <span><b>Consent date</b>: {new Date(pregnancySubStudyConsent).toLocaleDateString()}</span>
                                <button onClick={this._handleClickWithdrawPregnancyConsent} >Withdraw pregnancy sub study consent</button>
                                <br /> <br />
                                <span>Select date of consent: </span>
                                <PickDate startDate={this.state.selectedPregnancyConsentDate} handleChange={this._handlePregnancyConsentDateChange} />
                                <button disabled={this.state.selectedPregnancyConsentDate === undefined} onClick={this._handleClickGivesPregnancyConsent}>Change pregnancy sub study consent date</button>
                            </div>
                            :
                            <div>
                                <span>Select date of consent: </span>
                                <PickDate handleChange={this._handlePregnancyConsentDateChange} />
                                <button disabled={this.state.selectedPregnancyConsentDate === undefined} onClick={this._handleClickGivesPregnancyConsent}>Patient gives consent for pregnancy sub study</button>
                            </div>
                    }
                </PatientProfileSectionScaffold>
                : null}
        </>;
    }
}


/**
 * @prop {Object} this.props.match
 */
@connect(state => ({
    data: state.patientProfile.data,
    adminPriv: state.login.adminPriv
}))
class DeletePatient extends Component {
    constructor() {
        super();
        this._handleClickDelete = this._handleClickDelete.bind(this);
        this._deleteFunction = this._deleteFunction.bind(this);
    }

    _handleClickDelete() {
        store.dispatch(addAlert({ alert: `Are you sure you want to delete patient ${this.props.data.patientId}?`, handler: this._deleteFunction }));
    }

    _deleteFunction() {
        const body = {
            patientId: this.props.match.params.patientId,
            data: {
                patientId: this.props.data.id
            }
        };
        store.dispatch(erasePatientAPICall(body));
    }

    render() {

        if (this.props.adminPriv === 1)
            return <PatientProfileSectionScaffold sectionName='Delete'>
                <button onClick={this._handleClickDelete} className={style.deleteButton}>Delete this patient</button>
            </PatientProfileSectionScaffold>;
        return null;
    }
}