work_tuimer/models/
day_data.rs

1use super::WorkRecord;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use time::Date;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct DayData {
8    pub date: Date,
9    pub last_id: u32,
10    pub work_records: HashMap<u32, WorkRecord>,
11}
12
13impl DayData {
14    pub fn new(date: Date) -> Self {
15        DayData {
16            date,
17            last_id: 0,
18            work_records: HashMap::new(),
19        }
20    }
21
22    pub fn add_record(&mut self, record: WorkRecord) {
23        if record.id > self.last_id {
24            self.last_id = record.id;
25        }
26        self.work_records.insert(record.id, record);
27    }
28
29    pub fn remove_record(&mut self, id: u32) -> Option<WorkRecord> {
30        self.work_records.remove(&id)
31    }
32
33    pub fn next_id(&mut self) -> u32 {
34        self.last_id += 1;
35        self.last_id
36    }
37
38    pub fn get_sorted_records(&self) -> Vec<&WorkRecord> {
39        let mut records: Vec<&WorkRecord> = self.work_records.values().collect();
40        records.sort_by_key(|r| r.start);
41        records
42    }
43
44    pub fn get_grouped_totals(&self) -> Vec<(String, u32)> {
45        let mut totals: HashMap<String, u32> = HashMap::new();
46
47        for record in self.work_records.values() {
48            *totals.entry(record.name.clone()).or_insert(0) += record.total_minutes;
49        }
50
51        let mut result: Vec<(String, u32)> = totals.into_iter().collect();
52        result.sort_by(|a, b| b.1.cmp(&a.1));
53        result
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::models::TimePoint;
61
62    fn create_test_date() -> Date {
63        Date::from_calendar_date(2025, time::Month::November, 6).unwrap()
64    }
65
66    fn create_test_record(id: u32, name: &str, start_hour: u8, end_hour: u8) -> WorkRecord {
67        let start = TimePoint::new(start_hour, 0).unwrap();
68        let end = TimePoint::new(end_hour, 0).unwrap();
69        WorkRecord::new(id, name.to_string(), start, end)
70    }
71
72    #[test]
73    fn test_new_day_data() {
74        let date = create_test_date();
75        let day = DayData::new(date);
76
77        assert_eq!(day.date, date);
78        assert_eq!(day.last_id, 0);
79        assert_eq!(day.work_records.len(), 0);
80    }
81
82    #[test]
83    fn test_add_record() {
84        let mut day = DayData::new(create_test_date());
85        let record = create_test_record(1, "Coding", 9, 17);
86
87        day.add_record(record.clone());
88
89        assert_eq!(day.work_records.len(), 1);
90        assert_eq!(day.last_id, 1);
91        assert!(day.work_records.contains_key(&1));
92    }
93
94    #[test]
95    fn test_add_multiple_records() {
96        let mut day = DayData::new(create_test_date());
97
98        day.add_record(create_test_record(1, "Coding", 9, 12));
99        day.add_record(create_test_record(2, "Meeting", 13, 14));
100        day.add_record(create_test_record(3, "Code Review", 14, 16));
101
102        assert_eq!(day.work_records.len(), 3);
103        assert_eq!(day.last_id, 3);
104    }
105
106    #[test]
107    fn test_add_record_updates_last_id() {
108        let mut day = DayData::new(create_test_date());
109
110        day.add_record(create_test_record(5, "Task", 9, 10));
111        assert_eq!(day.last_id, 5);
112
113        day.add_record(create_test_record(2, "Task2", 10, 11));
114        assert_eq!(day.last_id, 5); // Should not decrease
115
116        day.add_record(create_test_record(10, "Task3", 11, 12));
117        assert_eq!(day.last_id, 10);
118    }
119
120    #[test]
121    fn test_remove_record() {
122        let mut day = DayData::new(create_test_date());
123        day.add_record(create_test_record(1, "Coding", 9, 17));
124
125        let removed = day.remove_record(1);
126        assert!(removed.is_some());
127        assert_eq!(removed.unwrap().name, "Coding");
128        assert_eq!(day.work_records.len(), 0);
129    }
130
131    #[test]
132    fn test_remove_nonexistent_record() {
133        let mut day = DayData::new(create_test_date());
134        day.add_record(create_test_record(1, "Coding", 9, 17));
135
136        let removed = day.remove_record(999);
137        assert!(removed.is_none());
138        assert_eq!(day.work_records.len(), 1);
139    }
140
141    #[test]
142    fn test_next_id() {
143        let mut day = DayData::new(create_test_date());
144
145        assert_eq!(day.next_id(), 1);
146        assert_eq!(day.next_id(), 2);
147        assert_eq!(day.next_id(), 3);
148        assert_eq!(day.last_id, 3);
149    }
150
151    #[test]
152    fn test_next_id_after_add_record() {
153        let mut day = DayData::new(create_test_date());
154        day.add_record(create_test_record(5, "Task", 9, 10));
155
156        assert_eq!(day.last_id, 5);
157        assert_eq!(day.next_id(), 6);
158        assert_eq!(day.next_id(), 7);
159    }
160
161    #[test]
162    fn test_get_sorted_records_empty() {
163        let day = DayData::new(create_test_date());
164        let sorted = day.get_sorted_records();
165        assert_eq!(sorted.len(), 0);
166    }
167
168    #[test]
169    fn test_get_sorted_records_single() {
170        let mut day = DayData::new(create_test_date());
171        day.add_record(create_test_record(1, "Coding", 9, 17));
172
173        let sorted = day.get_sorted_records();
174        assert_eq!(sorted.len(), 1);
175        assert_eq!(sorted[0].name, "Coding");
176    }
177
178    #[test]
179    fn test_get_sorted_records_already_sorted() {
180        let mut day = DayData::new(create_test_date());
181        day.add_record(create_test_record(1, "Morning", 9, 12));
182        day.add_record(create_test_record(2, "Afternoon", 13, 17));
183
184        let sorted = day.get_sorted_records();
185        assert_eq!(sorted.len(), 2);
186        assert_eq!(sorted[0].name, "Morning");
187        assert_eq!(sorted[1].name, "Afternoon");
188    }
189
190    #[test]
191    fn test_get_sorted_records_unsorted() {
192        let mut day = DayData::new(create_test_date());
193        day.add_record(create_test_record(1, "Afternoon", 13, 17));
194        day.add_record(create_test_record(2, "Morning", 9, 12));
195        day.add_record(create_test_record(3, "Evening", 18, 20));
196
197        let sorted = day.get_sorted_records();
198        assert_eq!(sorted.len(), 3);
199        assert_eq!(sorted[0].name, "Morning");
200        assert_eq!(sorted[1].name, "Afternoon");
201        assert_eq!(sorted[2].name, "Evening");
202    }
203
204    #[test]
205    fn test_get_sorted_records_same_start_time() {
206        let mut day = DayData::new(create_test_date());
207        let start = TimePoint::new(9, 0).unwrap();
208        let end1 = TimePoint::new(10, 0).unwrap();
209        let end2 = TimePoint::new(11, 0).unwrap();
210
211        day.add_record(WorkRecord::new(1, "Task1".to_string(), start, end1));
212        day.add_record(WorkRecord::new(2, "Task2".to_string(), start, end2));
213
214        let sorted = day.get_sorted_records();
215        assert_eq!(sorted.len(), 2);
216        // Both start at 9:00, order doesn't matter but both should be present
217        assert!(sorted.iter().any(|r| r.name == "Task1"));
218        assert!(sorted.iter().any(|r| r.name == "Task2"));
219    }
220
221    #[test]
222    fn test_get_grouped_totals_empty() {
223        let day = DayData::new(create_test_date());
224        let totals = day.get_grouped_totals();
225        assert_eq!(totals.len(), 0);
226    }
227
228    #[test]
229    fn test_get_grouped_totals_single_task() {
230        let mut day = DayData::new(create_test_date());
231        day.add_record(create_test_record(1, "Coding", 9, 17)); // 8 hours
232
233        let totals = day.get_grouped_totals();
234        assert_eq!(totals.len(), 1);
235        assert_eq!(totals[0].0, "Coding");
236        assert_eq!(totals[0].1, 480); // 8 * 60 minutes
237    }
238
239    #[test]
240    fn test_get_grouped_totals_multiple_different_tasks() {
241        let mut day = DayData::new(create_test_date());
242        day.add_record(create_test_record(1, "Coding", 9, 12)); // 3 hours
243        day.add_record(create_test_record(2, "Meeting", 13, 14)); // 1 hour
244        day.add_record(create_test_record(3, "Code Review", 14, 16)); // 2 hours
245
246        let totals = day.get_grouped_totals();
247        assert_eq!(totals.len(), 3);
248
249        // Should be sorted by duration (descending)
250        assert_eq!(totals[0].0, "Coding");
251        assert_eq!(totals[0].1, 180);
252        assert_eq!(totals[1].0, "Code Review");
253        assert_eq!(totals[1].1, 120);
254        assert_eq!(totals[2].0, "Meeting");
255        assert_eq!(totals[2].1, 60);
256    }
257
258    #[test]
259    fn test_get_grouped_totals_same_task_multiple_times() {
260        let mut day = DayData::new(create_test_date());
261        day.add_record(create_test_record(1, "Coding", 9, 11)); // 2 hours
262        day.add_record(create_test_record(2, "Meeting", 11, 12)); // 1 hour
263        day.add_record(create_test_record(3, "Coding", 13, 16)); // 3 hours
264        day.add_record(create_test_record(4, "Coding", 16, 17)); // 1 hour
265
266        let totals = day.get_grouped_totals();
267        assert_eq!(totals.len(), 2);
268
269        // Coding should be grouped: 2 + 3 + 1 = 6 hours
270        assert_eq!(totals[0].0, "Coding");
271        assert_eq!(totals[0].1, 360);
272        assert_eq!(totals[1].0, "Meeting");
273        assert_eq!(totals[1].1, 60);
274    }
275
276    #[test]
277    fn test_get_grouped_totals_sorted_by_duration() {
278        let mut day = DayData::new(create_test_date());
279        day.add_record(create_test_record(1, "Short", 9, 10)); // 1 hour
280        day.add_record(create_test_record(2, "Long", 10, 15)); // 5 hours
281        day.add_record(create_test_record(3, "Medium", 15, 17)); // 2 hours
282
283        let totals = day.get_grouped_totals();
284
285        // Should be sorted by duration descending
286        assert_eq!(totals[0].0, "Long");
287        assert_eq!(totals[1].0, "Medium");
288        assert_eq!(totals[2].0, "Short");
289    }
290
291    #[test]
292    fn test_clone() {
293        let mut day1 = DayData::new(create_test_date());
294        day1.add_record(create_test_record(1, "Coding", 9, 17));
295
296        let day2 = day1.clone();
297
298        assert_eq!(day1.date, day2.date);
299        assert_eq!(day1.last_id, day2.last_id);
300        assert_eq!(day1.work_records.len(), day2.work_records.len());
301    }
302}