Skip to main content

max / goingson

3.5 KB · 127 lines History Blame Raw
1 //! Time tracking commands: start/stop/discard timer, get active, list sessions, summary.
2
3 use chrono::{DateTime, Utc};
4 use serde::{Deserialize, Serialize};
5 use std::sync::Arc;
6 use tauri::State;
7 use tracing::instrument;
8
9 use goingson_core::{TaskId, TimeSession, TimeTrackingSummary};
10
11 use crate::state::{AppState, DESKTOP_USER_ID};
12 use super::ApiError;
13
14 // ============ Types ============
15
16 #[derive(Debug, Serialize)]
17 #[serde(rename_all = "camelCase")]
18 pub struct ActiveTimerResponse {
19 pub session: TimeSession,
20 pub task_id: TaskId,
21 pub task_description: String,
22 pub elapsed_minutes: i32,
23 }
24
25 #[derive(Debug, Deserialize)]
26 #[serde(rename_all = "camelCase")]
27 pub struct TimeSummaryInput {
28 pub start: DateTime<Utc>,
29 pub end: DateTime<Utc>,
30 }
31
32 #[derive(Debug, Deserialize)]
33 #[serde(rename_all = "camelCase")]
34 pub struct LogManualTimeInput {
35 pub task_id: TaskId,
36 pub minutes: i32,
37 pub date: DateTime<Utc>,
38 }
39
40 // ============ Commands ============
41
42 /// Starts a timer on a task.
43 ///
44 /// Fails if the user already has an active timer on any task.
45 #[tauri::command]
46 #[instrument(skip_all)]
47 pub async fn start_timer(
48 state: State<'_, Arc<AppState>>,
49 task_id: TaskId,
50 ) -> Result<TimeSession, ApiError> {
51 Ok(state.tasks.start_timer(task_id, DESKTOP_USER_ID).await?)
52 }
53
54 /// Stops the active timer on a task.
55 ///
56 /// Sets ended_at, calculates duration, and updates the task's actual_minutes cache.
57 #[tauri::command]
58 #[instrument(skip_all)]
59 pub async fn stop_timer(
60 state: State<'_, Arc<AppState>>,
61 task_id: TaskId,
62 ) -> Result<Option<TimeSession>, ApiError> {
63 Ok(state.tasks.stop_timer(task_id, DESKTOP_USER_ID).await?)
64 }
65
66 /// Discards the active timer without recording time.
67 #[tauri::command]
68 #[instrument(skip_all)]
69 pub async fn discard_timer(
70 state: State<'_, Arc<AppState>>,
71 task_id: TaskId,
72 ) -> Result<bool, ApiError> {
73 Ok(state.tasks.discard_timer(task_id, DESKTOP_USER_ID).await?)
74 }
75
76 /// Gets the currently active timer for the user (at most one).
77 ///
78 /// Returns the session with task description for display.
79 #[tauri::command]
80 #[instrument(skip_all)]
81 pub async fn get_active_timer(
82 state: State<'_, Arc<AppState>>,
83 ) -> Result<Option<ActiveTimerResponse>, ApiError> {
84 match state.tasks.get_active_timer(DESKTOP_USER_ID).await? {
85 Some((session, description)) => {
86 let elapsed_minutes = session.elapsed_minutes();
87 Ok(Some(ActiveTimerResponse {
88 task_id: session.task_id,
89 session,
90 task_description: description,
91 elapsed_minutes,
92 }))
93 }
94 None => Ok(None),
95 }
96 }
97
98 /// Lists all time sessions for a task.
99 #[tauri::command]
100 #[instrument(skip_all)]
101 pub async fn list_time_sessions(
102 state: State<'_, Arc<AppState>>,
103 task_id: TaskId,
104 ) -> Result<Vec<TimeSession>, ApiError> {
105 Ok(state.tasks.list_time_sessions(task_id, DESKTOP_USER_ID).await?)
106 }
107
108 /// Logs a manual time entry (retroactive, no live timer).
109 #[tauri::command]
110 #[instrument(skip_all)]
111 pub async fn log_manual_time(
112 state: State<'_, Arc<AppState>>,
113 input: LogManualTimeInput,
114 ) -> Result<TimeSession, ApiError> {
115 Ok(state.tasks.log_manual_time(input.task_id, DESKTOP_USER_ID, input.minutes, input.date).await?)
116 }
117
118 /// Gets time tracking summary grouped by project and date.
119 #[tauri::command]
120 #[instrument(skip_all)]
121 pub async fn get_time_summary(
122 state: State<'_, Arc<AppState>>,
123 input: TimeSummaryInput,
124 ) -> Result<Vec<TimeTrackingSummary>, ApiError> {
125 Ok(state.tasks.get_time_summary(DESKTOP_USER_ID, input.start, input.end).await?)
126 }
127