@@ -8,8 +8,8 @@ use std::rc::Rc;
88
99use crate :: layout:: page_layout;
1010use crate :: store:: use_store;
11- use crate :: utils:: { fmt_decimal, parse_date_or_now, parse_decimal} ;
12- use hrt_shared:: types:: { InjectableEstradiols , SubVial , Vial } ;
11+ use crate :: utils:: { fmt_decimal, injectable_iu_from_dose , parse_date_or_now, parse_decimal} ;
12+ use hrt_shared:: types:: { DosageHistoryEntry , InjectableEstradiols , SubVial , Vial } ;
1313
1414const ESTER_OPTIONS : [ InjectableEstradiols ; 6 ] = [
1515 InjectableEstradiols :: Benzoate ,
@@ -74,6 +74,70 @@ pub fn VialsPage() -> impl IntoView {
7474 let new_sub_numbers = create_rw_signal ( HashMap :: < String , String > :: new ( ) ) ;
7575 let new_sub_iu = create_rw_signal ( HashMap :: < String , String > :: new ( ) ) ;
7676
77+ let cartridge_usage = {
78+ let store = store. clone ( ) ;
79+ move || {
80+ let data_value = store. data . get ( ) ;
81+ let include_spent = show_spent. get ( ) ;
82+ let mut items = Vec :: new ( ) ;
83+ for vial in & data_value. vials {
84+ if !include_spent && vial. isSpent . unwrap_or ( false ) {
85+ continue ;
86+ }
87+ for sub in & vial. subVials {
88+ let initial = sub. initialIu ;
89+ let mut used = 0.0 ;
90+ let mut skipped = 0 ;
91+ for entry in & data_value. dosageHistory {
92+ let ( dose, unit, sub_id, entry_vial) = match entry {
93+ DosageHistoryEntry :: InjectableEstradiol {
94+ dose,
95+ unit,
96+ subVialId,
97+ vialId,
98+ ..
99+ } => ( dose, unit, subVialId. as_ref ( ) , vialId. as_ref ( ) ) ,
100+ _ => continue ,
101+ } ;
102+ if sub_id != Some ( & sub. id ) {
103+ continue ;
104+ }
105+ let iu = injectable_iu_from_dose (
106+ & data_value,
107+ * dose,
108+ unit,
109+ entry_vial. or ( Some ( & vial. id ) ) ,
110+ Some ( & vial. id ) ,
111+ ) ;
112+ if let Some ( value) = iu {
113+ used += value;
114+ } else {
115+ skipped += 1 ;
116+ }
117+ }
118+ if initial. is_some ( ) || used > 0.0 || skipped > 0 {
119+ let label = format ! (
120+ "{} · #{}" ,
121+ vial. esterKind
122+ . clone( )
123+ . unwrap_or_else( || "Unknown" . to_string( ) ) ,
124+ sub. personalNumber
125+ ) ;
126+ items. push ( (
127+ sub. id . clone ( ) ,
128+ label,
129+ initial,
130+ used,
131+ initial. map ( |v| v - used) ,
132+ skipped,
133+ ) ) ;
134+ }
135+ }
136+ }
137+ items
138+ }
139+ } ;
140+
77141 let update_sub_number = {
78142 let new_sub_numbers = new_sub_numbers;
79143 move |vial_id : String , value : String | {
@@ -242,6 +306,40 @@ pub fn VialsPage() -> impl IntoView {
242306 <A href="/vials/create" >"Create New Vial" </A >
243307 </div>
244308 </div>
309+ <div class="card" >
310+ <h3>"Cartridge Balance" </h3>
311+ <p class="muted" >"Used and remaining IU for tracked cartridges." </p>
312+ <Show
313+ when=move || !cartridge_usage( ) . is_empty( )
314+ fallback=move || view! { <p class="muted" >"No cartridge tracking yet." </p> }
315+ >
316+ <ul class="muted" >
317+ <For
318+ each=cartridge_usage
319+ key=|( sub_id, _, _, _, _, _) | sub_id. clone( )
320+ children=move |( _, label, initial, used, remaining, skipped) | {
321+ let initial_label = initial
322+ . map( |v| format!( "{} IU" , fmt_decimal( v, 0 ) ) )
323+ . unwrap_or_else( || "Set starting IU" . to_string( ) ) ;
324+ let used_label = format!( "{} IU used" , fmt_decimal( used, 0 ) ) ;
325+ let remaining_label = remaining
326+ . map( |v| format!( "{} IU left" , fmt_decimal( v, 0 ) ) )
327+ . unwrap_or_else( || "Unknown remaining" . to_string( ) ) ;
328+ let skipped_label = if skipped > 0 {
329+ format!( " · {skipped} entry(s) missing concentration" )
330+ } else {
331+ "" . to_string( )
332+ } ;
333+ view! {
334+ <li>
335+ { format!( "{label}: {initial_label} · {used_label} · {remaining_label}{skipped_label}" ) }
336+ </li>
337+ }
338+ }
339+ />
340+ </ul>
341+ </Show >
342+ </div>
245343 <Show
246344 when=move || !vials( ) . is_empty( )
247345 fallback=move || view! { <p class="muted" >"No vials yet." </p> }
0 commit comments