//! Tauri command modules for the GoingsOn desktop application. //! //! This module organizes all IPC commands into domain-specific submodules //! for better maintainability and separation of concerns. //! //! # Module Organization //! //! - [`project`] - Project CRUD operations //! - [`task`] - Task management, annotations, and subtasks //! - [`event`] - Calendar event operations //! - [`email`] - Email management and IMAP account sync //! - [`oauth`] - OAuth2 authentication for email providers //! - [`search`] - Full-text search across all entities //! - [`day_planning`] - Time blocking and scheduling //! - [`saved_views`] - Custom filter views //! - [`stats`] - Dashboard statistics //! - [`window`] - Window management commands mod app_info; pub(crate) mod attachment; mod contact; mod daily_note; mod day_planning; pub(crate) mod email; mod email_account; mod email_sync; pub mod error; mod event; mod export; mod import_external; mod milestone; mod monthly_review; mod oauth; mod plugin; mod preferences; mod project; mod saved_views; mod search; mod stats; mod sync; mod task; mod task_state; mod task_subtasks; mod time_tracking; mod themes; mod weekly_review; mod window; #[cfg(test)] mod tests; // Re-export error types for use in commands pub use error::ApiError; pub use error::{OptionNotFound, OptionApiError, ResultApiError}; // Re-export all commands for registration in main.rs pub use app_info::*; pub use attachment::*; pub use contact::*; pub use daily_note::*; pub use day_planning::*; pub use email::*; pub use email_account::*; pub use email_sync::*; pub use event::*; pub use export::*; pub use import_external::*; pub use milestone::*; pub use monthly_review::*; pub use oauth::*; pub use project::*; pub use saved_views::*; pub use search::*; pub use stats::*; pub use sync::*; pub use task::*; pub use task_state::*; pub use task_subtasks::*; pub use time_tracking::*; pub use themes::*; pub use weekly_review::*; pub use window::*; pub use plugin::*; pub use preferences::*; pub use preferences::load as load_preferences; // ============ Shared Types ============ use chrono::{DateTime, Utc}; use serde::Deserialize; /// Input for snoozing tasks or emails until a specific time. #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SnoozeInput { pub until: DateTime, } /// Input for marking items as waiting for response. #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WaitingInput { pub expected_response_date: Option>, } /// Input for linking an entity to a project. #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LinkProjectInput { pub project_id: Option, } // ============ Snooze Options ============ use chrono::{Datelike, Duration, Local, NaiveTime, TimeZone, Timelike, Weekday}; use serde::Serialize; use tracing::instrument; /// A single snooze option with timestamp and display label. /// /// Each option represents a pre-computed snooze time that the user can select, /// such as "Later Today" or "Next Week". The timestamp is in UTC for storage, /// while the formatted string shows local time for display. #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct SnoozeOption { /// Unique identifier for this option (e.g., "laterToday", "tomorrow"). pub key: String, /// Human-readable label for the UI (e.g., "Later Today", "Tomorrow"). pub label: String, /// The snooze timestamp in UTC, used for storage and sorting. pub time: DateTime, /// Formatted local time for display (e.g., "Sat, Feb 15, 10:00 AM"). pub formatted: String, } /// Response containing all available snooze options. /// /// Provides pre-computed snooze times based on the current local time, /// including smart defaults like "Later Today" (which is omitted if it's /// already past a reasonable hour) and "Next Week" (always the next Monday). #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct SnoozeOptionsResponse { /// List of available snooze options, ordered by time. pub options: Vec, /// Minimum allowed time for custom snooze (current time in UTC). pub min_custom: DateTime, } /// Get pre-computed snooze options for the UI. /// /// Returns smart snooze times, sorted chronologically: /// - Later Today: 3 hours from now, or 5pm if past 2pm (omitted if already past) /// - Tomorrow: 9am tomorrow /// - This Weekend: Saturday 10am (next Saturday if already Saturday) /// - Next Week: Monday 9am (at least 2 days away to avoid overlap with Tomorrow) #[tauri::command] #[instrument(skip_all)] pub fn get_snooze_options() -> SnoozeOptionsResponse { let now = Local::now(); let today = now.date_naive(); let mut options = Vec::new(); // Later today: 3 hours from now, or 5pm if past 2pm let later_today = if now.hour() >= 14 { // After 2pm, suggest 5pm let five_pm = NaiveTime::from_hms_opt(17, 0, 0).expect("17:00 is valid"); Local.from_local_datetime(&today.and_time(five_pm)).earliest() } else { // 3 hours from now Some(now + Duration::hours(3)) }; if let Some(lt) = later_today { if lt > now { let utc_time = lt.with_timezone(&Utc); options.push(SnoozeOption { key: "laterToday".to_string(), label: "Later Today".to_string(), time: utc_time, formatted: format_snooze_time(<), }); } } // Tomorrow 9am let tomorrow = today + Duration::days(1); if let Some(tomorrow_9am) = Local .from_local_datetime(&tomorrow.and_time(NaiveTime::from_hms_opt(9, 0, 0).expect("09:00 is valid"))) .earliest() { options.push(SnoozeOption { key: "tomorrow".to_string(), label: "Tomorrow".to_string(), time: tomorrow_9am.with_timezone(&Utc), formatted: format_snooze_time(&tomorrow_9am), }); } // This weekend (Saturday 10am) let days_until_saturday = (Weekday::Sat.num_days_from_monday() as i64 - now.weekday().num_days_from_monday() as i64 + 7) % 7; let days_until_saturday = if days_until_saturday == 0 { 7 } else { days_until_saturday }; let saturday = today + Duration::days(days_until_saturday); if let Some(weekend) = Local .from_local_datetime(&saturday.and_time(NaiveTime::from_hms_opt(10, 0, 0).expect("10:00 is valid"))) .earliest() { options.push(SnoozeOption { key: "weekend".to_string(), label: "This Weekend".to_string(), time: weekend.with_timezone(&Utc), formatted: format_snooze_time(&weekend), }); } // Next week (Monday 9am) - ensure it's at least 2 days away to avoid overlap with "Tomorrow" let days_until_monday = (Weekday::Mon.num_days_from_monday() as i64 - now.weekday().num_days_from_monday() as i64 + 7) % 7; // If next Monday is tomorrow (1 day) or today (0 days), use the following Monday let days_until_monday = if days_until_monday <= 1 { days_until_monday + 7 } else { days_until_monday }; let monday = today + Duration::days(days_until_monday); if let Some(next_week) = Local .from_local_datetime(&monday.and_time(NaiveTime::from_hms_opt(9, 0, 0).expect("09:00 is valid"))) .earliest() { options.push(SnoozeOption { key: "nextWeek".to_string(), label: "Next Week".to_string(), time: next_week.with_timezone(&Utc), formatted: format_snooze_time(&next_week), }); } // Sort options by time to ensure chronological order options.sort_by_key(|o| o.time); SnoozeOptionsResponse { options, min_custom: now.with_timezone(&Utc), } } /// Format a snooze time for display (e.g., "Sat, Feb 15, 10:00 AM"). fn format_snooze_time(dt: &DateTime) -> String where Tz::Offset: std::fmt::Display, { dt.format("%a, %b %-d, %-I:%M %p").to_string() }