feat: version 2.11.0 -- contadores de dependencias/comentarios/documentos/miembros en listados

Actividades (dentro de un proyecto): nuevas columnas con la cantidad de dependencias,
comentarios y documentos de cada fila -- subconsultas correlacionadas agregadas al
LIST_SELECT existente (mismo patron que share_count de notas/proyectos), siempre numericas
(0 si no hay, nunca oculto).

Proyectos: nuevas columnas con la cantidad de miembros, actividades completas al 100% sobre
el total (ej. "1/4", mismo patron pendientes/total que ya usa la pestaña de tareas de una
actividad), comentarios y documentos.

Nuevo componente CountBadge (icono + numero) reutilizado en ambos listados -- a diferencia de
SharedWithIcon/Tabs que ocultan el badge en 0, este siempre lo muestra.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-14 11:40:07 -05:00
parent 481ba008da
commit 4520abe96f
10 changed files with 76 additions and 12 deletions
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Plus, Save, Trash2, ArrowRightLeft, FolderUp, Archive, RotateCcw } from 'lucide-react';
import { Plus, Save, Trash2, ArrowRightLeft, FolderUp, Archive, RotateCcw, Link2, MessageSquare, Paperclip } from 'lucide-react';
import { useUi } from '../../context/ui-context.jsx';
import { useAuth } from '../../context/auth-context.jsx';
import { useAsync, useAction } from '../../hooks/useAsync.js';
@@ -19,6 +19,7 @@ import { ComboBox } from '../../components/ComboBox.jsx';
import { StatusBadge, ProgressBar, DueBadge } from '../../components/StatusBadge.jsx';
import { dueStatus } from '../../utils/dueStatus.js';
import { DetailPopup } from '../../components/DetailPopup.jsx';
import { CountBadge } from '../../components/CountBadge.jsx';
import ActivityDetailPanel from './ActivityDetailPanel.jsx';
const EMPTY_FORM = { name: '', description: '', responsibleUserId: '', priorityId: '', statusId: '', startDate: '', dueDate: '', isMilestone: false };
@@ -212,6 +213,9 @@ export default function ProjectActivitiesTab({ project, projectId, openActivityI
</div>
) },
{ key: 'progress_pct', label: t('activity_progress'), render: (r) => <div style={{ width: 100 }}><ProgressBar value={Number(r.progress_pct)} /></div> },
{ key: 'dependencies_count', label: t('activity_dependencies'), render: (r) => <CountBadge icon={Link2} count={r.dependencies_count} title={t('activity_dependencies')} /> },
{ key: 'comments_count', label: t('tab_comments'), render: (r) => <CountBadge icon={MessageSquare} count={r.comments_count} title={t('tab_comments')} /> },
{ key: 'documents_count', label: t('tab_documents'), render: (r) => <CountBadge icon={Paperclip} count={r.documents_count} title={t('tab_documents')} /> },
{ key: 'updated_at', label: t('activity_updated_at'), render: (r) => <span style={{ whiteSpace: 'nowrap' }}>{String(r.updated_at).slice(0, 16)}</span> },
];
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Plus, Save, Trash2, RotateCcw, ArrowLeft } from 'lucide-react';
import { Plus, Save, Trash2, RotateCcw, ArrowLeft, Users, ListChecks, MessageSquare, Paperclip } from 'lucide-react';
import { useUi } from '../../context/ui-context.jsx';
import { useAuth } from '../../context/auth-context.jsx';
import { useAsync, useAction } from '../../hooks/useAsync.js';
@@ -20,6 +20,7 @@ import { StatusBadge, ProgressBar } from '../../components/StatusBadge.jsx';
import { Badge } from '../../components/Badge.jsx';
import { DetailPopup } from '../../components/DetailPopup.jsx';
import { SharedWithIcon } from '../../components/SharedWithIcon.jsx';
import { CountBadge } from '../../components/CountBadge.jsx';
const CAN_DELETE_ROLES = ['superadmin', 'admin', 'it_manager', 'project_lead'];
@@ -169,7 +170,13 @@ export default function ProyectosPage() {
{ key: 'status_name', label: t('common_status'), render: (r) => <StatusBadge name={r.status_name} color={r.status_color} /> },
{ key: 'priority_name', label: t('common_priority'), sortValue: (r) => r.priority_level, render: (r) => <StatusBadge name={r.priority_name} color={r.priority_color} /> },
{ key: 'owner_name', label: t('project_owner') },
{ key: 'members_count', label: t('project_members'), render: (r) => <CountBadge icon={Users} count={r.members_count} title={t('project_members')} /> },
{ key: 'progress_pct', label: t('project_progress'), render: (r) => <div style={{ width: 90 }}><ProgressBar value={Number(r.progress_pct)} /></div> },
{ key: 'activities_progress', label: t('tab_activities'), sortValue: (r) => (r.activities_total ? r.activities_complete / r.activities_total : 0), render: (r) => (
<span className="count-badge" title={t('tab_activities')}><ListChecks size={14} />{r.activities_complete}/{r.activities_total}</span>
) },
{ key: 'comments_count', label: t('tab_comments'), render: (r) => <CountBadge icon={MessageSquare} count={r.comments_count} title={t('tab_comments')} /> },
{ key: 'documents_count', label: t('tab_documents'), render: (r) => <CountBadge icon={Paperclip} count={r.documents_count} title={t('tab_documents')} /> },
{ key: 'shared', label: '', sortable: false, render: (r) => <SharedWithIcon projectId={r.id} count={r.shared_count} /> },
{ key: 'risk_level', label: t('project_risk'), render: (r) => <Badge color={RISK_COLOR[r.risk_level]}>{t(`risk_${r.risk_level}`)}</Badge> },
{ key: 'updated_at', label: t('project_updated_at'), render: (r) => String(r.updated_at).slice(0, 16) },