feat: version 2.17.7 -- Panel: medidor y detalle en "Proyectos activos"
Porcentaje de activos sobre el total sin cancelados, "N de M" y lista de proyectos activos al hacer clic. La lista de detalle pasa a un componente compartido con "Proyectos atrasados". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "project-linkedcorp-backend",
|
||||
"version": "2.17.6",
|
||||
"version": "2.17.7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/server.js",
|
||||
|
||||
@@ -14,17 +14,19 @@ function memberFilterFor(currentUser) {
|
||||
|
||||
export const getSummary = asyncHandler(async (req, res) => {
|
||||
const memberUserId = memberFilterFor(req.currentUser);
|
||||
const [kpis, myActivities, byStatus, overdueProjects] = await Promise.all([
|
||||
const [kpis, myActivities, byStatus, overdueProjects, activeProjects] = await Promise.all([
|
||||
repo.summary(memberUserId),
|
||||
repo.myPendingActivities(req.currentUser.id, memberUserId),
|
||||
repo.projectsByStatus(memberUserId),
|
||||
repo.overdueProjectsList(memberUserId),
|
||||
repo.activeProjectsList(memberUserId),
|
||||
]);
|
||||
ok(res, {
|
||||
kpis,
|
||||
myActivities: myActivities.map((a) => ({ ...a, dueStatus: dueStatus(a.due_date) })),
|
||||
projectsByStatus: byStatus,
|
||||
overdueProjects,
|
||||
activeProjects: activeProjects.map((p) => ({ ...p, dueStatus: dueStatus(p.estimated_end_date) })),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,13 @@ export async function summary(memberUserId) {
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
// Base del medidor de "Proyectos activos": todos menos los cancelados (los completados si
|
||||
// cuentan). El catalogo no tiene bandera de "cancelado", se identifica por nombre.
|
||||
const [[nonCancelledProjects]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
||||
WHERE p.deleted_at IS NULL AND ps.name NOT LIKE 'cancel%' ${memberClause}`,
|
||||
memberParams
|
||||
);
|
||||
const [[overdueProjects]] = await pool.query(
|
||||
`SELECT COUNT(*) AS c FROM projects p JOIN project_statuses ps ON ps.id = p.status_id
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL
|
||||
@@ -50,6 +57,7 @@ export async function summary(memberUserId) {
|
||||
|
||||
return {
|
||||
activeProjects: activeProjects.c,
|
||||
nonCancelledProjects: nonCancelledProjects.c,
|
||||
overdueProjects: overdueProjects.c,
|
||||
dueSoonProjects: dueSoonProjects.c,
|
||||
pendingActivities: pendingActivities.c,
|
||||
@@ -59,6 +67,19 @@ export async function summary(memberUserId) {
|
||||
};
|
||||
}
|
||||
|
||||
// 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 = ?)' : '';
|
||||
const [rows] = await pool.query(
|
||||
`SELECT p.id, p.code, p.name, p.estimated_end_date, p.progress_pct, u.name AS owner_name, ps.name AS status_name
|
||||
FROM projects p JOIN project_statuses ps ON ps.id = p.status_id LEFT JOIN users u ON u.id = p.owner_user_id
|
||||
WHERE ps.is_closed = 0 AND p.deleted_at IS NULL ${memberClause}
|
||||
ORDER BY (p.estimated_end_date IS NULL), p.estimated_end_date, p.code`,
|
||||
memberUserId ? [memberUserId] : []
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
// Mismo criterio que el KPI overdueProjects de summary() -- la lista y el numero nunca difieren.
|
||||
export async function overdueProjectsList(memberUserId) {
|
||||
const memberClause = memberUserId ? 'AND EXISTS (SELECT 1 FROM project_members pm WHERE pm.project_id = p.id AND pm.user_id = ?)' : '';
|
||||
|
||||
Reference in New Issue
Block a user