From 1ee456264780a8f251cabadbbbfb382491d3f431 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 02:02:57 +0200 Subject: [PATCH] feat: add favorites table, CRUD commands, and Pinia store --- src-tauri/src/commands.rs | 58 +++++++++++++++++++++++++++++++++++++++ src-tauri/src/database.rs | 13 +++++++++ src-tauri/src/lib.rs | 4 +++ src/stores/favorites.ts | 57 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 src/stores/favorites.ts diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index d4bd9c9..b6b0915 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -677,3 +677,61 @@ pub fn get_project_budget_status(state: State, project_id: i64) -> Res "percent_amount": project_row.1.map(|b| if b > 0.0 { (amount_used / b) * 100.0 } else { 0.0 }) })) } + +// Favorite structs and commands +#[derive(Debug, Serialize, Deserialize)] +pub struct Favorite { + pub id: Option, + pub project_id: i64, + pub task_id: Option, + pub description: Option, + pub sort_order: i32, +} + +#[tauri::command] +pub fn get_favorites(state: State) -> Result, String> { + let conn = state.db.lock().map_err(|e| e.to_string())?; + let mut stmt = conn.prepare( + "SELECT id, project_id, task_id, description, sort_order FROM favorites ORDER BY sort_order" + ).map_err(|e| e.to_string())?; + let favs = stmt.query_map([], |row| { + Ok(Favorite { + id: Some(row.get(0)?), + project_id: row.get(1)?, + task_id: row.get(2)?, + description: row.get(3)?, + sort_order: row.get(4)?, + }) + }).map_err(|e| e.to_string())?; + favs.collect::, _>>().map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn create_favorite(state: State, fav: Favorite) -> Result { + let conn = state.db.lock().map_err(|e| e.to_string())?; + conn.execute( + "INSERT INTO favorites (project_id, task_id, description, sort_order) VALUES (?1, ?2, ?3, ?4)", + params![fav.project_id, fav.task_id, fav.description, fav.sort_order], + ).map_err(|e| e.to_string())?; + Ok(conn.last_insert_rowid()) +} + +#[tauri::command] +pub fn delete_favorite(state: State, id: i64) -> Result<(), String> { + let conn = state.db.lock().map_err(|e| e.to_string())?; + conn.execute("DELETE FROM favorites WHERE id = ?1", params![id]) + .map_err(|e| e.to_string())?; + Ok(()) +} + +#[tauri::command] +pub fn reorder_favorites(state: State, ids: Vec) -> Result<(), String> { + let conn = state.db.lock().map_err(|e| e.to_string())?; + for (i, id) in ids.iter().enumerate() { + conn.execute( + "UPDATE favorites SET sort_order = ?1 WHERE id = ?2", + params![i as i32, id], + ).map_err(|e| e.to_string())?; + } + Ok(()) +} diff --git a/src-tauri/src/database.rs b/src-tauri/src/database.rs index 3249972..33a5790 100644 --- a/src-tauri/src/database.rs +++ b/src-tauri/src/database.rs @@ -158,6 +158,19 @@ pub fn init_db(conn: &Connection) -> Result<(), rusqlite::Error> { [], )?; + conn.execute( + "CREATE TABLE IF NOT EXISTS favorites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER NOT NULL, + task_id INTEGER, + description TEXT, + sort_order INTEGER DEFAULT 0, + FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE SET NULL + )", + [], + )?; + 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 b2358a0..44c6f46 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -73,6 +73,10 @@ pub fn run() { commands::get_entry_tags, commands::set_entry_tags, commands::get_project_budget_status, + commands::get_favorites, + commands::create_favorite, + commands::delete_favorite, + commands::reorder_favorites, ]) .setup(|app| { #[cfg(desktop)] diff --git a/src/stores/favorites.ts b/src/stores/favorites.ts new file mode 100644 index 0000000..22fecee --- /dev/null +++ b/src/stores/favorites.ts @@ -0,0 +1,57 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { invoke } from '@tauri-apps/api/core' + +export interface Favorite { + id?: number + project_id: number + task_id?: number | null + description?: string | null + sort_order: number +} + +export const useFavoritesStore = defineStore('favorites', () => { + const favorites = ref([]) + + async function fetchFavorites() { + try { + favorites.value = await invoke('get_favorites') + } catch (error) { + console.error('Failed to fetch favorites:', error) + } + } + + async function createFavorite(fav: Favorite): Promise { + try { + const id = await invoke('create_favorite', { fav }) + favorites.value.push({ ...fav, id: Number(id) }) + return Number(id) + } catch (error) { + console.error('Failed to create favorite:', error) + return null + } + } + + async function deleteFavorite(id: number): Promise { + try { + await invoke('delete_favorite', { id }) + favorites.value = favorites.value.filter(f => f.id !== id) + return true + } catch (error) { + console.error('Failed to delete favorite:', error) + return false + } + } + + async function reorderFavorites(ids: number[]): Promise { + try { + await invoke('reorder_favorites', { ids }) + return true + } catch (error) { + console.error('Failed to reorder favorites:', error) + return false + } + } + + return { favorites, fetchFavorites, createFavorite, deleteFavorite, reorderFavorites } +})