extender Turso db: tablas de usuarios, celulas, project_members, ausencias, daily_logs, performance
This commit is contained in:
@@ -26,6 +26,90 @@ pub struct WorkItem {
|
||||
pub priority: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AlphaUser {
|
||||
pub id: i64,
|
||||
pub email: String,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub role: Option<String>,
|
||||
pub seniority: Option<String>,
|
||||
pub cell_id: Option<i64>,
|
||||
pub is_active: bool,
|
||||
pub created_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Cell {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub created_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CellMember {
|
||||
pub id: i64,
|
||||
pub cell_id: i64,
|
||||
pub user_id: i64,
|
||||
pub joined_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ProjectMember {
|
||||
pub id: i64,
|
||||
pub user_id: i64,
|
||||
pub initiative_id: i64,
|
||||
pub initiative_name: Option<String>,
|
||||
pub role: Option<String>,
|
||||
pub assigned_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Absence {
|
||||
pub id: i64,
|
||||
pub user_id: i64,
|
||||
pub start_date: String,
|
||||
pub end_date: String,
|
||||
#[serde(rename = "type")]
|
||||
pub absence_type: String,
|
||||
pub reason: Option<String>,
|
||||
pub created_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct DailyLog {
|
||||
pub id: i64,
|
||||
pub user_id: i64,
|
||||
pub initiative_id: i64,
|
||||
pub date: String,
|
||||
pub work_item_id: Option<i64>,
|
||||
pub what_worked_on: Option<String>,
|
||||
pub progress_pct: Option<f64>,
|
||||
pub impediments: Option<String>,
|
||||
pub plan_for_tomorrow: Option<String>,
|
||||
pub hours_spent: Option<f64>,
|
||||
pub created_at: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PerformanceSnapshot {
|
||||
pub id: i64,
|
||||
pub user_id: i64,
|
||||
pub initiative_id: Option<i64>,
|
||||
pub period: String,
|
||||
pub planned_sp: Option<f64>,
|
||||
pub completed_sp: Option<f64>,
|
||||
pub planned_hours: Option<f64>,
|
||||
pub actual_hours: Option<f64>,
|
||||
pub velocity: Option<f64>,
|
||||
pub spi: Option<f64>,
|
||||
pub cpi: Option<f64>,
|
||||
pub compliance_pct: Option<f64>,
|
||||
pub impediment_count: Option<i64>,
|
||||
pub calculated_at: Option<String>,
|
||||
}
|
||||
|
||||
async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
||||
let db = libsql::Builder::new_local(db_path)
|
||||
.build()
|
||||
@@ -57,6 +141,89 @@ async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
||||
priority TEXT DEFAULT 'medium',
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alpha_users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
email TEXT NOT NULL,
|
||||
first_name TEXT DEFAULT '',
|
||||
last_name TEXT DEFAULT '',
|
||||
role TEXT,
|
||||
seniority TEXT,
|
||||
cell_id INTEGER,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cells (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cell_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
cell_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
joined_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (cell_id) REFERENCES cells(id),
|
||||
FOREIGN KEY (user_id) REFERENCES alpha_users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
initiative_id INTEGER NOT NULL,
|
||||
initiative_name TEXT,
|
||||
role TEXT,
|
||||
assigned_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (user_id) REFERENCES alpha_users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS absences (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
start_date TEXT NOT NULL,
|
||||
end_date TEXT NOT NULL,
|
||||
type TEXT DEFAULT 'vacation',
|
||||
reason TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (user_id) REFERENCES alpha_users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS daily_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
initiative_id INTEGER NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
work_item_id INTEGER,
|
||||
what_worked_on TEXT,
|
||||
progress_pct REAL,
|
||||
impediments TEXT,
|
||||
plan_for_tomorrow TEXT,
|
||||
hours_spent REAL,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (user_id) REFERENCES alpha_users(id),
|
||||
FOREIGN KEY (work_item_id) REFERENCES work_items(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS performance_snapshots (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
initiative_id INTEGER,
|
||||
period TEXT NOT NULL DEFAULT 'weekly',
|
||||
planned_sp REAL,
|
||||
completed_sp REAL,
|
||||
planned_hours REAL,
|
||||
actual_hours REAL,
|
||||
velocity REAL,
|
||||
spi REAL,
|
||||
cpi REAL,
|
||||
compliance_pct REAL,
|
||||
impediment_count INTEGER,
|
||||
calculated_at TEXT DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (user_id) REFERENCES alpha_users(id)
|
||||
);"
|
||||
)
|
||||
.await
|
||||
@@ -68,6 +235,10 @@ async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
||||
pub mod commands {
|
||||
use super::*;
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Projects
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_projects(state: State<'_, Mutex<String>>) -> Result<Vec<Project>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
@@ -133,6 +304,10 @@ pub mod commands {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Work Items
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_work_items(state: State<'_, Mutex<String>>, project_id: i64) -> Result<Vec<WorkItem>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
@@ -198,4 +373,374 @@ pub mod commands {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Users (Alpha)
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_users(state: State<'_, Mutex<String>>) -> Result<Vec<AlphaUser>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
let mut rows = conn
|
||||
.query("SELECT id, email, first_name, last_name, role, seniority, cell_id, is_active, created_at FROM alpha_users ORDER BY first_name", ())
|
||||
.await
|
||||
.map_err(|e| format!("Query: {e}"))?;
|
||||
|
||||
let mut users = Vec::new();
|
||||
while let Some(row) = rows.next().await.map_err(|e| format!("Row: {e}"))? {
|
||||
users.push(AlphaUser {
|
||||
id: row.get::<i64>(0).unwrap_or(0),
|
||||
email: row.get::<String>(1).unwrap_or_default(),
|
||||
first_name: row.get::<String>(2).unwrap_or_default(),
|
||||
last_name: row.get::<String>(3).unwrap_or_default(),
|
||||
role: row.get::<String>(4).ok(),
|
||||
seniority: row.get::<String>(5).ok(),
|
||||
cell_id: row.get::<i64>(6).ok(),
|
||||
is_active: row.get::<i64>(7).unwrap_or(1) != 0,
|
||||
created_at: row.get::<String>(8).ok(),
|
||||
});
|
||||
}
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_user(state: State<'_, Mutex<String>>, user: AlphaUser) -> Result<i64, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO alpha_users (id, email, first_name, last_name, role, seniority, cell_id, is_active)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||
libsql::params![
|
||||
user.id,
|
||||
user.email,
|
||||
user.first_name,
|
||||
user.last_name,
|
||||
user.role,
|
||||
user.seniority,
|
||||
user.cell_id,
|
||||
user.is_active as i64,
|
||||
],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Insert: {e}"))?;
|
||||
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_user_fields(
|
||||
state: State<'_, Mutex<String>>,
|
||||
id: i64,
|
||||
role: Option<String>,
|
||||
seniority: Option<String>,
|
||||
cell_id: Option<i64>,
|
||||
) -> Result<(), String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
conn.execute(
|
||||
"UPDATE alpha_users SET role = COALESCE(?1, role), seniority = COALESCE(?2, seniority), cell_id = COALESCE(?3, cell_id) WHERE id = ?4",
|
||||
libsql::params![role, seniority, cell_id, id],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Update: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Cells
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_cells(state: State<'_, Mutex<String>>) -> Result<Vec<Cell>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
let mut rows = conn
|
||||
.query("SELECT id, name, description, created_at FROM cells ORDER BY name", ())
|
||||
.await
|
||||
.map_err(|e| format!("Query: {e}"))?;
|
||||
|
||||
let mut cells = Vec::new();
|
||||
while let Some(row) = rows.next().await.map_err(|e| format!("Row: {e}"))? {
|
||||
cells.push(Cell {
|
||||
id: row.get::<i64>(0).unwrap_or(0),
|
||||
name: row.get::<String>(1).unwrap_or_default(),
|
||||
description: row.get::<String>(2).ok(),
|
||||
created_at: row.get::<String>(3).ok(),
|
||||
});
|
||||
}
|
||||
Ok(cells)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_cell(state: State<'_, Mutex<String>>, cell: Cell) -> Result<i64, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
if cell.id != 0 {
|
||||
conn.execute(
|
||||
"UPDATE cells SET name = ?1, description = ?2 WHERE id = ?3",
|
||||
libsql::params![cell.name, cell.description, cell.id],
|
||||
).await.map_err(|e| format!("Update: {e}"))?;
|
||||
Ok(cell.id)
|
||||
} else {
|
||||
conn.execute(
|
||||
"INSERT INTO cells (name, description) VALUES (?1, ?2)",
|
||||
libsql::params![cell.name, cell.description],
|
||||
).await.map_err(|e| format!("Insert: {e}"))?;
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Project Members
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_project_members(
|
||||
state: State<'_, Mutex<String>>,
|
||||
user_id: i64,
|
||||
members: Vec<ProjectMember>,
|
||||
) -> Result<(), String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
conn.execute("DELETE FROM project_members WHERE user_id = ?1", libsql::params![user_id])
|
||||
.await
|
||||
.map_err(|e| format!("Delete: {e}"))?;
|
||||
|
||||
for m in &members {
|
||||
conn.execute(
|
||||
"INSERT INTO project_members (user_id, initiative_id, initiative_name, role) VALUES (?1, ?2, ?3, ?4)",
|
||||
libsql::params![user_id, m.initiative_id, m.initiative_name.clone(), m.role.clone()],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Insert: {e}"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_project_members(
|
||||
state: State<'_, Mutex<String>>,
|
||||
user_id: i64,
|
||||
) -> Result<Vec<ProjectMember>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
let mut rows = conn
|
||||
.query("SELECT id, user_id, initiative_id, initiative_name, role, assigned_at FROM project_members WHERE user_id = ?1 ORDER BY assigned_at DESC", libsql::params![user_id])
|
||||
.await
|
||||
.map_err(|e| format!("Query: {e}"))?;
|
||||
|
||||
let mut members = Vec::new();
|
||||
while let Some(row) = rows.next().await.map_err(|e| format!("Row: {e}"))? {
|
||||
members.push(ProjectMember {
|
||||
id: row.get::<i64>(0).unwrap_or(0),
|
||||
user_id: row.get::<i64>(1).unwrap_or(0),
|
||||
initiative_id: row.get::<i64>(2).unwrap_or(0),
|
||||
initiative_name: row.get::<String>(3).ok(),
|
||||
role: row.get::<String>(4).ok(),
|
||||
assigned_at: row.get::<String>(5).ok(),
|
||||
});
|
||||
}
|
||||
Ok(members)
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Absences
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_absences(state: State<'_, Mutex<String>>, user_id: i64) -> Result<Vec<Absence>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
let mut rows = conn
|
||||
.query("SELECT id, user_id, start_date, end_date, type, reason, created_at FROM absences WHERE user_id = ?1 ORDER BY start_date DESC", libsql::params![user_id])
|
||||
.await
|
||||
.map_err(|e| format!("Query: {e}"))?;
|
||||
|
||||
let mut absences = Vec::new();
|
||||
while let Some(row) = rows.next().await.map_err(|e| format!("Row: {e}"))? {
|
||||
absences.push(Absence {
|
||||
id: row.get::<i64>(0).unwrap_or(0),
|
||||
user_id: row.get::<i64>(1).unwrap_or(0),
|
||||
start_date: row.get::<String>(2).unwrap_or_default(),
|
||||
end_date: row.get::<String>(3).unwrap_or_default(),
|
||||
absence_type: row.get::<String>(4).unwrap_or_else(|_| "vacation".into()),
|
||||
reason: row.get::<String>(5).ok(),
|
||||
created_at: row.get::<String>(6).ok(),
|
||||
});
|
||||
}
|
||||
Ok(absences)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_absence(state: State<'_, Mutex<String>>, absence: Absence) -> Result<i64, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
if absence.id != 0 {
|
||||
conn.execute(
|
||||
"UPDATE absences SET start_date = ?1, end_date = ?2, type = ?3, reason = ?4 WHERE id = ?5",
|
||||
libsql::params![absence.start_date, absence.end_date, absence.absence_type, absence.reason, absence.id],
|
||||
).await.map_err(|e| format!("Update: {e}"))?;
|
||||
Ok(absence.id)
|
||||
} else {
|
||||
conn.execute(
|
||||
"INSERT INTO absences (user_id, start_date, end_date, type, reason) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
libsql::params![absence.user_id, absence.start_date, absence.end_date, absence.absence_type, absence.reason],
|
||||
).await.map_err(|e| format!("Insert: {e}"))?;
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_absence(state: State<'_, Mutex<String>>, id: i64) -> Result<(), String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
conn.execute("DELETE FROM absences WHERE id = ?1", libsql::params![id])
|
||||
.await
|
||||
.map_err(|e| format!("Delete: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Daily Logs
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_daily_logs(
|
||||
state: State<'_, Mutex<String>>,
|
||||
user_id: i64,
|
||||
initiative_id: Option<i64>,
|
||||
) -> Result<Vec<DailyLog>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
let query = if let Some(pid) = initiative_id {
|
||||
"SELECT id, user_id, initiative_id, date, work_item_id, what_worked_on, progress_pct, impediments, plan_for_tomorrow, hours_spent, created_at FROM daily_logs WHERE user_id = ?1 AND initiative_id = ?2 ORDER BY date DESC"
|
||||
} else {
|
||||
"SELECT id, user_id, initiative_id, date, work_item_id, what_worked_on, progress_pct, impediments, plan_for_tomorrow, hours_spent, created_at FROM daily_logs WHERE user_id = ?1 ORDER BY date DESC"
|
||||
};
|
||||
|
||||
let mut rows = if let Some(pid) = initiative_id {
|
||||
conn.query(query, libsql::params![user_id, pid]).await.map_err(|e| format!("Query: {e}"))?
|
||||
} else {
|
||||
conn.query(query, libsql::params![user_id]).await.map_err(|e| format!("Query: {e}"))?
|
||||
};
|
||||
|
||||
let mut logs = Vec::new();
|
||||
while let Some(row) = rows.next().await.map_err(|e| format!("Row: {e}"))? {
|
||||
logs.push(DailyLog {
|
||||
id: row.get::<i64>(0).unwrap_or(0),
|
||||
user_id: row.get::<i64>(1).unwrap_or(0),
|
||||
initiative_id: row.get::<i64>(2).unwrap_or(0),
|
||||
date: row.get::<String>(3).unwrap_or_default(),
|
||||
work_item_id: row.get::<i64>(4).ok(),
|
||||
what_worked_on: row.get::<String>(5).ok(),
|
||||
progress_pct: row.get::<f64>(6).ok(),
|
||||
impediments: row.get::<String>(7).ok(),
|
||||
plan_for_tomorrow: row.get::<String>(8).ok(),
|
||||
hours_spent: row.get::<f64>(9).ok(),
|
||||
created_at: row.get::<String>(10).ok(),
|
||||
});
|
||||
}
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_daily_log(state: State<'_, Mutex<String>>, log: DailyLog) -> Result<i64, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
if log.id != 0 {
|
||||
conn.execute(
|
||||
"UPDATE daily_logs SET what_worked_on = ?1, progress_pct = ?2, impediments = ?3, plan_for_tomorrow = ?4, hours_spent = ?5 WHERE id = ?6",
|
||||
libsql::params![log.what_worked_on, log.progress_pct, log.impediments, log.plan_for_tomorrow, log.hours_spent, log.id],
|
||||
).await.map_err(|e| format!("Update: {e}"))?;
|
||||
Ok(log.id)
|
||||
} else {
|
||||
conn.execute(
|
||||
"INSERT INTO daily_logs (user_id, initiative_id, date, work_item_id, what_worked_on, progress_pct, impediments, plan_for_tomorrow, hours_spent) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||
libsql::params![log.user_id, log.initiative_id, log.date, log.work_item_id, log.what_worked_on, log.progress_pct, log.impediments, log.plan_for_tomorrow, log.hours_spent],
|
||||
).await.map_err(|e| format!("Insert: {e}"))?;
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────
|
||||
// Performance
|
||||
// ────────────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_performance(
|
||||
state: State<'_, Mutex<String>>,
|
||||
user_id: i64,
|
||||
initiative_id: Option<i64>,
|
||||
) -> Result<Vec<PerformanceSnapshot>, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
let query = if let Some(pid) = initiative_id {
|
||||
"SELECT id, user_id, initiative_id, period, planned_sp, completed_sp, planned_hours, actual_hours, velocity, spi, cpi, compliance_pct, impediment_count, calculated_at FROM performance_snapshots WHERE user_id = ?1 AND initiative_id = ?2 ORDER BY calculated_at DESC"
|
||||
} else {
|
||||
"SELECT id, user_id, initiative_id, period, planned_sp, completed_sp, planned_hours, actual_hours, velocity, spi, cpi, compliance_pct, impediment_count, calculated_at FROM performance_snapshots WHERE user_id = ?1 ORDER BY calculated_at DESC"
|
||||
};
|
||||
|
||||
let mut rows = if let Some(pid) = initiative_id {
|
||||
conn.query(query, libsql::params![user_id, pid]).await.map_err(|e| format!("Query: {e}"))?
|
||||
} else {
|
||||
conn.query(query, libsql::params![user_id]).await.map_err(|e| format!("Query: {e}"))?
|
||||
};
|
||||
|
||||
let mut snapshots = Vec::new();
|
||||
while let Some(row) = rows.next().await.map_err(|e| format!("Row: {e}"))? {
|
||||
snapshots.push(PerformanceSnapshot {
|
||||
id: row.get::<i64>(0).unwrap_or(0),
|
||||
user_id: row.get::<i64>(1).unwrap_or(0),
|
||||
initiative_id: row.get::<i64>(2).ok(),
|
||||
period: row.get::<String>(3).unwrap_or_default(),
|
||||
planned_sp: row.get::<f64>(4).ok(),
|
||||
completed_sp: row.get::<f64>(5).ok(),
|
||||
planned_hours: row.get::<f64>(6).ok(),
|
||||
actual_hours: row.get::<f64>(7).ok(),
|
||||
velocity: row.get::<f64>(8).ok(),
|
||||
spi: row.get::<f64>(9).ok(),
|
||||
cpi: row.get::<f64>(10).ok(),
|
||||
compliance_pct: row.get::<f64>(11).ok(),
|
||||
impediment_count: row.get::<i64>(12).ok(),
|
||||
calculated_at: row.get::<String>(13).ok(),
|
||||
});
|
||||
}
|
||||
Ok(snapshots)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_performance(state: State<'_, Mutex<String>>, snap: PerformanceSnapshot) -> Result<i64, String> {
|
||||
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||
let conn = get_conn(&db_path).await?;
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO performance_snapshots (user_id, initiative_id, period, planned_sp, completed_sp, planned_hours, actual_hours, velocity, spi, cpi, compliance_pct, impediment_count) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
||||
libsql::params![
|
||||
snap.user_id, snap.initiative_id, snap.period,
|
||||
snap.planned_sp, snap.completed_sp,
|
||||
snap.planned_hours, snap.actual_hours,
|
||||
snap.velocity, snap.spi, snap.cpi,
|
||||
snap.compliance_pct, snap.impediment_count,
|
||||
],
|
||||
).await.map_err(|e| format!("Insert: {e}"))?;
|
||||
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user