//! Time tracking commands: start/stop/discard timer, get active, list sessions, summary. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tauri::State; use tracing::instrument; use goingson_core::{TaskId, TimeSession, TimeTrackingSummary}; use crate::state::{AppState, DESKTOP_USER_ID}; use super::ApiError; // ============ Types ============ #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveTimerResponse { pub session: TimeSession, pub task_id: TaskId, pub task_description: String, pub elapsed_minutes: i32, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TimeSummaryInput { pub start: DateTime, pub end: DateTime, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LogManualTimeInput { pub task_id: TaskId, pub minutes: i32, pub date: DateTime, } // ============ Commands ============ /// Starts a timer on a task. /// /// Fails if the user already has an active timer on any task. #[tauri::command] #[instrument(skip_all)] pub async fn start_timer( state: State<'_, Arc>, task_id: TaskId, ) -> Result { Ok(state.tasks.start_timer(task_id, DESKTOP_USER_ID).await?) } /// Stops the active timer on a task. /// /// Sets ended_at, calculates duration, and updates the task's actual_minutes cache. #[tauri::command] #[instrument(skip_all)] pub async fn stop_timer( state: State<'_, Arc>, task_id: TaskId, ) -> Result, ApiError> { Ok(state.tasks.stop_timer(task_id, DESKTOP_USER_ID).await?) } /// Discards the active timer without recording time. #[tauri::command] #[instrument(skip_all)] pub async fn discard_timer( state: State<'_, Arc>, task_id: TaskId, ) -> Result { Ok(state.tasks.discard_timer(task_id, DESKTOP_USER_ID).await?) } /// Gets the currently active timer for the user (at most one). /// /// Returns the session with task description for display. #[tauri::command] #[instrument(skip_all)] pub async fn get_active_timer( state: State<'_, Arc>, ) -> Result, ApiError> { match state.tasks.get_active_timer(DESKTOP_USER_ID).await? { Some((session, description)) => { let elapsed_minutes = session.elapsed_minutes(); Ok(Some(ActiveTimerResponse { task_id: session.task_id, session, task_description: description, elapsed_minutes, })) } None => Ok(None), } } /// Lists all time sessions for a task. #[tauri::command] #[instrument(skip_all)] pub async fn list_time_sessions( state: State<'_, Arc>, task_id: TaskId, ) -> Result, ApiError> { Ok(state.tasks.list_time_sessions(task_id, DESKTOP_USER_ID).await?) } /// Logs a manual time entry (retroactive, no live timer). #[tauri::command] #[instrument(skip_all)] pub async fn log_manual_time( state: State<'_, Arc>, input: LogManualTimeInput, ) -> Result { Ok(state.tasks.log_manual_time(input.task_id, DESKTOP_USER_ID, input.minutes, input.date).await?) } /// Gets time tracking summary grouped by project and date. #[tauri::command] #[instrument(skip_all)] pub async fn get_time_summary( state: State<'_, Arc>, input: TimeSummaryInput, ) -> Result, ApiError> { Ok(state.tasks.get_time_summary(DESKTOP_USER_ID, input.start, input.end).await?) }