1use crate::{
6 cli::{OutputArgs, OutputFormat, TodoEditArgs},
7 config::Config,
8 event_formatter::EventFormatter,
9 short_id::{EventWithShortId, ShortIdMap, TodoWithShortId},
10 todo_formatter::TodoFormatter,
11};
12use aimcal_core::{
13 Aim, EventConditions, Pager, SortOrder, TodoConditions, TodoPatch, TodoSort, TodoStatus,
14};
15use chrono::{Duration, Local, Utc};
16use colored::Colorize;
17use std::{error::Error, path::PathBuf};
18
19pub async fn command_dashboard(config: Option<PathBuf>) -> Result<(), Box<dyn Error>> {
21 log::debug!("Parsing configuration...");
22 let config = Config::parse(config).await?;
23 let aim = Aim::new(&config.core).await?;
24 let map = ShortIdMap::load_or_new(&config)?;
25
26 log::debug!("Generating dashboard...");
27 let now = Local::now().naive_local();
28
29 println!("🗓️ {}", "Events".bold());
30 let conds = EventConditions { now };
31 let args = OutputArgs {
32 output_format: OutputFormat::Table,
33 };
34 list_events(&aim, &map, &conds, &args).await?;
35 println!();
36
37 println!("✅ {}", "Todos".bold());
38 let conds = TodoConditions {
39 now,
40 status: Some(TodoStatus::NeedsAction),
41 due: Some(Duration::days(2)),
42 };
43 let args = OutputArgs {
44 output_format: OutputFormat::Table,
45 };
46 list_todos(&aim, &map, &conds, &args).await?;
47
48 map.dump(&config)?;
49 Ok(())
50}
51
52pub async fn command_events(
54 config: Option<PathBuf>,
55 args: &OutputArgs,
56) -> Result<(), Box<dyn Error>> {
57 log::debug!("Parsing configuration...");
58 let config = Config::parse(config).await?;
59 let aim = Aim::new(&config.core).await?;
60 let map = ShortIdMap::load_or_new(&config)?;
61
62 log::debug!("Listing events...");
63 let now = Local::now().naive_local();
64 let conds = EventConditions { now };
65 list_events(&aim, &map, &conds, args).await?;
66
67 map.dump(&config)?;
68 Ok(())
69}
70
71pub async fn command_todos(
73 config: Option<PathBuf>,
74 args: &OutputArgs,
75) -> Result<(), Box<dyn Error>> {
76 log::debug!("Parsing configuration...");
77 let config = Config::parse(config).await?;
78 let aim = Aim::new(&config.core).await?;
79 let map = ShortIdMap::load_or_new(&config)?;
80
81 log::debug!("Listing todos...");
82 let now = Local::now().naive_local();
83 let conds = TodoConditions {
84 now,
85 status: Some(TodoStatus::NeedsAction),
86 due: Some(Duration::days(2)),
87 };
88 list_todos(&aim, &map, &conds, args).await?;
89
90 map.dump(&config)?;
91 Ok(())
92}
93
94pub async fn command_done(
96 config: Option<PathBuf>,
97 args: &TodoEditArgs,
98) -> Result<(), Box<dyn Error>> {
99 log::debug!("Marking todo as done...");
100 let patch = TodoPatch {
101 completed: Some(Some(Utc::now().into())),
102 status: Some(TodoStatus::Completed),
103 ..Default::default()
104 };
105 edit_todo(config, args, patch).await
106}
107
108pub async fn command_undo(
110 config: Option<PathBuf>,
111 args: &TodoEditArgs,
112) -> Result<(), Box<dyn Error>> {
113 log::debug!("Marking todo as undone...");
114 let patch = TodoPatch {
115 completed: Some(None),
116 status: Some(TodoStatus::NeedsAction),
117 ..Default::default()
118 };
119 edit_todo(config, args, patch).await
120}
121
122async fn list_events(
124 aim: &Aim,
125 map: &ShortIdMap,
126 conds: &EventConditions,
127 args: &OutputArgs,
128) -> Result<(), Box<dyn Error>> {
129 const MAX: i64 = 16;
130 let pager: Pager = (MAX, 0).into();
131 let events = aim.list_events(conds, &pager).await?;
132 if events.len() >= (MAX as usize) {
133 let total = aim.count_events(conds).await?;
134 if total > MAX {
135 println!("Displaying the {total}/{MAX} events");
136 }
137 }
138
139 let events: Vec<_> = events
140 .into_iter()
141 .map(|event| EventWithShortId::with(map, event))
142 .collect();
143
144 let formatter = EventFormatter::new(conds.now).with_output_format(args.output_format);
145 println!("{}", formatter.format(&events));
146 Ok(())
147}
148
149async fn list_todos(
151 aim: &Aim,
152 map: &ShortIdMap,
153 conds: &TodoConditions,
154 args: &OutputArgs,
155) -> Result<(), Box<dyn Error>> {
156 const MAX: i64 = 16;
157 let pager = (MAX, 0).into();
158 let sort = vec![
159 TodoSort::Priority {
160 order: SortOrder::Desc,
161 none_first: false, },
163 TodoSort::Due(SortOrder::Desc),
164 ];
165 let todos = aim.list_todos(conds, &sort, &pager).await?;
166 if todos.len() >= (MAX as usize) {
167 let total = aim.count_todos(conds).await?;
168 if total > MAX {
169 println!("Displaying the {total}/{MAX} todos");
170 }
171 }
172
173 let todos: Vec<_> = todos
174 .into_iter()
175 .map(|todo| TodoWithShortId::with(map, todo))
176 .collect();
177
178 let formatter = TodoFormatter::new(conds.now).with_output_format(args.output_format);
179 println!("{}", formatter.format(&todos));
180 Ok(())
181}
182
183async fn edit_todo(
184 config: Option<PathBuf>,
185 args: &TodoEditArgs,
186 mut patch: TodoPatch,
187) -> Result<(), Box<dyn Error>> {
188 let now = Local::now().naive_local();
189
190 log::debug!("Parsing configuration...");
191 let config = Config::parse(config).await?;
192 let aim = Aim::new(&config.core).await?;
193 let map = ShortIdMap::load_or_new(&config)?;
194
195 log::debug!("Edit todo ...");
196 patch.uid = args
197 .uid_or_short_id
198 .parse()
199 .ok()
200 .and_then(|a| map.find(a))
201 .unwrap_or_else(|| args.uid_or_short_id.to_string()); let todo = aim.upsert_todo(patch.clone()).await?;
203 let todo = TodoWithShortId::with(&map, todo);
204
205 let formatter = TodoFormatter::new(now).with_output_format(args.output_format);
206 println!("{}", formatter.format(&[todo]));
207 Ok(())
208}