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,
|
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> {
|
async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
||||||
let db = libsql::Builder::new_local(db_path)
|
let db = libsql::Builder::new_local(db_path)
|
||||||
.build()
|
.build()
|
||||||
@@ -57,6 +141,89 @@ async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
|||||||
priority TEXT DEFAULT 'medium',
|
priority TEXT DEFAULT 'medium',
|
||||||
created_at TEXT DEFAULT (datetime('now')),
|
created_at TEXT DEFAULT (datetime('now')),
|
||||||
FOREIGN KEY (project_id) REFERENCES projects(id)
|
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
|
.await
|
||||||
@@ -68,6 +235,10 @@ async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
|||||||
pub mod commands {
|
pub mod commands {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
// Projects
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_projects(state: State<'_, Mutex<String>>) -> Result<Vec<Project>, String> {
|
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();
|
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||||
@@ -133,6 +304,10 @@ pub mod commands {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
// Work Items
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_work_items(state: State<'_, Mutex<String>>, project_id: i64) -> Result<Vec<WorkItem>, String> {
|
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();
|
let db_path = state.lock().map_err(|e| e.to_string())?.clone();
|
||||||
@@ -198,4 +373,374 @@ pub mod commands {
|
|||||||
|
|
||||||
Ok(())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,20 @@ fn main() {
|
|||||||
commands::get_work_items,
|
commands::get_work_items,
|
||||||
commands::save_work_item,
|
commands::save_work_item,
|
||||||
commands::delete_work_item,
|
commands::delete_work_item,
|
||||||
|
commands::get_users,
|
||||||
|
commands::save_user,
|
||||||
|
commands::update_user_fields,
|
||||||
|
commands::get_cells,
|
||||||
|
commands::save_cell,
|
||||||
|
commands::save_project_members,
|
||||||
|
commands::get_project_members,
|
||||||
|
commands::get_absences,
|
||||||
|
commands::save_absence,
|
||||||
|
commands::delete_absence,
|
||||||
|
commands::get_daily_logs,
|
||||||
|
commands::save_daily_log,
|
||||||
|
commands::get_performance,
|
||||||
|
commands::save_performance,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
Reference in New Issue
Block a user