Skip to content

Commit 5204cd4

Browse files
committed
HRT-0003: add concentration control on estrannaise page
1 parent 0e784c5 commit 5204cd4

File tree

1 file changed

+111
-2
lines changed

1 file changed

+111
-2
lines changed

crates/web/src/pages/estrannaise.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::estrannaise::compute_estrannaise_series;
1414
use crate::layout::page_layout;
1515
use crate::store::use_store;
1616
use crate::utils::{
17-
compute_fudge_factor, fmt_blood_value, fmt_date_label, hormone_unit_label,
17+
compute_fudge_factor, fmt_blood_value, fmt_date_label, fmt_decimal, hormone_unit_label,
1818
injectable_dose_from_iu, parse_decimal,
1919
};
2020
use hrt_shared::logic::predict_e2_pg_ml;
@@ -107,6 +107,77 @@ pub fn EstrannaisePage() -> impl IntoView {
107107
}
108108
});
109109

110+
let concentration_target_vial = create_memo({
111+
let store = store.clone();
112+
move |_| {
113+
let data_value = store.data.get();
114+
let schedule_vial_id = data_value
115+
.injectableEstradiol
116+
.as_ref()
117+
.and_then(|cfg| cfg.vialId.clone())?;
118+
let vial = data_value
119+
.vials
120+
.iter()
121+
.find(|vial| vial.id == schedule_vial_id)?;
122+
let mut label = vial
123+
.esterKind
124+
.clone()
125+
.unwrap_or_else(|| "Unknown ester".to_string());
126+
if let Some(batch) = &vial.batchNumber {
127+
label.push_str(&format!(" · {batch}"));
128+
}
129+
Some((schedule_vial_id, label, vial.concentrationMgPerMl))
130+
}
131+
});
132+
133+
let concentration_input = create_rw_signal(String::new());
134+
create_effect({
135+
let concentration_target_vial = concentration_target_vial.clone();
136+
move |_| {
137+
let value = concentration_target_vial
138+
.get()
139+
.and_then(|(_, _, concentration)| concentration)
140+
.map(|concentration| fmt_decimal(concentration, 3))
141+
.unwrap_or_default();
142+
concentration_input.set(value);
143+
}
144+
});
145+
146+
let apply_concentration_update = {
147+
let store = store.clone();
148+
let concentration_target_vial = concentration_target_vial.clone();
149+
let concentration_input = concentration_input;
150+
move |raw_value: String| {
151+
let Some((vial_id, _, current)) = concentration_target_vial.get() else {
152+
return;
153+
};
154+
let trimmed = raw_value.trim().to_string();
155+
let next = if trimmed.is_empty() {
156+
None
157+
} else {
158+
parse_decimal(&trimmed).filter(|value| *value > 0.0)
159+
};
160+
if !trimmed.is_empty() && next.is_none() {
161+
concentration_input.set(
162+
current
163+
.map(|value| fmt_decimal(value, 3))
164+
.unwrap_or_default(),
165+
);
166+
return;
167+
}
168+
if next == current {
169+
return;
170+
}
171+
store.data.update(|data| {
172+
if let Some(vial) = data.vials.iter_mut().find(|vial| vial.id == vial_id) {
173+
vial.concentrationMgPerMl = next;
174+
}
175+
});
176+
store.mark_dirty();
177+
}
178+
};
179+
let apply_concentration_update = StoredValue::new(Rc::new(apply_concentration_update));
180+
110181
let estrannaise_series = create_memo({
111182
let settings = store.settings;
112183
move |_| {
@@ -383,6 +454,44 @@ pub fn EstrannaisePage() -> impl IntoView {
383454
<option value="8" selected=move || forecast_weeks.get() == 8>"8"</option>
384455
</select>
385456
</div>
457+
<Show when=move || store.settings.get().displayInjectableInIU.unwrap_or(false)>
458+
<Show
459+
when=move || concentration_target_vial.get().is_some()
460+
fallback=move || view! {
461+
<div class="chart-toolbar-group">
462+
<span class="muted">
463+
"Select a schedule vial to set concentration here."
464+
</span>
465+
</div>
466+
}
467+
>
468+
<div class="chart-toolbar-group">
469+
<label class="muted">"Concentration (mg/mL)"</label>
470+
<input
471+
type="text"
472+
step="any"
473+
min="0"
474+
class="chart-input"
475+
placeholder="auto"
476+
on:input=move |ev| concentration_input.set(event_target_value(&ev))
477+
on:change=move |ev| {
478+
let apply_concentration_update =
479+
apply_concentration_update.get_value();
480+
apply_concentration_update(event_target_value(&ev))
481+
}
482+
prop:value=move || concentration_input.get()
483+
/>
484+
<span class="muted">
485+
{move || {
486+
concentration_target_vial
487+
.get()
488+
.map(|(_, label, _)| format!("Schedule vial: {label}"))
489+
.unwrap_or_default()
490+
}}
491+
</span>
492+
</div>
493+
</Show>
494+
</Show>
386495
<div class="chart-toolbar-group">
387496
<label class="muted">
388497
{move || if forecast_dose_in_iu.get() { "Dose (IU)" } else { "Dose (mg)" }}
@@ -402,7 +511,7 @@ pub fn EstrannaisePage() -> impl IntoView {
402511
&& !forecast_dose_in_iu.get()
403512
}
404513
>
405-
<span class="muted">"Set injectable vial concentration to enter IU."</span>
514+
<span class="muted">"Set concentration above to enter IU."</span>
406515
</Show>
407516
</div>
408517
<div class="chart-toolbar-group">

0 commit comments

Comments
 (0)