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).then_with(|| a.0.cmp(&b.0)));
54 result
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::models::TimePoint;
62
63 fn create_test_date() -> Date {
64 Date::from_calendar_date(2025, time::Month::November, 6).unwrap()
65 }
66
67 fn create_test_record(id: u32, name: &str, start_hour: u8, end_hour: u8) -> WorkRecord {
68 let start = TimePoint::new(start_hour, 0).unwrap();
69 let end = TimePoint::new(end_hour, 0).unwrap();
70 WorkRecord::new(id, name.to_string(), start, end)
71 }
72
73 #[test]
74 fn test_new_day_data() {
75 let date = create_test_date();
76 let day = DayData::new(date);
77
78 assert_eq!(day.date, date);
79 assert_eq!(day.last_id, 0);
80 assert_eq!(day.work_records.len(), 0);
81 }
82
83 #[test]
84 fn test_add_record() {
85 let mut day = DayData::new(create_test_date());
86 let record = create_test_record(1, "Coding", 9, 17);
87
88 day.add_record(record.clone());
89
90 assert_eq!(day.work_records.len(), 1);
91 assert_eq!(day.last_id, 1);
92 assert!(day.work_records.contains_key(&1));
93 }
94
95 #[test]
96 fn test_add_multiple_records() {
97 let mut day = DayData::new(create_test_date());
98
99 day.add_record(create_test_record(1, "Coding", 9, 12));
100 day.add_record(create_test_record(2, "Meeting", 13, 14));
101 day.add_record(create_test_record(3, "Code Review", 14, 16));
102
103 assert_eq!(day.work_records.len(), 3);
104 assert_eq!(day.last_id, 3);
105 }
106
107 #[test]
108 fn test_add_record_updates_last_id() {
109 let mut day = DayData::new(create_test_date());
110
111 day.add_record(create_test_record(5, "Task", 9, 10));
112 assert_eq!(day.last_id, 5);
113
114 day.add_record(create_test_record(2, "Task2", 10, 11));
115 assert_eq!(day.last_id, 5); day.add_record(create_test_record(10, "Task3", 11, 12));
118 assert_eq!(day.last_id, 10);
119 }
120
121 #[test]
122 fn test_remove_record() {
123 let mut day = DayData::new(create_test_date());
124 day.add_record(create_test_record(1, "Coding", 9, 17));
125
126 let removed = day.remove_record(1);
127 assert!(removed.is_some());
128 assert_eq!(removed.unwrap().name, "Coding");
129 assert_eq!(day.work_records.len(), 0);
130 }
131
132 #[test]
133 fn test_remove_nonexistent_record() {
134 let mut day = DayData::new(create_test_date());
135 day.add_record(create_test_record(1, "Coding", 9, 17));
136
137 let removed = day.remove_record(999);
138 assert!(removed.is_none());
139 assert_eq!(day.work_records.len(), 1);
140 }
141
142 #[test]
143 fn test_next_id() {
144 let mut day = DayData::new(create_test_date());
145
146 assert_eq!(day.next_id(), 1);
147 assert_eq!(day.next_id(), 2);
148 assert_eq!(day.next_id(), 3);
149 assert_eq!(day.last_id, 3);
150 }
151
152 #[test]
153 fn test_next_id_after_add_record() {
154 let mut day = DayData::new(create_test_date());
155 day.add_record(create_test_record(5, "Task", 9, 10));
156
157 assert_eq!(day.last_id, 5);
158 assert_eq!(day.next_id(), 6);
159 assert_eq!(day.next_id(), 7);
160 }
161
162 #[test]
163 fn test_get_sorted_records_empty() {
164 let day = DayData::new(create_test_date());
165 let sorted = day.get_sorted_records();
166 assert_eq!(sorted.len(), 0);
167 }
168
169 #[test]
170 fn test_get_sorted_records_single() {
171 let mut day = DayData::new(create_test_date());
172 day.add_record(create_test_record(1, "Coding", 9, 17));
173
174 let sorted = day.get_sorted_records();
175 assert_eq!(sorted.len(), 1);
176 assert_eq!(sorted[0].name, "Coding");
177 }
178
179 #[test]
180 fn test_get_sorted_records_already_sorted() {
181 let mut day = DayData::new(create_test_date());
182 day.add_record(create_test_record(1, "Morning", 9, 12));
183 day.add_record(create_test_record(2, "Afternoon", 13, 17));
184
185 let sorted = day.get_sorted_records();
186 assert_eq!(sorted.len(), 2);
187 assert_eq!(sorted[0].name, "Morning");
188 assert_eq!(sorted[1].name, "Afternoon");
189 }
190
191 #[test]
192 fn test_get_sorted_records_unsorted() {
193 let mut day = DayData::new(create_test_date());
194 day.add_record(create_test_record(1, "Afternoon", 13, 17));
195 day.add_record(create_test_record(2, "Morning", 9, 12));
196 day.add_record(create_test_record(3, "Evening", 18, 20));
197
198 let sorted = day.get_sorted_records();
199 assert_eq!(sorted.len(), 3);
200 assert_eq!(sorted[0].name, "Morning");
201 assert_eq!(sorted[1].name, "Afternoon");
202 assert_eq!(sorted[2].name, "Evening");
203 }
204
205 #[test]
206 fn test_get_sorted_records_same_start_time() {
207 let mut day = DayData::new(create_test_date());
208 let start = TimePoint::new(9, 0).unwrap();
209 let end1 = TimePoint::new(10, 0).unwrap();
210 let end2 = TimePoint::new(11, 0).unwrap();
211
212 day.add_record(WorkRecord::new(1, "Task1".to_string(), start, end1));
213 day.add_record(WorkRecord::new(2, "Task2".to_string(), start, end2));
214
215 let sorted = day.get_sorted_records();
216 assert_eq!(sorted.len(), 2);
217 assert!(sorted.iter().any(|r| r.name == "Task1"));
219 assert!(sorted.iter().any(|r| r.name == "Task2"));
220 }
221
222 #[test]
223 fn test_get_grouped_totals_empty() {
224 let day = DayData::new(create_test_date());
225 let totals = day.get_grouped_totals();
226 assert_eq!(totals.len(), 0);
227 }
228
229 #[test]
230 fn test_get_grouped_totals_single_task() {
231 let mut day = DayData::new(create_test_date());
232 day.add_record(create_test_record(1, "Coding", 9, 17)); let totals = day.get_grouped_totals();
235 assert_eq!(totals.len(), 1);
236 assert_eq!(totals[0].0, "Coding");
237 assert_eq!(totals[0].1, 480); }
239
240 #[test]
241 fn test_get_grouped_totals_multiple_different_tasks() {
242 let mut day = DayData::new(create_test_date());
243 day.add_record(create_test_record(1, "Coding", 9, 12)); day.add_record(create_test_record(2, "Meeting", 13, 14)); day.add_record(create_test_record(3, "Code Review", 14, 16)); let totals = day.get_grouped_totals();
248 assert_eq!(totals.len(), 3);
249
250 assert_eq!(totals[0].0, "Coding");
252 assert_eq!(totals[0].1, 180);
253 assert_eq!(totals[1].0, "Code Review");
254 assert_eq!(totals[1].1, 120);
255 assert_eq!(totals[2].0, "Meeting");
256 assert_eq!(totals[2].1, 60);
257 }
258
259 #[test]
260 fn test_get_grouped_totals_same_task_multiple_times() {
261 let mut day = DayData::new(create_test_date());
262 day.add_record(create_test_record(1, "Coding", 9, 11)); day.add_record(create_test_record(2, "Meeting", 11, 12)); day.add_record(create_test_record(3, "Coding", 13, 16)); day.add_record(create_test_record(4, "Coding", 16, 17)); let totals = day.get_grouped_totals();
268 assert_eq!(totals.len(), 2);
269
270 assert_eq!(totals[0].0, "Coding");
272 assert_eq!(totals[0].1, 360);
273 assert_eq!(totals[1].0, "Meeting");
274 assert_eq!(totals[1].1, 60);
275 }
276
277 #[test]
278 fn test_get_grouped_totals_sorted_by_duration() {
279 let mut day = DayData::new(create_test_date());
280 day.add_record(create_test_record(1, "Short", 9, 10)); day.add_record(create_test_record(2, "Long", 10, 15)); day.add_record(create_test_record(3, "Medium", 15, 17)); let totals = day.get_grouped_totals();
285
286 assert_eq!(totals[0].0, "Long");
288 assert_eq!(totals[1].0, "Medium");
289 assert_eq!(totals[2].0, "Short");
290 }
291
292 #[test]
293 fn test_get_grouped_totals_stable_sort_on_tied_durations() {
294 let mut day = DayData::new(create_test_date());
295 day.add_record(create_test_record(1, "Zebra Task", 9, 11)); day.add_record(create_test_record(2, "Alpha Task", 11, 13)); day.add_record(create_test_record(3, "Beta Task", 13, 15)); let totals = day.get_grouped_totals();
301
302 assert_eq!(totals.len(), 3);
304 assert_eq!(totals[0].0, "Alpha Task");
305 assert_eq!(totals[0].1, 120);
306 assert_eq!(totals[1].0, "Beta Task");
307 assert_eq!(totals[1].1, 120);
308 assert_eq!(totals[2].0, "Zebra Task");
309 assert_eq!(totals[2].1, 120);
310
311 for _ in 0..10 {
313 let totals_repeat = day.get_grouped_totals();
314 assert_eq!(totals_repeat[0].0, "Alpha Task");
315 assert_eq!(totals_repeat[1].0, "Beta Task");
316 assert_eq!(totals_repeat[2].0, "Zebra Task");
317 }
318 }
319
320 #[test]
321 fn test_clone() {
322 let mut day1 = DayData::new(create_test_date());
323 day1.add_record(create_test_record(1, "Coding", 9, 17));
324
325 let day2 = day1.clone();
326
327 assert_eq!(day1.date, day2.date);
328 assert_eq!(day1.last_id, day2.last_id);
329 assert_eq!(day1.work_records.len(), day2.work_records.len());
330 }
331}