1use super::*;
2use crate::model::Priority;
3use crossterm::event::{KeyCode, KeyEvent};
4
5impl App {
6 pub fn close_popup(&mut self) {
7 self.popup = None;
8 self.input_mode = InputMode::Normal;
9 self.input_buffer.clear();
10 self.input_buffer2.clear();
11 self.input_due_date.clear();
12 self.input_tags.clear();
13 }
14
15 pub fn submit_popup(&mut self) {
16 match self.popup.clone() {
17 Some(Popup::AddTask) => {
18 let title = self.input_buffer.trim().to_string();
19 if title.is_empty() {
20 self.set_status("Title cannot be empty.", true);
21 return;
22 }
23 let due_date = match self.popup_due_date() {
24 Ok(d) => d,
25 Err(msg) => {
26 self.set_status(msg, true);
27 return;
28 }
29 };
30 let tags = self.popup_tags();
31 if let Err(e) = storage::add_task_full(
32 &self.db,
33 &mut self.data,
34 storage::TaskPayload {
35 title,
36 notes: self.input_buffer2.trim().to_string(),
37 estimated_minutes: self.input_number,
38 priority: self.input_priority,
39 tags,
40 due_date,
41 },
42 ) {
43 self.set_status(format!("Save error: {e}"), true);
44 return;
45 }
46 self.bump_data();
47 let indices = self.filtered_task_indices();
48 let sel = indices.len().saturating_sub(1);
49 self.task_state
50 .select(if indices.is_empty() { None } else { Some(sel) });
51 self.close_popup();
52 self.set_status("Task added.", false);
53 }
54 Some(Popup::EditTask(id)) => {
55 let title = self.input_buffer.trim().to_string();
56 if title.is_empty() {
57 self.set_status("Title cannot be empty.", true);
58 return;
59 }
60 let due_date = match self.popup_due_date() {
61 Ok(d) => d,
62 Err(msg) => {
63 self.set_status(msg, true);
64 return;
65 }
66 };
67 let tags = self.popup_tags();
68 let notes = self.input_buffer2.trim().to_string();
69 let estimate = self.input_number.clamp(1, 480);
70 let priority = self.input_priority;
71 if let Err(e) = storage::update_task(
72 &self.db,
73 &mut self.data,
74 id,
75 storage::TaskPayload {
76 title,
77 notes,
78 estimated_minutes: estimate,
79 priority,
80 tags,
81 due_date,
82 },
83 ) {
84 self.set_status(format!("Save error: {e}"), true);
85 return;
86 }
87 self.bump_data();
88 self.close_popup();
89 self.set_status("Task updated.", false);
90 }
91 Some(Popup::ConfirmDelete(id)) => {
92 match storage::delete_task(&self.db, &mut self.data, id) {
93 Ok(true) => {
94 if self.active_task == Some(id) {
95 self.set_active_task(None);
96 }
97 self.bump_data();
98 self.clamp_task_selection_after_mutation();
99 self.set_status("Task deleted.", false);
100 self.check_queue_empty();
101 }
102 Ok(false) => {}
103 Err(e) => self.set_status(format!("Delete error: {e}"), true),
104 }
105 self.close_popup();
106 }
107 Some(Popup::EmptyQueueChoice) => {}
108 None => {}
109 }
110 }
111
112 pub fn confirm_delete(&mut self) {
113 if let Some(Popup::ConfirmDelete(id)) = self.popup.clone() {
114 match storage::delete_task(&self.db, &mut self.data, id) {
115 Ok(true) => {
116 if self.active_task == Some(id) {
117 self.set_active_task(None);
118 }
119 self.bump_data();
120 self.clamp_task_selection_after_mutation();
121 self.set_status("Task deleted.", false);
122 self.check_queue_empty();
123 }
124 Ok(false) => {}
125 Err(e) => self.set_status(format!("Delete error: {e}"), true),
126 }
127 self.close_popup();
128 }
129 }
130
131 pub(crate) fn handle_popup_key(&mut self, key: KeyEvent) {
132 if matches!(self.popup, Some(Popup::EmptyQueueChoice)) {
133 match key.code {
134 KeyCode::Esc => self.close_popup(),
135 KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('Y') => {
136 self.data.empty_queue_behavior = EmptyQueueBehavior::FreeFocus;
137 self.close_popup();
138 self.set_status(
139 "All tasks done — free focus. Sessions log as general focus.",
140 false,
141 );
142 }
143 KeyCode::Char('p') | KeyCode::Char('P') => {
144 self.data.empty_queue_behavior = EmptyQueueBehavior::PauseTimer;
145 self.close_popup();
146 if self.timer.state == TimerState::Running {
147 self.pause_timer();
148 } else {
149 self.timer.reset();
150 }
151 self.set_status("All tasks done — timer paused.", false);
152 }
153 KeyCode::Char('a') | KeyCode::Char('A') => {
154 self.close_popup();
155 self.open_add_task();
156 }
157 _ => {}
158 }
159 return;
160 }
161 if matches!(self.popup, Some(Popup::ConfirmDelete(_))) {
162 match key.code {
163 KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => {
164 self.close_popup();
165 }
166 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
167 self.confirm_delete();
168 }
169 _ => {}
170 }
171 return;
172 }
173 let is_text_field = matches!(
174 self.input_field,
175 InputField::Title | InputField::Notes | InputField::DueDate | InputField::Tags
176 );
177 match key.code {
178 KeyCode::Esc => {
179 self.close_popup();
180 }
181 KeyCode::Tab | KeyCode::BackTab => {
182 let order = [
183 InputField::Title,
184 InputField::Notes,
185 InputField::Estimate,
186 InputField::Priority,
187 InputField::DueDate,
188 InputField::Tags,
189 ];
190 let idx = order
191 .iter()
192 .position(|f| *f == self.input_field)
193 .unwrap_or(0);
194 let next = if key.code == KeyCode::Tab {
195 (idx + 1) % order.len()
196 } else {
197 (idx + order.len() - 1) % order.len()
198 };
199 self.input_field = order[next];
200 }
201 KeyCode::Enter => {
202 self.submit_popup();
203 }
204 _ => {
205 if is_text_field {
206 self.handle_text_input(key);
207 } else {
208 self.handle_field_input(key);
209 }
210 }
211 }
212 }
213
214 pub(crate) fn handle_text_input(&mut self, key: KeyEvent) {
215 if self.input_field == InputField::DueDate {
217 match key.code {
218 KeyCode::Left => {
219 self.calendar_date -= chrono::Duration::days(1);
220 self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
221 return;
222 }
223 KeyCode::Right => {
224 self.calendar_date += chrono::Duration::days(1);
225 self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
226 return;
227 }
228 KeyCode::Up => {
229 self.calendar_date -= chrono::Duration::days(7);
230 self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
231 return;
232 }
233 KeyCode::Down => {
234 self.calendar_date += chrono::Duration::days(7);
235 self.input_due_date = self.calendar_date.format("%Y-%m-%d").to_string();
236 return;
237 }
238 _ => {}
239 }
240 }
241 let buf = match self.input_field {
242 InputField::Title => &mut self.input_buffer,
243 InputField::Notes => &mut self.input_buffer2,
244 InputField::DueDate => &mut self.input_due_date,
245 InputField::Tags => &mut self.input_tags,
246 _ => return,
247 };
248 match key.code {
249 KeyCode::Backspace => {
250 buf.pop();
251 }
252 KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
253 buf.push(c);
254 }
255 _ => {}
256 }
257 }
258
259 pub(crate) fn handle_field_input(&mut self, key: KeyEvent) {
260 match self.input_field {
261 InputField::Estimate => match key.code {
262 KeyCode::Char(c) if c.is_ascii_digit() => {
263 let d = c.to_digit(10).unwrap_or(0);
264 self.input_number = (self.input_number.saturating_mul(10) + d).min(480);
265 }
266 KeyCode::Backspace => {
267 self.input_number /= 10;
268 if self.input_number == 0 {
269 self.input_number = 1;
270 }
271 }
272 KeyCode::Up => self.input_number = (self.input_number + 5).min(480),
273 KeyCode::Down => self.input_number = self.input_number.saturating_sub(5).max(1),
274 _ => {}
275 },
276 InputField::Priority => {
277 let next = match key.code {
278 KeyCode::Right | KeyCode::Up | KeyCode::Char(' ') => {
279 match self.input_priority {
280 Priority::Low => Priority::Medium,
281 Priority::Medium => Priority::High,
282 Priority::High => Priority::Low,
283 }
284 }
285 KeyCode::Left | KeyCode::Down => match self.input_priority {
286 Priority::Low => Priority::High,
287 Priority::High => Priority::Medium,
288 Priority::Medium => Priority::Low,
289 },
290 KeyCode::Char('1') => Priority::Low,
291 KeyCode::Char('2') => Priority::Medium,
292 KeyCode::Char('3') => Priority::High,
293 _ => self.input_priority,
294 };
295 self.input_priority = next;
296 }
297 _ => {}
298 }
299 }
300}