feat: add tags table, CRUD commands, and Pinia store

This commit is contained in:
Your Name
2026-02-18 02:01:04 +02:00
parent afa8bce2c9
commit 26f1b19dde
4 changed files with 280 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
use crate::AppState;
use crate::os_detection;
use rusqlite::params;
use serde::{Deserialize, Serialize};
use tauri::State;
@@ -151,6 +152,7 @@ pub fn update_project(state: State<AppState>, project: Project) -> Result<(), St
#[tauri::command]
pub fn delete_project(state: State<AppState>, id: i64) -> Result<(), String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute("DELETE FROM tracked_apps WHERE project_id = ?1", params![id]).map_err(|e| e.to_string())?;
conn.execute("DELETE FROM projects WHERE id = ?1", params![id]).map_err(|e| e.to_string())?;
Ok(())
}
@@ -478,7 +480,8 @@ pub fn export_data(state: State<AppState>) -> Result<serde_json::Value, String>
pub fn clear_all_data(state: State<AppState>) -> Result<(), String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute_batch(
"DELETE FROM invoice_items;
"DELETE FROM tracked_apps;
DELETE FROM invoice_items;
DELETE FROM invoices;
DELETE FROM time_entries;
DELETE FROM tasks;
@@ -487,3 +490,150 @@ pub fn clear_all_data(state: State<AppState>) -> Result<(), String> {
).map_err(|e| e.to_string())?;
Ok(())
}
// Tracked Apps struct
#[derive(Debug, Serialize, Deserialize)]
pub struct TrackedApp {
pub id: Option<i64>,
pub project_id: i64,
pub exe_name: String,
pub exe_path: Option<String>,
pub display_name: Option<String>,
}
// OS Detection commands
#[tauri::command]
pub fn get_idle_seconds() -> Result<u64, String> {
Ok(os_detection::get_system_idle_seconds())
}
#[tauri::command]
pub fn get_visible_windows() -> Result<Vec<os_detection::WindowInfo>, String> {
Ok(os_detection::enumerate_visible_windows())
}
#[tauri::command]
pub fn get_running_processes() -> Result<Vec<os_detection::WindowInfo>, String> {
Ok(os_detection::enumerate_running_processes())
}
// Tracked Apps CRUD commands
#[tauri::command]
pub fn get_tracked_apps(state: State<AppState>, project_id: i64) -> Result<Vec<TrackedApp>, String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
let mut stmt = conn.prepare(
"SELECT id, project_id, exe_name, exe_path, display_name FROM tracked_apps WHERE project_id = ?1 ORDER BY display_name"
).map_err(|e| e.to_string())?;
let apps = stmt.query_map(params![project_id], |row| {
Ok(TrackedApp {
id: Some(row.get(0)?),
project_id: row.get(1)?,
exe_name: row.get(2)?,
exe_path: row.get(3)?,
display_name: row.get(4)?,
})
}).map_err(|e| e.to_string())?;
apps.collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn add_tracked_app(state: State<AppState>, app: TrackedApp) -> Result<i64, String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute(
"INSERT INTO tracked_apps (project_id, exe_name, exe_path, display_name) VALUES (?1, ?2, ?3, ?4)",
params![app.project_id, app.exe_name, app.exe_path, app.display_name],
).map_err(|e| e.to_string())?;
Ok(conn.last_insert_rowid())
}
#[tauri::command]
pub fn remove_tracked_app(state: State<AppState>, id: i64) -> Result<(), String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute("DELETE FROM tracked_apps WHERE id = ?1", params![id]).map_err(|e| e.to_string())?;
Ok(())
}
// Tag structs and commands
#[derive(Debug, Serialize, Deserialize)]
pub struct Tag {
pub id: Option<i64>,
pub name: String,
pub color: String,
}
#[tauri::command]
pub fn get_tags(state: State<AppState>) -> Result<Vec<Tag>, String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
let mut stmt = conn.prepare("SELECT id, name, color FROM tags ORDER BY name")
.map_err(|e| e.to_string())?;
let tags = stmt.query_map([], |row| {
Ok(Tag {
id: Some(row.get(0)?),
name: row.get(1)?,
color: row.get(2)?,
})
}).map_err(|e| e.to_string())?;
tags.collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn create_tag(state: State<AppState>, tag: Tag) -> Result<i64, String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute(
"INSERT INTO tags (name, color) VALUES (?1, ?2)",
params![tag.name, tag.color],
).map_err(|e| e.to_string())?;
Ok(conn.last_insert_rowid())
}
#[tauri::command]
pub fn update_tag(state: State<AppState>, tag: Tag) -> Result<(), String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute(
"UPDATE tags SET name = ?1, color = ?2 WHERE id = ?3",
params![tag.name, tag.color, tag.id],
).map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
pub fn delete_tag(state: State<AppState>, id: i64) -> Result<(), String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute("DELETE FROM entry_tags WHERE tag_id = ?1", params![id])
.map_err(|e| e.to_string())?;
conn.execute("DELETE FROM tags WHERE id = ?1", params![id])
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
pub fn get_entry_tags(state: State<AppState>, entry_id: i64) -> Result<Vec<Tag>, String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
let mut stmt = conn.prepare(
"SELECT t.id, t.name, t.color FROM tags t
JOIN entry_tags et ON t.id = et.tag_id
WHERE et.entry_id = ?1 ORDER BY t.name"
).map_err(|e| e.to_string())?;
let tags = stmt.query_map(params![entry_id], |row| {
Ok(Tag {
id: Some(row.get(0)?),
name: row.get(1)?,
color: row.get(2)?,
})
}).map_err(|e| e.to_string())?;
tags.collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())
}
#[tauri::command]
pub fn set_entry_tags(state: State<AppState>, entry_id: i64, tag_ids: Vec<i64>) -> Result<(), String> {
let conn = state.db.lock().map_err(|e| e.to_string())?;
conn.execute("DELETE FROM entry_tags WHERE entry_id = ?1", params![entry_id])
.map_err(|e| e.to_string())?;
for tag_id in tag_ids {
conn.execute(
"INSERT INTO entry_tags (entry_id, tag_id) VALUES (?1, ?2)",
params![entry_id, tag_id],
).map_err(|e| e.to_string())?;
}
Ok(())
}