From 22b287fcd804da261da6857199e599d54d1a59a4 Mon Sep 17 00:00:00 2001 From: Milton Gonzalez Date: Wed, 21 Oct 2026 08:01:45 -0500 Subject: [PATCH] feat: version 2.17.8 -- Panel: medidor y detalle en "Actividades pendientes" Base = actividades no eliminadas de proyectos no cancelados; el conteo de pendientes pasa a usar el mismo criterio (antes incluia eliminadas). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 10 +++++ VERSION | 2 +- backend/package.json | 2 +- backend/src/modules/dashboard/controller.js | 4 +- backend/src/modules/dashboard/repository.js | 30 ++++++++++++- frontend/package.json | 2 +- frontend/src/i18n/messages.js | 2 + .../src/pages/Dashboard/DashboardPage.jsx | 42 ++++++++++++++++++- 8 files changed, 86 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d9ae7..92333cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog - project.linkedcorp.net +## [2.17.8] - 2026-10-21 +### Changed +- Panel → "Actividades pendientes": mismo modelo que las tarjetas de proyectos. Medidor con el + porcentaje de pendientes sobre el total de actividades sin contar las de proyectos + cancelados, "N de M" debajo, y al hacer clic el bloque de abajo lista las actividades + pendientes (clic abre la actividad en su proyecto). +### Fixed +- Panel → "Actividades pendientes" contaba también actividades eliminadas y de proyectos + cancelados; ya no. + ## [2.17.7] - 2026-10-21 ### Changed - Panel → "Proyectos activos": mismo modelo que "Proyectos atrasados". Medidor con el diff --git a/VERSION b/VERSION index 17bbe1e..5dfcd3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.17.7 +2.17.8 diff --git a/backend/package.json b/backend/package.json index fee5a38..9a656e6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "project-linkedcorp-backend", - "version": "2.17.7", + "version": "2.17.8", "private": true, "type": "module", "main": "src/server.js", diff --git a/backend/src/modules/dashboard/controller.js b/backend/src/modules/dashboard/controller.js index 9f270c2..d2f2675 100644 --- a/backend/src/modules/dashboard/controller.js +++ b/backend/src/modules/dashboard/controller.js @@ -14,12 +14,13 @@ function memberFilterFor(currentUser) { export const getSummary = asyncHandler(async (req, res) => { const memberUserId = memberFilterFor(req.currentUser); - const [kpis, myActivities, byStatus, overdueProjects, activeProjects] = await Promise.all([ + const [kpis, myActivities, byStatus, overdueProjects, activeProjects, pendingActivities] = await Promise.all([ repo.summary(memberUserId), repo.myPendingActivities(req.currentUser.id, memberUserId), repo.projectsByStatus(memberUserId), repo.overdueProjectsList(memberUserId), repo.activeProjectsList(memberUserId), + repo.pendingActivitiesList(memberUserId), ]); ok(res, { kpis, @@ -27,6 +28,7 @@ export const getSummary = asyncHandler(async (req, res) => { projectsByStatus: byStatus, overdueProjects, activeProjects: activeProjects.map((p) => ({ ...p, dueStatus: dueStatus(p.estimated_end_date) })), + pendingActivities: pendingActivities.map((a) => ({ ...a, dueStatus: dueStatus(a.due_date) })), }); }); diff --git a/backend/src/modules/dashboard/repository.js b/backend/src/modules/dashboard/repository.js index e1e5197..c552fb2 100644 --- a/backend/src/modules/dashboard/repository.js +++ b/backend/src/modules/dashboard/repository.js @@ -31,10 +31,18 @@ export async function summary(memberUserId) { AND p.estimated_end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY) ${memberClause}`, memberParams ); + // Pendientes y su base (medidor): sin actividades eliminadas ni de proyectos cancelados. const [[pendingActivities]] = await pool.query( `SELECT COUNT(*) AS c FROM activities a JOIN activity_statuses st ON st.id = a.status_id - JOIN projects p ON p.id = a.project_id - WHERE st.is_closed = 0 AND p.deleted_at IS NULL ${memberClause}`, + JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id + WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL + AND ps.name NOT LIKE 'cancel%' ${memberClause}`, + memberParams + ); + const [[nonCancelledActivities]] = await pool.query( + `SELECT COUNT(*) AS c FROM activities a + JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id + WHERE a.deleted_at IS NULL AND p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%' ${memberClause}`, memberParams ); const [[overdueActivities]] = await pool.query( @@ -61,12 +69,30 @@ export async function summary(memberUserId) { overdueProjects: overdueProjects.c, dueSoonProjects: dueSoonProjects.c, pendingActivities: pendingActivities.c, + nonCancelledActivities: nonCancelledActivities.c, overdueActivities: overdueActivities.c, todayActivities: todayActivities.c, averageProgress: avgProgress.avg_pct === null ? 0 : Math.round(Number(avgProgress.avg_pct) * 100) / 100, }; } +// Mismo criterio que el KPI pendingActivities de summary(). +export async function pendingActivitiesList(memberUserId) { + const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : ''; + const [rows] = await pool.query( + `SELECT a.id, a.code, a.name, a.due_date, a.progress_pct, p.id AS project_id, p.code AS project_code, + p.name AS project_name, u.name AS responsible_name + FROM activities a JOIN activity_statuses st ON st.id = a.status_id + JOIN projects p ON p.id = a.project_id JOIN project_statuses ps ON ps.id = p.status_id + LEFT JOIN users u ON u.id = a.responsible_user_id + WHERE st.is_closed = 0 AND a.deleted_at IS NULL AND p.deleted_at IS NULL + AND ps.name NOT LIKE 'cancel%' ${memberClause} + ORDER BY (a.due_date IS NULL), a.due_date, p.code, a.code`, + memberUserId ? [memberUserId] : [] + ); + return rows; +} + // Mismo criterio que el KPI activeProjects de summary(). export async function activeProjectsList(memberUserId) { const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : ''; diff --git a/frontend/package.json b/frontend/package.json index edf536b..fb61d65 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "project-linkedcorp-frontend", "private": true, - "version": "2.17.7", + "version": "2.17.8", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/i18n/messages.js b/frontend/src/i18n/messages.js index d6d1e81..5199317 100644 --- a/frontend/src/i18n/messages.js +++ b/frontend/src/i18n/messages.js @@ -157,6 +157,7 @@ export const messages = { dashboard_my_activities: 'Mis actividades pendientes', dashboard_my_overdue_projects: 'Mis proyectos atrasados', dashboard_my_active_projects: 'Mis proyectos activos', + dashboard_pending_activities_list: 'Actividades pendientes', common_of: 'de', dashboard_back_to_my_activities: 'Ver mis actividades', dashboard_projects_by_status: 'Proyectos por estado', @@ -646,6 +647,7 @@ export const messages = { dashboard_my_activities: 'My pending activities', dashboard_my_overdue_projects: 'My overdue projects', dashboard_my_active_projects: 'My active projects', + dashboard_pending_activities_list: 'Pending activities', common_of: 'of', dashboard_back_to_my_activities: 'Show my activities', dashboard_projects_by_status: 'Projects by status', diff --git a/frontend/src/pages/Dashboard/DashboardPage.jsx b/frontend/src/pages/Dashboard/DashboardPage.jsx index 513447b..b77ee6d 100644 --- a/frontend/src/pages/Dashboard/DashboardPage.jsx +++ b/frontend/src/pages/Dashboard/DashboardPage.jsx @@ -40,6 +40,33 @@ function ProjectsDetail({ title, projects, getDueStatus, onBack }) { ); } +// Igual que ProjectsDetail pero para actividades (clic abre el proyecto en la actividad). +function ActivitiesDetail({ title, activities, onBack }) { + const { t } = useUi(); + const navigate = useNavigate(); + return ( +
+
+

{title}

+ +
+ {activities.length === 0 &&
{t('common_empty')}
} + {activities.map((a) => ( +
navigate(`/proyectos/${a.project_id}?tab=activities&activity=${a.id}`)} style={ROW_STYLE}> +
+
{a.code} — {a.name}
+
+ {a.project_code} — {a.project_name} · {a.responsible_name || '—'} · {a.due_date ? String(a.due_date).slice(0, 10) : '—'} +
+
+
+ +
+ ))} +
+ ); +} + export default function DashboardPage() { const { t } = useUi(); const navigate = useNavigate(); @@ -49,7 +76,9 @@ export default function DashboardPage() { const toggleDetail = (key) => setDetail((d) => (d === key ? null : key)); if (!data) return
{t('common_loading')}
; - const { kpis, myActivities, projectsByStatus, overdueProjects, activeProjects } = data; + const { kpis, myActivities, projectsByStatus, overdueProjects, activeProjects, pendingActivities } = data; + const activitiesTotal = Number(kpis.nonCancelledActivities) || 0; + const pending = ratioOf(kpis.pendingActivities, activitiesTotal); const activeTotal = Number(kpis.activeProjects) || 0; const nonCancelledTotal = Number(kpis.nonCancelledProjects) || 0; const active = ratioOf(kpis.activeProjects, nonCancelledTotal); @@ -75,7 +104,13 @@ export default function DashboardPage() { selected={detail === 'overdue'} /> - + toggleDetail('pending')} + selected={detail === 'pending'} + /> @@ -87,6 +122,9 @@ export default function DashboardPage() { {detail === 'active' && ( p.dueStatus} onBack={() => setDetail(null)} /> )} + {detail === 'pending' && ( + setDetail(null)} /> + )} {!detail && (

{t('dashboard_my_activities')}