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
2 changes: 1 addition & 1 deletion app/controllers/patients_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def respond_to_update_for_json_format
if @patient.update patient_params
@patient.reload
render json: {
patient: @patient.reload.as_json,
patient: @patient.as_json.merge(status_help_text: helpers.status_help_text(@patient)),
flash: {
notice: t('flash.patient_info_saved', timestamp: Time.zone.now.display_timestamp)
}
Expand Down
67 changes: 58 additions & 9 deletions app/javascript/src/components/Input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default Input = ({
name,
id,
type = "text",
value: initialValue,
value,
required,
help,
className,
Expand All @@ -15,23 +15,72 @@ export default Input = ({
tooltip,
...props
}) => {
const [value, setValue] = useState(initialValue || "")
// if we don't have an onChange handler, this component is an uncontrolled component
// if we have an onChange handler, we'll manage state like a traditional controlled component
const Component = !onChange ? UncontrolledInput : ControlledInput
return <Component
label={label}
name={name}
id={id}
type={type}
value={value}
required={required}
help={help}
className={className}
labelClassName={labelClassName}
autocomplete={autocomplete}
onChange={onChange}
tooltip={tooltip}
{...props}
/>
};


const UncontrolledInput = ({
label,
name,
id,
type,
value,
required,
help,
className,
labelClassName,
autocomplete,
onChange,
tooltip,
...props
}) => {
const labelClassNames = `${required ? 'required' : ''} ${labelClassName || ''}`
const inputClassNames = `form-control ${className || ''} ${labelClassNames.includes('sr-only') ? 'mt-6' : ''}`

const handleChange = (e) => {
setValue(e.target.value)
onChange?.(e)
}

return (
<div className="form-group">
<label className={labelClassNames} htmlFor={id}>
{label}
{tooltip}
</label>
<input type={type} name={name} id={id} required={required} autoComplete={autocomplete} className={inputClassNames} onChange={handleChange} value={value} {...props}></input>
<input type={type} name={name} id={id} required={required} autoComplete={autocomplete} className={inputClassNames} onChange={onChange} value={value} {...props}></input>
{help && <small className="form-text text-muted">{help}</small>}
</div>
)
};
}

const ControlledInput = ({
value: initialValue,
onChange,
...props
}) => {
const [value, setValue] = useState(initialValue || "")
const handleChange = (e) => {
setValue(e.target.value)
onChange?.(e)
}

return <UncontrolledInput
value={value}
onChange={handleChange}
{...props}
/>
}

19 changes: 6 additions & 13 deletions app/javascript/src/components/PatientDashboardForm.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from "react";
import React, { useState, useEffect } from "react";
import Input from './Input'
import Select from './Select'
import Tooltip from './Tooltip'
Expand All @@ -9,8 +9,6 @@ export default PatientDashboardForm = ({
patient,
weeksOptions,
daysOptions,
initialCallDate,
statusHelpText,
isAdmin,
patientPath,
formAuthenticityToken
Expand All @@ -22,11 +20,8 @@ export default PatientDashboardForm = ({

const [patientData, setPatientData] = useState(patient)

const statusTooltip = statusHelpText ? <Tooltip text={statusHelpText} /> : null

const autosave = async (updatedData) => {
const updatedPatientData = { ...patientData, ...updatedData }
setPatientData(updatedPatientData)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the main culprit right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually the change below with the debounce was the main culprit. a combo of the useMemo and not passing the params through to the autosave function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, good to know! makes sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really need to learn to just stop reaching for useMemo. I mess it up nearly every time and it has cost me hours of my life 😅


const putData = {
name: updatedPatientData.name,
Expand All @@ -44,9 +39,7 @@ export default PatientDashboardForm = ({
}
}

const debouncedAutosave = useMemo((params) => {
return debounce(autosave, 300)
}, []);
const debouncedAutosave = debounce(autosave);

// Stop the invocation of the debounced function after unmounting
useEffect(() => {
Expand Down Expand Up @@ -88,8 +81,8 @@ export default PatientDashboardForm = ({
label={i18n.t('common.days_along')}
labelClassName="sr-only"
options={daysOptions}
value={weeksOptions.find(opt => opt.value === patientData.last_menstrual_period_days)?.value}
help={i18n.t('patient.dashboard.called_on', { date: initialCallDate })}
value={daysOptions.find(opt => opt.value === patientData.last_menstrual_period_days)?.value}
help={i18n.t('patient.dashboard.called_on', { date: patientData.initial_call_date_display })}
onChange={e => autosave({ last_menstrual_period_days: e.target.value })}
/>
</div>
Expand Down Expand Up @@ -131,8 +124,8 @@ export default PatientDashboardForm = ({
label={i18n.t('patient.shared.status')}
value={patientData.status}
className="form-control-plaintext"
tooltip={statusTooltip}
onChange={e => debouncedAutosave({ status: e.target.value })}
disabled={true}
tooltip={patientData.status_help_text ? <Tooltip text={patientData.status_help_text} /> : null}
/>
</div>

Expand Down
8 changes: 5 additions & 3 deletions app/javascript/src/components/Select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ export default Select = ({
...props
}) => {
const labelClassNames = `${required ? 'required' : ''} ${labelClassName || ''}`
const selectClassNames = `form-control ${className || ''} ${labelClassNames.includes('sr-only') ? 'mt-6' : ''}`

const selectClassNames = `form-control ${className || ''} ${labelClassNames.includes('sr-only') ? 'mt-4' : ''}`

const selectedValue = !!value || value === 0 ? value : ""
if (!!onChange) {
props["value"] = value || ""
props["value"] = selectedValue
} else {
props["defaultValue"] = value || ""
props["defaultValue"] = selectedValue
}

return (
Expand Down
3 changes: 2 additions & 1 deletion app/models/patient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ def as_json
last_menstrual_period_at_appt_days: last_menstrual_period_at_appt_days,
last_menstrual_period_now_weeks: last_menstrual_period_now_weeks,
last_menstrual_period_now_days: last_menstrual_period_now_days,
primary_phone_display: primary_phone_display
primary_phone_display: primary_phone_display,
initial_call_date_display: initial_call_date.strftime('%m/%d/%Y')
)
end

Expand Down
85 changes: 9 additions & 76 deletions app/views/patients/_patient_dashboard.html.erb
Original file line number Diff line number Diff line change
@@ -1,77 +1,10 @@
<div id="patient_dashboard_content">
<%= bootstrap_form_with model: patient,
html: { id: 'patient_dashboard_form' },
local: false,
method: 'patch',
class: 'edit_patient' do |f| %>
<div class="row">

<div class="col-4">
<%= f.text_field :name,
label: t('patient.shared.name'),
autocomplete: 'off' %>
</div>

<div class="col">
<%= f.select :last_menstrual_period_weeks,
options_for_select(weeks_options, patient.last_menstrual_period_weeks ),
label: t('patient.dashboard.weeks_along'),
autocomplete: 'off',
help: t('patient.dashboard.currently', weeks: patient.last_menstrual_period_now_weeks, days: patient.last_menstrual_period_now_days) %>
</div>

<div class="col mt-4">
<%= f.select :last_menstrual_period_days,
options_for_select(days_options, patient.last_menstrual_period_days),
autocomplete: 'off',
skip_label: true,
help: t('patient.dashboard.called_on', date: patient.initial_call_date.strftime("%m/%d/%Y")) %>
<%= f.label :last_menstrual_period_days, t('common.days_along'), class: "sr-only" %>
</div>

<div class="col-3">
<%= f.date_field :appointment_date,
label: t('patient.shared.appt_date'),
autocomplete: 'off',
help: t('patient.dashboard.approx_gestation', weeks: patient.last_menstrual_period_at_appt_weeks, days: patient.last_menstrual_period_at_appt_days) %>
</div>
</div>

<div class="row">
<div class="col-4">
<%= f.text_field :primary_phone,
value: patient.primary_phone_display,
label: t('patient.dashboard.phone'),
autocomplete: 'off' %>
</div>

<div class="col">
<%= f.text_field :pronouns,
value: patient.pronouns,
autocomplete: 'off' %>
</div>

<div class="col">
<div class="form-group">
<label for="status"><%= t 'patient.shared.status' %> <%= tooltip_shell status_help_text(patient) %></label>
<input type="text" value="<%= patient.status %>" class="form-control form-control-plaintext" id="patient_status_display" autocomplete="off" disabled>
</div>
</div>

<div class="col-3">
<% if current_user.admin? %>
<div class="form-group">
<label for="admin-delete"><%= t 'patient.dashboard.delete_label' %></label>
<div>
<%= link_to t('patient.dashboard.delete'),
patient_path(patient),
class: 'btn btn-danger',
method: :delete,
data: { confirm: t('patient.dashboard.confirm_del', name: patient.name) } %>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
</div>
<%= render ReactComponent.new("PatientDashboardForm", raw_props: {
patient: patient.as_json.merge(status_help_text: status_help_text(@patient)),
weeksOptions: weeks_options.map { |opt| { label: opt[0], value: opt[1] } },
daysOptions: days_options.map { |opt| { label: opt[0], value: opt[1] } },
isAdmin: current_user.admin?,
patientPath: patient_path(patient),
formAuthenticityToken: form_authenticity_token
}) %>
</div>
5 changes: 5 additions & 0 deletions test/integration_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,9 @@ def click_away_from_field
find('nav').click
wait_for_ajax
end

def fill_in_field_with_autosave(locator, with:)
fill_in locator, with: with
sleep 0.3 # let frontend debounce do its thing
end
end
18 changes: 4 additions & 14 deletions test/system/update_patient_info_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ class UpdatePatientInfoTest < ApplicationSystemTestCase
describe 'changing patient dashboard information' do
describe 'updating name' do
before do
fill_in 'First and last name', with: 'Susie Everyteen 2'
click_away_from_field
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

honestly dope

wait_for_ajax
fill_in_field_with_autosave 'First and last name', with: 'Susie Everyteen 2'
reload_page_and_click_link 'Patient Information'
end

Expand Down Expand Up @@ -74,9 +72,7 @@ class UpdatePatientInfoTest < ApplicationSystemTestCase

describe 'updating appointment date' do
before do
fill_in 'Appointment date', with: 5.days.from_now.strftime('%m/%d/%Y')
click_away_from_field
wait_for_ajax
fill_in_field_with_autosave 'Appointment date', with: 5.days.from_now.strftime('%m/%d/%Y')
reload_page_and_click_link 'Patient Information'
end

Expand Down Expand Up @@ -107,9 +103,7 @@ class UpdatePatientInfoTest < ApplicationSystemTestCase

describe 'updating phone number' do
before do
fill_in 'Phone number', with: '123-666-8888'
click_away_from_field
wait_for_ajax
fill_in_field_with_autosave 'Phone number', with: '123-666-8888'
reload_page_and_click_link 'Patient Information'
end

Expand All @@ -122,9 +116,7 @@ class UpdatePatientInfoTest < ApplicationSystemTestCase

describe 'updating pronouns' do
before do
fill_in 'Pronouns', with: 'they/them'
click_away_from_field
wait_for_ajax
fill_in_field_with_autosave 'Pronouns', with: 'they/them'
reload_page_and_click_link 'Patient Information'
end

Expand Down Expand Up @@ -298,7 +290,6 @@ class UpdatePatientInfoTest < ApplicationSystemTestCase

it 'should flash failure on a bad field change' do
fill_in 'Phone number', with: '111-222-3333445'
click_away_from_field
assert has_text? 'Primary phone is the wrong length'
end
end
Expand Down Expand Up @@ -365,7 +356,6 @@ class UpdatePatientInfoTest < ApplicationSystemTestCase
private

def reload_page_and_click_link(link_text)
click_away_from_field
visit authenticated_root_path
visit edit_patient_path @patient
click_link link_text
Expand Down
Loading