feat: version 2.17.5 -- Panel: medidor y detalle en "Proyectos atrasados"

La tarjeta muestra un medidor semicircular (atrasados/activos) con el valor al
centro; al hacer clic, el bloque inferior lista los proyectos atrasados en vez
de mis actividades. Por ahora solo esta tarjeta.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-20 17:02:45 -05:00
parent 9231bd2c7a
commit 4583e3c456
10 changed files with 102 additions and 9 deletions
+33 -2
View File
@@ -1,3 +1,4 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useUi } from '../../context/ui-context.jsx';
import { useAsync } from '../../hooks/useAsync.js';
@@ -9,9 +10,13 @@ export default function DashboardPage() {
const { t } = useUi();
const navigate = useNavigate();
const { data } = useAsync(getSummary, []);
// Que muestra el bloque de abajo: null = mis actividades (por defecto), 'overdue' = proyectos atrasados.
const [detail, setDetail] = useState(null);
const toggleDetail = (key) => setDetail((d) => (d === key ? null : key));
if (!data) return <div className="text-muted">{t('common_loading')}</div>;
const { kpis, myActivities, projectsByStatus } = data;
const { kpis, myActivities, projectsByStatus, overdueProjects } = data;
const activeTotal = Number(kpis.activeProjects) || 0;
return (
<div>
@@ -19,7 +24,13 @@ export default function DashboardPage() {
<div className="kpi-grid">
<KpiCard label={t('dashboard_active_projects')} value={kpis.activeProjects} />
<KpiCard label={t('dashboard_overdue_projects')} value={kpis.overdueProjects} />
<KpiCard
label={t('dashboard_overdue_projects')}
value={kpis.overdueProjects}
gauge={{ ratio: activeTotal ? Number(kpis.overdueProjects) / activeTotal : 0, color: 'danger' }}
onClick={() => toggleDetail('overdue')}
selected={detail === 'overdue'}
/>
<KpiCard label={t('dashboard_due_soon_projects')} value={kpis.dueSoonProjects} />
<KpiCard label={t('dashboard_pending_activities')} value={kpis.pendingActivities} />
<KpiCard label={t('dashboard_overdue_activities')} value={kpis.overdueActivities} />
@@ -27,6 +38,25 @@ export default function DashboardPage() {
<KpiCard label={t('dashboard_average_progress')} value={`${kpis.averageProgress}%`} />
</div>
{detail === 'overdue' ? (
<div className="panel-card">
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
<h3>{t('dashboard_my_overdue_projects')}</h3>
<button type="button" className="link-button" onClick={() => setDetail(null)}>{t('dashboard_back_to_my_activities')}</button>
</div>
{overdueProjects.length === 0 && <div className="text-muted">{t('common_empty')}</div>}
{overdueProjects.map((p) => (
<div key={p.id} onClick={() => navigate(`/proyectos/${p.id}`)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '8px 0', borderBottom: '1px solid rgb(var(--line) / .5)', cursor: 'pointer' }}>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '.82rem', fontWeight: 600 }}>{p.code} — {p.name}</div>
<div className="text-muted" style={{ fontSize: '.72rem' }}>{p.owner_name || '—'} · {String(p.estimated_end_date).slice(0, 10)}</div>
</div>
<div style={{ width: 120 }}><ProgressBar value={Number(p.progress_pct)} /></div>
<DueBadge status="overdue" />
</div>
))}
</div>
) : (
<div className="panel-card">
<h3>{t('dashboard_my_activities')}</h3>
{myActivities.length === 0 && <div className="text-muted">{t('common_empty')}</div>}
@@ -41,6 +71,7 @@ export default function DashboardPage() {
</div>
))}
</div>
)}
<div className="panel-card">
<h3>{t('dashboard_projects_by_status')}</h3>