1use crate::fs::{display_name, is_checklist_item, VirtualFs};
6use crate::types::{FileEntry, FsError, DIR_ARCHIVE};
7
8#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
10pub struct CompletedItem {
11 pub display_name: String,
13 pub is_checklist: bool,
15}
16
17#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
19pub struct TodayReport {
20 pub completed_items: Vec<CompletedItem>,
22 pub total_done: usize,
24}
25
26pub fn done_today(fs: &VirtualFs) -> Result<Vec<FileEntry>, FsError> {
28 let all = fs.files_and_dirs(DIR_ARCHIVE)?;
29 let midnight = beginning_of_day_utc();
30 Ok(all
31 .into_iter()
32 .filter(|f| !f.is_dir && f.ctime > midnight)
33 .collect())
34}
35
36pub fn today_report(fs: &VirtualFs) -> Result<TodayReport, FsError> {
38 let today_files = done_today(fs)?;
39 let all_archived = fs.files_and_dirs(DIR_ARCHIVE)?;
40 let total_done = all_archived.iter().filter(|f| !f.is_dir).count();
41
42 let completed_items: Vec<CompletedItem> = today_files
43 .iter()
44 .map(|f| {
45 let is_checklist = is_checklist_item(&f.name);
46 CompletedItem {
47 display_name: display_name(&f.name),
48 is_checklist,
49 }
50 })
51 .collect();
52
53 Ok(TodayReport {
54 completed_items,
55 total_done,
56 })
57}
58
59pub fn format_today_report(report: &TodayReport) -> String {
61 let mut lines: Vec<String> = Vec::new();
62 for item in &report.completed_items {
63 let emoji = if item.is_checklist { "āļø" } else { "ā
" };
64 lines.push(format!("{} <b>{}</b>", emoji, item.display_name));
65 }
66 lines.push(format!("\nš {} tasks done in total", report.total_done));
67 lines.join("\n")
68}
69
70fn beginning_of_day_utc() -> i64 {
72 use chrono::Utc;
73 let now = Utc::now();
74 let midnight = now.date_naive().and_hms_opt(0, 0, 0).unwrap();
75 let dt = midnight.and_utc();
76 dt.timestamp_millis()
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use tempfile::TempDir;
83
84 fn setup() -> (VirtualFs, TempDir) {
85 let dir = TempDir::new().unwrap();
86 let fs = VirtualFs::new(dir.path().to_path_buf()).unwrap();
87 (fs, dir)
88 }
89
90 #[test]
91 fn test_done_today_empty() {
92 let (fs, _t) = setup();
93 let result = done_today(&fs).unwrap();
94 assert!(result.is_empty());
95 }
96
97 #[test]
98 fn test_today_report_empty() {
99 let (fs, _t) = setup();
100 let report = today_report(&fs).unwrap();
101 assert!(report.completed_items.is_empty());
102 assert_eq!(report.total_done, 0);
103 }
104
105 #[test]
106 fn test_today_report_with_files() {
107 let (fs, _t) = setup();
108 fs.write(DIR_ARCHIVE, "MyTask.md", "content").unwrap();
109 let report = today_report(&fs).unwrap();
110 assert_eq!(report.completed_items.len(), 1);
111 assert_eq!(report.total_done, 1);
112 assert_eq!(report.completed_items[0].display_name, "MyTask");
113 assert!(!report.completed_items[0].is_checklist);
114 }
115
116 #[test]
117 fn test_format_today_report() {
118 let report = TodayReport {
119 completed_items: vec![CompletedItem {
120 display_name: "Rust".into(),
121 is_checklist: false,
122 }],
123 total_done: 5,
124 };
125 let formatted = format_today_report(&report);
126 assert!(formatted.contains("ā
"));
127 assert!(formatted.contains("<b>Rust</b>"));
128 assert!(formatted.contains("š 5 tasks done in total"));
129 }
130}