proyectos: status true/false ahora muestra Activo/Inactivo via i18n
This commit is contained in:
+30
-4
@@ -11,6 +11,8 @@ pub struct Project {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
pub start_date: Option<String>,
|
pub start_date: Option<String>,
|
||||||
pub end_date: Option<String>,
|
pub end_date: Option<String>,
|
||||||
|
pub hus_count: Option<i64>,
|
||||||
|
pub epics_count: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@@ -155,7 +157,7 @@ async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
|||||||
let conn = db.connect().map_err(|e| format!("Connect: {e}"))?;
|
let conn = db.connect().map_err(|e| format!("Connect: {e}"))?;
|
||||||
|
|
||||||
conn.execute_batch(
|
conn.execute_batch(
|
||||||
"CREATE TABLE IF NOT EXISTS projects (
|
" CREATE TABLE IF NOT EXISTS projects (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
key TEXT,
|
key TEXT,
|
||||||
@@ -163,6 +165,8 @@ async fn get_conn(db_path: &str) -> Result<libsql::Connection, String> {
|
|||||||
status TEXT DEFAULT 'active',
|
status TEXT DEFAULT 'active',
|
||||||
start_date TEXT,
|
start_date TEXT,
|
||||||
end_date TEXT,
|
end_date TEXT,
|
||||||
|
hus_count INTEGER DEFAULT 0,
|
||||||
|
epics_count INTEGER DEFAULT 0,
|
||||||
created_at TEXT DEFAULT (datetime('now'))
|
created_at TEXT DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -325,7 +329,7 @@ pub mod commands {
|
|||||||
let conn = get_conn(&db_path).await?;
|
let conn = get_conn(&db_path).await?;
|
||||||
|
|
||||||
let mut rows = conn
|
let mut rows = conn
|
||||||
.query("SELECT id, name, key, description, status, start_date, end_date FROM projects ORDER BY name", ())
|
.query("SELECT id, name, key, description, status, start_date, end_date, hus_count, epics_count FROM projects ORDER BY name", ())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Query: {e}"))?;
|
.map_err(|e| format!("Query: {e}"))?;
|
||||||
|
|
||||||
@@ -339,6 +343,8 @@ pub mod commands {
|
|||||||
status: row.get::<String>(4).unwrap_or_else(|_| "active".into()),
|
status: row.get::<String>(4).unwrap_or_else(|_| "active".into()),
|
||||||
start_date: row.get::<String>(5).ok(),
|
start_date: row.get::<String>(5).ok(),
|
||||||
end_date: row.get::<String>(6).ok(),
|
end_date: row.get::<String>(6).ok(),
|
||||||
|
hus_count: row.get::<i64>(7).ok(),
|
||||||
|
epics_count: row.get::<i64>(8).ok(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(projects)
|
Ok(projects)
|
||||||
@@ -350,8 +356,8 @@ pub mod commands {
|
|||||||
let conn = get_conn(&db_path).await?;
|
let conn = get_conn(&db_path).await?;
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO projects (id, name, key, description, status, start_date, end_date)
|
"INSERT OR REPLACE INTO projects (id, name, key, description, status, start_date, end_date, hus_count, epics_count)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||||
libsql::params![
|
libsql::params![
|
||||||
project.id,
|
project.id,
|
||||||
project.name,
|
project.name,
|
||||||
@@ -360,6 +366,8 @@ pub mod commands {
|
|||||||
project.status,
|
project.status,
|
||||||
project.start_date,
|
project.start_date,
|
||||||
project.end_date,
|
project.end_date,
|
||||||
|
project.hus_count,
|
||||||
|
project.epics_count,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -368,6 +376,24 @@ pub mod commands {
|
|||||||
Ok(conn.last_insert_rowid())
|
Ok(conn.last_insert_rowid())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn update_project_counts(state: State<'_, Mutex<String>>, initiative_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(
|
||||||
|
"UPDATE projects SET
|
||||||
|
hus_count = (SELECT COUNT(*) FROM user_stories WHERE initiative_id = ?1),
|
||||||
|
epics_count = (SELECT COUNT(*) FROM epics WHERE initiative_id = ?1)
|
||||||
|
WHERE id = ?1",
|
||||||
|
libsql::params![initiative_id],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Update counts: {e}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_project(state: State<'_, Mutex<String>>, id: i64) -> Result<(), String> {
|
pub async fn delete_project(state: State<'_, Mutex<String>>, id: i64) -> Result<(), 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();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ fn main() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
commands::get_projects,
|
commands::get_projects,
|
||||||
commands::save_project,
|
commands::save_project,
|
||||||
|
commands::update_project_counts,
|
||||||
commands::delete_project,
|
commands::delete_project,
|
||||||
commands::get_work_items,
|
commands::get_work_items,
|
||||||
commands::save_work_item,
|
commands::save_work_item,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"retry": "Retry",
|
"retry": "Retry",
|
||||||
"backToProjects": "← Back to Projects",
|
"backToProjects": "Back to Projects",
|
||||||
"noDescription": "No description"
|
"noDescription": "No description"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
@@ -79,6 +79,8 @@
|
|||||||
"selectProject": "Select a project from the sidebar"
|
"selectProject": "Select a project from the sidebar"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
"active": "Active",
|
||||||
|
"inactive": "Inactive",
|
||||||
"backlog": "Backlog",
|
"backlog": "Backlog",
|
||||||
"todo": "To do",
|
"todo": "To do",
|
||||||
"inProgress": "In progress",
|
"inProgress": "In progress",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"loading": "Cargando...",
|
"loading": "Cargando...",
|
||||||
"retry": "Reintentar",
|
"retry": "Reintentar",
|
||||||
"backToProjects": "← Volver a Proyectos",
|
"backToProjects": "Volver a Proyectos",
|
||||||
"noDescription": "Sin descripción"
|
"noDescription": "Sin descripción"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
@@ -79,6 +79,8 @@
|
|||||||
"selectProject": "Seleccioná un proyecto del panel lateral"
|
"selectProject": "Seleccioná un proyecto del panel lateral"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
|
"active": "Activo",
|
||||||
|
"inactive": "Inactivo",
|
||||||
"backlog": "Backlog",
|
"backlog": "Backlog",
|
||||||
"todo": "Por hacer",
|
"todo": "Por hacer",
|
||||||
"inProgress": "En progreso",
|
"inProgress": "En progreso",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export interface ProjectRecord {
|
|||||||
status: string
|
status: string
|
||||||
start_date: string | null
|
start_date: string | null
|
||||||
end_date: string | null
|
end_date: string | null
|
||||||
|
hus_count: number | null
|
||||||
|
epics_count: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EpicRecord {
|
export interface EpicRecord {
|
||||||
@@ -141,6 +143,9 @@ export const tauriDb = {
|
|||||||
saveProject(project: ProjectRecord): Promise<number> {
|
saveProject(project: ProjectRecord): Promise<number> {
|
||||||
return safeInvoke('save_project', { project })
|
return safeInvoke('save_project', { project })
|
||||||
},
|
},
|
||||||
|
updateProjectCounts(initiativeId: number): Promise<void> {
|
||||||
|
return safeInvoke('update_project_counts', { initiativeId })
|
||||||
|
},
|
||||||
deleteProject(id: number): Promise<void> {
|
deleteProject(id: number): Promise<void> {
|
||||||
return safeInvoke('delete_project', { id })
|
return safeInvoke('delete_project', { id })
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const useProjectsStore = defineStore('projects', () => {
|
|||||||
status: p.status || 'active',
|
status: p.status || 'active',
|
||||||
start_date: p.start_date ?? null,
|
start_date: p.start_date ?? null,
|
||||||
end_date: p.end_date ?? null,
|
end_date: p.end_date ?? null,
|
||||||
|
hus_count: null,
|
||||||
|
epics_count: null,
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ export const useWorkItemsStore = defineStore('workitems', () => {
|
|||||||
if (isFirstVisit || newEpics.length > 0) {
|
if (isFirstVisit || newEpics.length > 0) {
|
||||||
await syncEpicsToTurso(id, isFirstVisit ? epicData : newEpics)
|
await syncEpicsToTurso(id, isFirstVisit ? epicData : newEpics)
|
||||||
}
|
}
|
||||||
|
// Actualizar contadores en projects
|
||||||
|
await tauriDb.updateProjectCounts(id).catch(() => {})
|
||||||
|
|
||||||
firstVisit.value.add(id)
|
firstVisit.value.add(id)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const statusLabel = (status: unknown) => {
|
|||||||
<FileText class="size-4 text-muted-foreground" />
|
<FileText class="size-4 text-muted-foreground" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div class="text-2xl font-bold">{{ workItems.totalHUs }}</div>
|
<div id="dashboard-stats-hus-count" class="text-2xl font-bold">{{ workItems.totalHUs }}</div>
|
||||||
<p class="text-xs text-muted-foreground">{{ t('dashboard.husSubtitle') }}</p>
|
<p class="text-xs text-muted-foreground">{{ t('dashboard.husSubtitle') }}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ const tabContent: Record<string, { title: string; description: string; cards: {
|
|||||||
<SiteHeader :active-tab="viewingProject ? 'projects' : activeTab" />
|
<SiteHeader :active-tab="viewingProject ? 'projects' : activeTab" />
|
||||||
<div class="flex flex-1 flex-col">
|
<div class="flex flex-1 flex-col">
|
||||||
<div class="@container/main flex flex-1 flex-col gap-2">
|
<div class="@container/main flex flex-1 flex-col gap-2">
|
||||||
<div class="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
<div class="flex flex-col gap-0 py-4 md:gap-0 md:py-6">
|
||||||
<template v-if="viewingProject">
|
<template v-if="viewingProject">
|
||||||
<div class="px-4 lg:px-6">
|
<div class="px-4 lg:px-6">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ import { Skeleton } from "@/components/ui/skeleton"
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const projects = useProjectsStore()
|
const projects = useProjectsStore()
|
||||||
|
|
||||||
|
function getStatusVariant(status?: string) {
|
||||||
|
const s = String(status ?? '').toLowerCase()
|
||||||
|
if (s === 'true' || ['active', 'completado', 'done', 'completed'].includes(s)) return 'default'
|
||||||
|
if (s === 'false' || ['inactive', 'paused', 'cancelled'].includes(s)) return 'secondary'
|
||||||
|
return 'outline'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusLabel(status?: string) {
|
||||||
|
const s = String(status ?? '').toLowerCase()
|
||||||
|
if (s === 'true' || s === 'active') return t('status.active')
|
||||||
|
if (s === 'false' || s === 'inactive') return t('status.inactive')
|
||||||
|
return s || '—'
|
||||||
|
}
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'select-project': [id: number]
|
'select-project': [id: number]
|
||||||
}>()
|
}>()
|
||||||
@@ -23,14 +37,6 @@ onMounted(() => {
|
|||||||
projects.fetchProjects()
|
projects.fetchProjects()
|
||||||
})
|
})
|
||||||
|
|
||||||
function getStatusVariant(status?: string) {
|
|
||||||
switch (status) {
|
|
||||||
case 'active': return 'default'
|
|
||||||
case 'completed': return 'secondary'
|
|
||||||
case 'paused': return 'outline'
|
|
||||||
default: return 'default'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -95,8 +101,8 @@ function getStatusVariant(status?: string) {
|
|||||||
<CardTitle class="text-base">
|
<CardTitle class="text-base">
|
||||||
{{ p.initiative_name || p.name || t('projects.unnamedFallback', { id: p.id }) }}
|
{{ p.initiative_name || p.name || t('projects.unnamedFallback', { id: p.id }) }}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Badge :variant="getStatusVariant(p.status)">
|
<Badge id="projects-status-badge" :variant="getStatusVariant(p.status)">
|
||||||
{{ p.status || 'active' }}
|
{{ getStatusLabel(p.status) }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<CardDescription class="line-clamp-2">
|
<CardDescription class="line-clamp-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user