From a3ea37baa1eed525e4080e5cac6795e4f44eb640 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Feb 2026 15:15:50 +0200 Subject: [PATCH] feat: timesheet row persistence backend --- src-tauri/src/commands.rs | 67 +++++++++++++++++++++++++++++++++++++++ src-tauri/src/database.rs | 11 +++++++ src-tauri/src/lib.rs | 3 ++ 3 files changed, 81 insertions(+) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index dab89b7..3869bd3 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -2407,6 +2407,73 @@ pub fn delete_entry_template(state: State, id: i64) -> Result<(), Stri Ok(()) } +#[tauri::command] +pub fn get_timesheet_rows(state: State, week_start: String) -> Result, String> { + let conn = state.db.lock().map_err(|e| e.to_string())?; + let mut stmt = conn.prepare( + "SELECT id, week_start, project_id, task_id, sort_order FROM timesheet_rows WHERE week_start = ?1 ORDER BY sort_order" + ).map_err(|e| e.to_string())?; + let rows = stmt.query_map(params![week_start], |row| { + Ok(serde_json::json!({ + "id": row.get::<_, i64>(0)?, + "week_start": row.get::<_, String>(1)?, + "project_id": row.get::<_, i64>(2)?, + "task_id": row.get::<_, Option>(3)?, + "sort_order": row.get::<_, i64>(4)?, + })) + }).map_err(|e| e.to_string())?; + Ok(rows.filter_map(|r| r.ok()).collect()) +} + +#[tauri::command] +pub fn save_timesheet_rows(state: State, week_start: String, rows: Vec) -> Result<(), String> { + let conn = state.db.lock().map_err(|e| e.to_string())?; + conn.execute("BEGIN TRANSACTION", []).map_err(|e| e.to_string())?; + + if let Err(e) = conn.execute("DELETE FROM timesheet_rows WHERE week_start = ?1", params![week_start]) { + let _ = conn.execute("ROLLBACK", []); + return Err(e.to_string()); + } + + for (i, row) in rows.iter().enumerate() { + let project_id = row.get("project_id").and_then(|v| v.as_i64()).unwrap_or(0); + let task_id = row.get("task_id").and_then(|v| v.as_i64()); + if let Err(e) = conn.execute( + "INSERT INTO timesheet_rows (week_start, project_id, task_id, sort_order) VALUES (?1, ?2, ?3, ?4)", + params![week_start, project_id, task_id, i as i64], + ) { + let _ = conn.execute("ROLLBACK", []); + return Err(e.to_string()); + } + } + + conn.execute("COMMIT", []).map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +pub fn get_previous_week_structure(state: State, current_week_start: String) -> Result, String> { + let conn = state.db.lock().map_err(|e| e.to_string())?; + let current = chrono::NaiveDate::parse_from_str(¤t_week_start, "%Y-%m-%d") + .map_err(|e| e.to_string())?; + let prev = current - chrono::Duration::days(7); + let prev_str = prev.format("%Y-%m-%d").to_string(); + + let mut stmt = conn.prepare( + "SELECT id, week_start, project_id, task_id, sort_order FROM timesheet_rows WHERE week_start = ?1 ORDER BY sort_order" + ).map_err(|e| e.to_string())?; + let rows = stmt.query_map(params![prev_str], |row| { + Ok(serde_json::json!({ + "id": row.get::<_, i64>(0)?, + "week_start": row.get::<_, String>(1)?, + "project_id": row.get::<_, i64>(2)?, + "task_id": row.get::<_, Option>(3)?, + "sort_order": row.get::<_, i64>(4)?, + })) + }).map_err(|e| e.to_string())?; + Ok(rows.filter_map(|r| r.ok()).collect()) +} + fn format_seconds_as_time(secs: i64) -> String { let h = secs / 3600; let m = (secs % 3600) / 60; diff --git a/src-tauri/src/database.rs b/src-tauri/src/database.rs index 2a1451f..a9a3586 100644 --- a/src-tauri/src/database.rs +++ b/src-tauri/src/database.rs @@ -326,6 +326,17 @@ pub fn init_db(conn: &Connection) -> Result<(), rusqlite::Error> { [], )?; + conn.execute( + "CREATE TABLE IF NOT EXISTS timesheet_rows ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + week_start TEXT NOT NULL, + project_id INTEGER NOT NULL REFERENCES projects(id), + task_id INTEGER REFERENCES tasks(id), + sort_order INTEGER NOT NULL DEFAULT 0 + )", + [], + )?; + conn.execute( "CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c13e724..1286843 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -135,6 +135,9 @@ pub fn run() { commands::get_entry_templates, commands::create_entry_template, commands::delete_entry_template, + commands::get_timesheet_rows, + commands::save_timesheet_rows, + commands::get_previous_week_structure, ]) .setup(|app| { #[cfg(desktop)]