From 125ddc31175e8f4582b610375c992a4238f3fff6 Mon Sep 17 00:00:00 2001 From: stefiosif Date: Sat, 17 Jan 2026 19:27:35 +0200 Subject: [PATCH] Change `ResidentId(String)` to `ResidentId(u8)`, impl `Copy` for `ShiftType`, use `u8` for all `UserConfig` params --- src-tauri/src/config.rs | 25 +++--- src-tauri/src/export.rs | 12 +-- src-tauri/src/resident.rs | 24 +++--- src-tauri/src/schedule.rs | 41 +++++----- src-tauri/src/scheduler.rs | 20 ++--- src-tauri/src/workload.rs | 142 ++++++++++++++------------------- src-tauri/tests/integration.rs | 72 ++++++++--------- src/routes/state.svelte.ts | 17 ++-- 8 files changed, 161 insertions(+), 192 deletions(-) diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 9f794a9..4a4cf65 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -12,11 +12,8 @@ const YEAR: i32 = 2026; pub struct ToxicPair((ResidentId, ResidentId)); impl ToxicPair { - pub fn new(res_id_1: &str, res_id_2: &str) -> Self { - Self(( - ResidentId(res_id_1.to_string()), - ResidentId(res_id_2.to_string()), - )) + pub fn new(res_id_1: u8, res_id_2: u8) -> Self { + Self((ResidentId(res_id_1), ResidentId(res_id_2))) } pub fn matches(&self, other: &ToxicPair) -> bool { @@ -35,18 +32,18 @@ impl From<(ResidentId, ResidentId)> for ToxicPair { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct UserConfigDTO { - month: usize, + month: u8, year: i32, - holidays: Vec, + holidays: Vec, residents: Vec, - toxic_pairs: Vec<(String, String)>, + toxic_pairs: Vec<(u8, u8)>, } #[derive(Debug, Clone)] pub struct UserConfig { pub month: Month, pub year: i32, - pub holidays: Vec, + pub holidays: Vec, pub residents: Vec, pub toxic_pairs: Vec, @@ -80,7 +77,7 @@ impl UserConfig { } } - pub fn with_holidays(mut self, holidays: Vec) -> Self { + pub fn with_holidays(mut self, holidays: Vec) -> Self { self.holidays = holidays; self.total_holiday_slots = self.total_holiday_slots(); self @@ -110,7 +107,7 @@ impl UserConfig { pub fn is_holiday_or_weekend_slot(&self, day: u8) -> bool { let day = Day(day); day.is_weekend(self.month.number_from_month(), self.year) - || self.holidays.contains(&(day.0 as usize)) + || self.holidays.contains(&(day.0)) } } @@ -144,7 +141,7 @@ impl Default for UserConfig { impl From for UserConfig { fn from(value: UserConfigDTO) -> Self { - let month = Month::try_from(value.month as u8).unwrap(); + let month = Month::try_from(value.month).unwrap(); let total_days = month.num_days(YEAR).unwrap(); @@ -155,7 +152,7 @@ impl From for UserConfig { let total_holiday_slots = (1..=total_days) .filter(|&d| { Day(d).is_weekend(month.number_from_month(), value.year) - || value.holidays.contains(&(d as usize)) + || value.holidays.contains(&d) }) .map(|d| if Day(d).is_open_shift() { 2 } else { 1 }) .sum(); @@ -168,7 +165,7 @@ impl From for UserConfig { toxic_pairs: value .toxic_pairs .into_iter() - .map(|p| ToxicPair::new(&p.0, &p.1)) + .map(|p| ToxicPair::new(p.0, p.1)) .collect(), total_days, total_slots, diff --git a/src-tauri/src/export.rs b/src-tauri/src/export.rs index 500e6b5..6fb7546 100644 --- a/src-tauri/src/export.rs +++ b/src-tauri/src/export.rs @@ -179,12 +179,12 @@ mod tests { #[fixture] fn config() -> UserConfig { UserConfig::default().with_residents(vec![ - Resident::new("1", "Στέφανος"), - Resident::new("2", "Ιορδάνης"), - Resident::new("3", "Μαρία"), - Resident::new("4", "Βεατρίκη"), - Resident::new("5", "Τάκης"), - Resident::new("6", "Μάκης"), + Resident::new(1, "Στέφανος"), + Resident::new(2, "Ιορδάνης"), + Resident::new(3, "Μαρία"), + Resident::new(4, "Βεατρίκη"), + Resident::new(5, "Τάκης"), + Resident::new(6, "Μάκης"), ]) } diff --git a/src-tauri/src/resident.rs b/src-tauri/src/resident.rs index 526e66f..0258c94 100644 --- a/src-tauri/src/resident.rs +++ b/src-tauri/src/resident.rs @@ -5,8 +5,8 @@ use crate::{ slot::{Day, ShiftPosition, Slot}, }; -#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] -pub struct ResidentId(pub String); +#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Copy)] +pub struct ResidentId(pub u8); #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -15,7 +15,7 @@ pub struct Resident { pub name: String, pub negative_shifts: Vec, pub manual_shifts: Vec, - pub max_shifts: Option, + pub max_shifts: Option, pub allowed_types: Vec, pub reduced_load: bool, } @@ -23,19 +23,19 @@ pub struct Resident { #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ResidentDTO { - id: String, + id: u8, name: String, - negative_shifts: Vec, + negative_shifts: Vec, manual_shifts: Vec, - max_shifts: Option, + max_shifts: Option, allowed_types: Vec, reduced_load: bool, } impl Resident { - pub fn new(id: &str, name: &str) -> Self { + pub fn new(id: u8, name: &str) -> Self { Self { - id: ResidentId(id.to_string()), + id: ResidentId(id), name: name.to_string(), negative_shifts: Vec::new(), manual_shifts: Vec::new(), @@ -54,7 +54,7 @@ impl Resident { self } - pub fn with_max_shifts(mut self, max_shifts: usize) -> Self { + pub fn with_max_shifts(mut self, max_shifts: u8) -> Self { self.max_shifts = Some(max_shifts); self } @@ -80,11 +80,7 @@ impl From for Resident { Self { id: ResidentId(value.id), name: value.name, - negative_shifts: value - .negative_shifts - .into_iter() - .map(|d| Day(d as u8)) - .collect(), + negative_shifts: value.negative_shifts.into_iter().map(Day).collect(), manual_shifts: value .manual_shifts .into_iter() diff --git a/src-tauri/src/schedule.rs b/src-tauri/src/schedule.rs index 00029f7..bd5d775 100644 --- a/src-tauri/src/schedule.rs +++ b/src-tauri/src/schedule.rs @@ -23,7 +23,7 @@ impl MonthlySchedule { pub fn prefill(&mut self, config: &UserConfig) { for r in &config.residents { for s in &r.manual_shifts { - self.insert(*s, &r.id); + self.insert(*s, r.id); } } } @@ -32,8 +32,8 @@ impl MonthlySchedule { self.0.get(slot) } - pub fn insert(&mut self, slot: Slot, resident_id: &ResidentId) { - self.0.insert(slot, resident_id.clone()); + pub fn insert(&mut self, slot: Slot, resident_id: ResidentId) { + self.0.insert(slot, resident_id); } pub fn remove(&mut self, slot: Slot) { @@ -102,7 +102,7 @@ impl MonthlySchedule { return config .toxic_pairs .iter() - .any(|pair| pair.matches(&ToxicPair::from((r1.clone(), r2.clone())))); + .any(|pair| pair.matches(&ToxicPair::from((*r1, *r2)))); } false @@ -184,7 +184,7 @@ impl Serialize for MonthlySchedule { } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Copy)] pub enum ShiftType { Closed, OpenFirst, @@ -209,24 +209,24 @@ mod tests { #[fixture] fn resident() -> Resident { - Resident::new("1", "Stefanos") + Resident::new(1, "Stefanos") } #[fixture] fn toxic_config() -> UserConfig { UserConfig::default() .with_residents(vec![ - Resident::new("1", "Stefanos"), - Resident::new("2", "Iordanis"), + Resident::new(1, "Stefanos"), + Resident::new(2, "Iordanis"), ]) - .with_toxic_pairs(vec![ToxicPair::new("1", "2")]) + .with_toxic_pairs(vec![ToxicPair::new(1, 2)]) } #[fixture] fn config() -> UserConfig { UserConfig::default().with_residents(vec![ - Resident::new("1", "Stefanos"), - Resident::new("2", "Iordanis"), + Resident::new(1, "Stefanos"), + Resident::new(2, "Iordanis"), ]) } @@ -235,12 +235,9 @@ mod tests { let slot_1 = Slot::new(Day(1), ShiftPosition::First); let slot_2 = Slot::new(Day(1), ShiftPosition::Second); - schedule.insert(slot_1, &resident.id); + schedule.insert(slot_1, resident.id); - assert_eq!( - schedule.get_resident_id(&slot_1), - Some(&ResidentId("1".to_string())) - ); + assert_eq!(schedule.get_resident_id(&slot_1), Some(&ResidentId(1))); assert_eq!(schedule.get_resident_id(&slot_2), None); } @@ -248,7 +245,7 @@ mod tests { fn test_remove_resident(mut schedule: MonthlySchedule, resident: Resident) { let slot_1 = Slot::new(Day(1), ShiftPosition::First); - schedule.insert(slot_1, &resident.id); + schedule.insert(slot_1, resident.id); schedule.remove(slot_1); assert_eq!(schedule.get_resident_id(&slot_1), None); @@ -260,9 +257,9 @@ mod tests { let slot_2 = Slot::new(Day(1), ShiftPosition::Second); let slot_3 = Slot::new(Day(2), ShiftPosition::First); - schedule.insert(slot_1, &resident.id); - schedule.insert(slot_2, &resident.id); - schedule.insert(slot_3, &resident.id); + schedule.insert(slot_1, resident.id); + schedule.insert(slot_2, resident.id); + schedule.insert(slot_3, resident.id); assert!(!schedule.has_resident_in_consecutive_days(&slot_1)); assert!(!schedule.has_resident_in_consecutive_days(&slot_2)); @@ -277,8 +274,8 @@ mod tests { let stefanos = &toxic_config.residents[0]; let iordanis = &toxic_config.residents[1]; - schedule.insert(slot_1, &stefanos.id); - schedule.insert(slot_2, &iordanis.id); + schedule.insert(slot_1, stefanos.id); + schedule.insert(slot_2, iordanis.id); assert!(schedule.has_toxic_pair(&slot_2, &toxic_config)) } diff --git a/src-tauri/src/scheduler.rs b/src-tauri/src/scheduler.rs index 2be22bc..92d0c2b 100644 --- a/src-tauri/src/scheduler.rs +++ b/src-tauri/src/scheduler.rs @@ -27,7 +27,7 @@ impl Scheduler { pub fn run(&self, schedule: &mut MonthlySchedule, tracker: &mut WorkloadTracker) -> bool { schedule.prefill(&self.config); for (slot, res_id) in schedule.0.iter() { - tracker.insert(res_id, &self.config, *slot); + tracker.insert(*res_id, &self.config, *slot); } self.search(schedule, tracker, Slot::default()) @@ -64,7 +64,7 @@ impl Scheduler { (workload, (tie_breaker * 1000.0) as usize) }); - for id in &valid_resident_ids { + for id in valid_resident_ids { schedule.insert(slot, id); tracker.insert(id, &self.config, slot); @@ -80,7 +80,7 @@ impl Scheduler { } /// Return all valid residents for the current slot - pub fn valid_residents(&self, slot: Slot, schedule: &MonthlySchedule) -> Vec<&ResidentId> { + pub fn valid_residents(&self, slot: Slot, schedule: &MonthlySchedule) -> Vec { let required_type = slot.shift_type(); let other_resident = slot .other_position() @@ -94,7 +94,7 @@ impl Scheduler { && !r.negative_shifts.contains(&slot.day) && r.allowed_types.contains(&required_type) }) - .map(|r| &r.id) + .map(|r| r.id) .collect() } } @@ -120,12 +120,12 @@ mod tests { #[fixture] fn config() -> UserConfig { UserConfig::default().with_residents(vec![ - Resident::new("1", "Stefanos"), - Resident::new("2", "Iordanis"), - Resident::new("3", "Maria"), - Resident::new("4", "Veatriki"), - Resident::new("5", "Takis"), - Resident::new("6", "Akis"), + Resident::new(1, "Stefanos"), + Resident::new(2, "Iordanis"), + Resident::new(3, "Maria"), + Resident::new(4, "Veatriki"), + Resident::new(5, "Takis"), + Resident::new(6, "Akis"), ]) } diff --git a/src-tauri/src/workload.rs b/src-tauri/src/workload.rs index 1a3fdf1..346c5b0 100644 --- a/src-tauri/src/workload.rs +++ b/src-tauri/src/workload.rs @@ -52,8 +52,7 @@ impl WorkloadBounds { // if all residents have a manually set max shifts size, just use those values for the max workload if auto_computed_residents.is_empty() { for r in &config.residents { - self.max_workloads - .insert(r.id.clone(), r.max_shifts.unwrap_or(0) as u8); + self.max_workloads.insert(r.id, r.max_shifts.unwrap_or(0)); } return; } @@ -61,23 +60,23 @@ impl WorkloadBounds { // Untested scenario: Resident has manual max_shifts and also reduced workload flag // Probably should forbid using both options from GUI - let manual_max_shifts_sum: usize = config + let manual_max_shifts_sum: u8 = config .residents .iter() .map(|r| r.max_shifts.unwrap_or(0)) .sum(); - let max_shifts_ceiling = ((config.total_slots as usize - manual_max_shifts_sum) as f32 + let max_shifts_ceiling = ((config.total_slots - manual_max_shifts_sum) as f32 / auto_computed_residents.len() as f32) .ceil() as u8; for r in &config.residents { let max_shifts = match r.max_shifts { - Some(shifts) => shifts as u8, + Some(shifts) => shifts, None if r.reduced_load => max_shifts_ceiling - 1, None => max_shifts_ceiling, }; - self.max_workloads.insert(r.id.clone(), max_shifts); + self.max_workloads.insert(r.id, max_shifts); } } @@ -91,7 +90,7 @@ impl WorkloadBounds { let share = (workload_limit as f32 / total_slots as f32) * total_holiday_slots as f32; let holiday_limit = share.ceil() as u8; - self.max_holiday_shifts.insert(r.id.clone(), holiday_limit); + self.max_holiday_shifts.insert(r.id, holiday_limit); } } @@ -112,23 +111,20 @@ impl WorkloadBounds { .iter() .filter(|r| r.allowed_types.len() == 1) { - let shift_type = &res.allowed_types[0]; + let shift_type = res.allowed_types[0]; let total_limit = *self.max_workloads.get(&res.id).unwrap_or(&0); - local_limits.insert((res.id.clone(), shift_type.clone()), total_limit); - local_thresholds.insert( - (res.id.clone(), shift_type.clone()), - total_limit.saturating_sub(2), - ); + local_limits.insert((res.id, shift_type), total_limit); + local_thresholds.insert((res.id, shift_type), total_limit.saturating_sub(2)); - for other_type in &all_shift_types { + for other_type in all_shift_types { if other_type != shift_type { - local_limits.insert((res.id.clone(), other_type.clone()), 0); - local_thresholds.insert((res.id.clone(), other_type.clone()), 0); + local_limits.insert((res.id, other_type), 0); + local_thresholds.insert((res.id, other_type), 0); } } - if let Some(s) = supply_by_shift_type.get_mut(shift_type) { + if let Some(s) = supply_by_shift_type.get_mut(&shift_type) { *s = s.saturating_sub(total_limit) } } @@ -144,19 +140,16 @@ impl WorkloadBounds { let deduct_amount = (total_limit as f32 / 2.0) as u8; - for shift_type in &all_shift_types { - if res.allowed_types.contains(shift_type) { - local_limits.insert((res.id.clone(), shift_type.clone()), per_type); - local_thresholds.insert( - (res.id.clone(), shift_type.clone()), - per_type.saturating_sub(2), - ); - if let Some(s) = supply_by_shift_type.get_mut(shift_type) { + for shift_type in all_shift_types { + if res.allowed_types.contains(&shift_type) { + local_limits.insert((res.id, shift_type), per_type); + local_thresholds.insert((res.id, shift_type), per_type.saturating_sub(2)); + if let Some(s) = supply_by_shift_type.get_mut(&shift_type) { *s = s.saturating_sub(deduct_amount); } } else { - local_limits.insert((res.id.clone(), shift_type.clone()), 0); - local_thresholds.insert((res.id.clone(), shift_type.clone()), 0); + local_limits.insert((res.id, shift_type), 0); + local_thresholds.insert((res.id, shift_type), 0); } } } @@ -172,19 +165,16 @@ impl WorkloadBounds { let deduct_amount = (total_limit as f32 / 3.0) as u8; - for shift_type in &all_shift_types { - if res.allowed_types.contains(shift_type) { - local_limits.insert((res.id.clone(), shift_type.clone()), per_type); - local_thresholds.insert( - (res.id.clone(), shift_type.clone()), - per_type.saturating_sub(2), - ); - if let Some(s) = supply_by_shift_type.get_mut(shift_type) { + for shift_type in all_shift_types { + if res.allowed_types.contains(&shift_type) { + local_limits.insert((res.id, shift_type), per_type); + local_thresholds.insert((res.id, shift_type), per_type.saturating_sub(2)); + if let Some(s) = supply_by_shift_type.get_mut(&shift_type) { *s = s.saturating_sub(deduct_amount); } } else { - local_limits.insert((res.id.clone(), shift_type.clone()), 0); - local_thresholds.insert((res.id.clone(), shift_type.clone()), 0); + local_limits.insert((res.id, shift_type), 0); + local_thresholds.insert((res.id, shift_type), 0); } } } @@ -202,32 +192,29 @@ pub struct WorkloadTracker { } impl WorkloadTracker { - pub fn insert(&mut self, res_id: &ResidentId, config: &UserConfig, slot: Slot) { - *self.total_counts.entry(res_id.clone()).or_insert(0) += 1; + pub fn insert(&mut self, res_id: ResidentId, config: &UserConfig, slot: Slot) { + *self.total_counts.entry(res_id).or_insert(0) += 1; *self .type_counts - .entry((res_id.clone(), slot.shift_type())) + .entry((res_id, slot.shift_type())) .or_insert(0) += 1; if config.is_holiday_or_weekend_slot(slot.day.0) { - *self.holidays.entry(res_id.clone()).or_insert(0) += 1; + *self.holidays.entry(res_id).or_insert(0) += 1; } } - pub fn remove(&mut self, resident_id: &ResidentId, config: &UserConfig, slot: Slot) { - if let Some(count) = self.total_counts.get_mut(resident_id) { + pub fn remove(&mut self, resident_id: ResidentId, config: &UserConfig, slot: Slot) { + if let Some(count) = self.total_counts.get_mut(&resident_id) { *count = count.saturating_sub(1); } - if let Some(count) = self - .type_counts - .get_mut(&(resident_id.clone(), slot.shift_type())) - { + if let Some(count) = self.type_counts.get_mut(&(resident_id, slot.shift_type())) { *count = count.saturating_sub(1); } if config.is_holiday_or_weekend_slot(slot.day.0) { - if let Some(count) = self.holidays.get_mut(resident_id) { + if let Some(count) = self.holidays.get_mut(&resident_id) { *count = count.saturating_sub(1); } } @@ -250,14 +237,8 @@ impl WorkloadTracker { for r in &config.residents { for shift_type in SHIFT_TYPES { - let current_load = self - .type_counts - .get(&(r.id.clone(), shift_type.clone())) - .unwrap_or(&0); - if let Some(&min) = bounds - .min_by_shift_type - .get(&(r.id.clone(), shift_type.clone())) - { + let current_load = self.type_counts.get(&(r.id, shift_type)).unwrap_or(&0); + if let Some(&min) = bounds.min_by_shift_type.get(&(r.id, shift_type)) { if *current_load < min { return false; } @@ -308,21 +289,18 @@ impl WorkloadTracker { let shift_type = slot.shift_type(); let current_load = self .type_counts - .get(&(resident_id.clone(), shift_type.clone())) + .get(&(*resident_id, shift_type)) .unwrap_or(&0); - if let Some(&max) = bounds - .max_by_shift_type - .get(&(resident_id.clone(), shift_type.clone())) - { + if let Some(&max) = bounds.max_by_shift_type.get(&(*resident_id, shift_type)) { return *current_load > max; } false } - pub fn get_type_count(&self, res_id: &ResidentId, stype: ShiftType) -> u8 { - *self.type_counts.get(&(res_id.clone(), stype)).unwrap_or(&0) + pub fn get_type_count(&self, res_id: &ResidentId, shift_type: ShiftType) -> u8 { + *self.type_counts.get(&(*res_id, shift_type)).unwrap_or(&0) } } @@ -339,11 +317,11 @@ mod tests { #[fixture] fn config() -> UserConfig { UserConfig::default().with_residents(vec![ - Resident::new("1", "Stefanos").with_max_shifts(2), - Resident::new("2", "Iordanis").with_max_shifts(2), - Resident::new("3", "Maria").with_reduced_load(), - Resident::new("4", "Veatriki"), - Resident::new("5", "Takis"), + Resident::new(1, "Stefanos").with_max_shifts(2), + Resident::new(2, "Iordanis").with_max_shifts(2), + Resident::new(3, "Maria").with_reduced_load(), + Resident::new(4, "Veatriki"), + Resident::new(5, "Takis"), ]) } @@ -356,52 +334,52 @@ mod tests { fn test_max_workloads(config: UserConfig) { let bounds = WorkloadBounds::new_with_config(&config); - assert_eq!(bounds.max_workloads[&ResidentId("1".to_string())], 2); - assert_eq!(bounds.max_workloads[&ResidentId("2".to_string())], 2); - assert!(bounds.max_workloads[&ResidentId("3".to_string())] > 0); + assert_eq!(bounds.max_workloads[&ResidentId(1)], 2); + assert_eq!(bounds.max_workloads[&ResidentId(2)], 2); + assert!(bounds.max_workloads[&ResidentId(3)] > 0); } #[rstest] fn test_is_total_workload_exceeded(mut tracker: WorkloadTracker, config: UserConfig) { - let res_id = ResidentId("1".to_string()); + let res_id = ResidentId(1); let mut bounds = WorkloadBounds::default(); - bounds.max_workloads.insert(res_id.clone(), 1); + bounds.max_workloads.insert(res_id, 1); let slot_1 = Slot::new(Day(1), ShiftPosition::First); let slot_2 = Slot::new(Day(2), ShiftPosition::First); - tracker.insert(&res_id, &config, slot_1); + tracker.insert(res_id, &config, slot_1); assert!(!tracker.is_total_workload_exceeded(&bounds, &res_id,)); - tracker.insert(&res_id, &config, slot_2); + tracker.insert(res_id, &config, slot_2); assert!(tracker.is_total_workload_exceeded(&bounds, &res_id,)); } #[rstest] fn test_is_holiday_workload_exceeded(mut tracker: WorkloadTracker, config: UserConfig) { - let res_id = ResidentId("1".to_string()); + let res_id = ResidentId(1); let mut bounds = WorkloadBounds::default(); - bounds.max_holiday_shifts.insert(res_id.clone(), 1); + bounds.max_holiday_shifts.insert(res_id, 1); let sat = Slot::new(Day(7), ShiftPosition::First); let sun = Slot::new(Day(8), ShiftPosition::First); - tracker.insert(&res_id, &config, sat); + tracker.insert(res_id, &config, sat); assert!(!tracker.is_holiday_workload_exceeded(&bounds, &res_id)); - tracker.insert(&res_id, &config, sun); + tracker.insert(res_id, &config, sun); assert!(tracker.is_holiday_workload_exceeded(&bounds, &res_id)); } #[rstest] fn test_backtracking_accuracy(mut tracker: WorkloadTracker, config: UserConfig) { - let res_id = ResidentId("1".to_string()); + let res_id = ResidentId(1); let slot = Slot::new(Day(1), ShiftPosition::First); - tracker.insert(&res_id, &config, slot); + tracker.insert(res_id, &config, slot); assert_eq!(tracker.current_workload(&res_id), 1); - tracker.remove(&res_id, &config, slot); + tracker.remove(res_id, &config, slot); assert_eq!(tracker.current_workload(&res_id), 0); } } diff --git a/src-tauri/tests/integration.rs b/src-tauri/tests/integration.rs index aaf6877..edb687c 100644 --- a/src-tauri/tests/integration.rs +++ b/src-tauri/tests/integration.rs @@ -13,10 +13,10 @@ mod integration_tests { #[fixture] fn minimal_config() -> UserConfig { UserConfig::new(2).with_residents(vec![ - Resident::new("1", "R1"), - Resident::new("2", "R2"), - Resident::new("3", "R3"), - Resident::new("4", "R4"), + Resident::new(1, "R1"), + Resident::new(2, "R2"), + Resident::new(3, "R3"), + Resident::new(4, "R4"), ]) } @@ -25,41 +25,41 @@ mod integration_tests { UserConfig::new(2) .with_holidays(vec![2, 3, 10, 11, 12, 25]) .with_residents(vec![ - Resident::new("1", "R1").with_max_shifts(3), - Resident::new("2", "R2").with_max_shifts(4), - Resident::new("3", "R3").with_reduced_load(), - Resident::new("4", "R4").with_allowed_types(vec![ShiftType::Closed]), - Resident::new("5", "R5") + Resident::new(1, "R1").with_max_shifts(3), + Resident::new(2, "R2").with_max_shifts(4), + Resident::new(3, "R3").with_reduced_load(), + Resident::new(4, "R4").with_allowed_types(vec![ShiftType::Closed]), + Resident::new(5, "R5") .with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]), - Resident::new("6", "R6").with_negative_shifts(vec![Day(5), Day(15), Day(25)]), - Resident::new("7", "R7"), - Resident::new("8", "R8"), - Resident::new("9", "R9"), - Resident::new("10", "R10"), + Resident::new(6, "R6").with_negative_shifts(vec![Day(5), Day(15), Day(25)]), + Resident::new(7, "R7"), + Resident::new(8, "R8"), + Resident::new(9, "R9"), + Resident::new(10, "R10"), ]) .with_toxic_pairs(vec![ - ToxicPair::new("1", "2"), - ToxicPair::new("3", "4"), - ToxicPair::new("7", "8"), + ToxicPair::new(1, 2), + ToxicPair::new(3, 4), + ToxicPair::new(7, 8), ]) } #[fixture] fn manual_shifts_heavy_config() -> UserConfig { UserConfig::new(2).with_residents(vec![ - Resident::new("1", "R1").with_manual_shifts(vec![ + Resident::new(1, "R1").with_manual_shifts(vec![ Slot::new(Day(1), ShiftPosition::First), Slot::new(Day(3), ShiftPosition::First), Slot::new(Day(5), ShiftPosition::Second), ]), - Resident::new("2", "R2").with_manual_shifts(vec![ + Resident::new(2, "R2").with_manual_shifts(vec![ Slot::new(Day(2), ShiftPosition::First), Slot::new(Day(4), ShiftPosition::First), ]), - Resident::new("3", "R3"), - Resident::new("4", "R4"), - Resident::new("5", "R5"), - Resident::new("6", "R6"), + Resident::new(3, "R3"), + Resident::new(4, "R4"), + Resident::new(5, "R5"), + Resident::new(6, "R6"), ]) } @@ -68,27 +68,27 @@ mod integration_tests { UserConfig::new(2) .with_holidays(vec![5, 12, 19, 26]) .with_residents(vec![ - Resident::new("1", "R1") + Resident::new(1, "R1") .with_max_shifts(3) .with_negative_shifts(vec![Day(1), Day(2), Day(3)]), - Resident::new("2", "R2") + Resident::new(2, "R2") .with_max_shifts(3) .with_negative_shifts(vec![Day(4), Day(5), Day(6)]), - Resident::new("3", "R3") + Resident::new(3, "R3") .with_max_shifts(3) .with_negative_shifts(vec![Day(7), Day(8), Day(9)]), - Resident::new("4", "R4").with_allowed_types(vec![ShiftType::Closed]), - Resident::new("5", "R5") + Resident::new(4, "R4").with_allowed_types(vec![ShiftType::Closed]), + Resident::new(5, "R5") .with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]), - Resident::new("6", "R6"), - Resident::new("7", "R7"), - Resident::new("8", "R8"), + Resident::new(6, "R6"), + Resident::new(7, "R7"), + Resident::new(8, "R8"), ]) .with_toxic_pairs(vec![ - ToxicPair::new("1", "2"), - ToxicPair::new("2", "3"), - ToxicPair::new("5", "6"), - ToxicPair::new("6", "7"), + ToxicPair::new(1, 2), + ToxicPair::new(2, 3), + ToxicPair::new(5, 6), + ToxicPair::new(6, 7), ]) } @@ -160,7 +160,7 @@ mod integration_tests { let r2 = schedule.get_resident_id(&Slot::new(day, ShiftPosition::Second)); assert_ne!(r1, r2); if let (Some(id1), Some(id2)) = (r1, r2) { - let pair = ToxicPair::from((id1.clone(), id2.clone())); + let pair = ToxicPair::from((*id1, *id2)); assert!(config.toxic_pairs.iter().all(|t| !t.matches(&pair))); } } diff --git a/src/routes/state.svelte.ts b/src/routes/state.svelte.ts index 142a2af..a54155b 100644 --- a/src/routes/state.svelte.ts +++ b/src/routes/state.svelte.ts @@ -2,7 +2,7 @@ import { CalendarDate, getDayOfWeek } from "@internationalized/date"; export interface Resident { - id: string; + id: number; name: string; negativeShifts: CalendarDate[]; manualShifts: CalendarDate[]; @@ -12,12 +12,13 @@ export interface Resident { } export interface ForbiddenPair { - id1: string; - id2: string; + id1: number; + id2: number; } export class RotaState { currentStep = $state(1); + residentsCounter = $state(0); residents = $state([]); selectedMonth = $state(2); selectedYear = $state(2026); @@ -38,7 +39,7 @@ export class RotaState { addResident() { this.residents.push({ - id: crypto.randomUUID(), + id: ++this.residentsCounter, name: "", negativeShifts: [], manualShifts: [], @@ -48,11 +49,11 @@ export class RotaState { }); } - removeResident(id: string) { + removeResident(id: number) { this.residents = this.residents.filter((r) => r.id !== id); } - findResident(id: string) { + findResident(id: number) { return this.residents.find((r) => r.id === id); } @@ -102,11 +103,11 @@ export type UserConfigDTO = { year: number; holidays: Array; residents: Array; - toxic_pairs: Array<[string, string]>; + toxic_pairs: Array<[number, number]>; }; export type ResidentDTO = { - id: string; + id: number; name: string; negativeShifts: Array; manualShifts: Array<{ day: number; position: ShiftPosition }>;