@@ -14,7 +14,7 @@ use crate::estrannaise::compute_estrannaise_series;
1414use crate :: layout:: page_layout;
1515use crate :: store:: use_store;
1616use 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} ;
2020use 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