feat: contador en las pestañas de Tareas/Comentarios/Documentos de una actividad

Circulo con el numero al lado del nombre de la pestaña: Tareas muestra las pendientes (sin
marcar), Comentarios y Documentos el total. Soporte agregado al componente Tabs reutilizable
(tab.count), no solo al detalle de actividad.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-16 12:52:00 -05:00
parent 551d4f8c38
commit 1b23da7151
7 changed files with 28 additions and 6 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog - project.linkedcorp.net
## [1.16.0] - 2026-09-16
### Added
- Contador en un circulo junto al nombre de la pestaña, dentro del detalle de una actividad:
"Tareas" muestra las tareas pendientes (sin marcar como hechas), "Comentarios" y "Documentos"
muestran el total. Se oculta cuando el contador es 0. Soporte agregado al componente `Tabs`
reutilizable (`tab.count`), disponible para cualquier otra pantalla que use pestañas.
## [1.15.2] - 2026-09-16
### Fixed
- El checkbox de "hecho" de una tarea (y el renombrado en linea) dejaba de reflejarse tras
+1 -1
View File
@@ -1 +1 @@
1.15.2
1.16.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "project-linkedcorp-backend",
"version": "1.15.2",
"version": "1.16.0",
"private": true,
"type": "module",
"main": "src/server.js",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "project-linkedcorp-frontend",
"private": true,
"version": "1.15.2",
"version": "1.16.0",
"type": "module",
"scripts": {
"dev": "vite",
+1
View File
@@ -4,6 +4,7 @@ export function Tabs({ tabs, active, onChange }) {
{tabs.map((tab) => (
<button key={tab.key} className={`tab-item ${active === tab.key ? 'active' : ''}`} onClick={() => onChange(tab.key)}>
{tab.label}
{!!tab.count && <span className="tab-badge">{tab.count}</span>}
</button>
))}
</div>
@@ -8,6 +8,8 @@ import { listTasks, createTask, updateTask, setTaskDone, deleteTask, reorderTask
import { promoteTaskToRecurring } from '../../services/recurringTasks.js';
import { listCatalog } from '../../services/catalogs.js';
import { listUsers } from '../../services/users.js';
import { listComments } from '../../services/comments.js';
import { listAttachments } from '../../services/attachments.js';
import { Button } from '../../components/Button.jsx';
import { Field, FieldRow } from '../../components/Field.jsx';
import { ComboBox } from '../../components/ComboBox.jsx';
@@ -41,6 +43,10 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
});
const [tab, setTab] = useState('detail');
// Recargadas al cambiar de pestaña (listas livianas) para que los contadores de las pestañas
// se mantengan al dia sin necesidad de que CommentsPanel/AttachmentsPanel avisen al padre.
const { data: comments } = useAsync(() => listComments('activity', activity.id), [activity.id, tab]);
const { data: attachments } = useAsync(() => listAttachments('activity', activity.id), [activity.id, tab]);
const [editing, setEditing] = useState(false);
const [form, setForm] = useState(() => toForm(activity));
const [newTask, setNewTask] = useState('');
@@ -53,6 +59,7 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
useEffect(() => { setForm(toForm(activity)); setEditing(false); }, [activity]);
const hasTasks = tasks && tasks.length > 0;
const pendingTasksCount = (tasks || []).filter((task) => !task.is_done).length;
const priorityOptions = (priorities || []).map((p) => ({ value: p.id, label: p.name }));
const statusOptions = (statuses || []).map((s) => ({ value: s.id, label: s.name }));
const userOptions = (users || []).map((u) => ({ value: u.id, label: u.name }));
@@ -141,10 +148,10 @@ export default function ActivityDetailPanel({ activity, projectId, siblingActivi
<Tabs
tabs={[
{ key: 'detail', label: t('tab_info') },
{ key: 'tasks', label: t('tasks_title') },
{ key: 'tasks', label: t('tasks_title'), count: pendingTasksCount },
{ key: 'dependencies', label: t('activity_dependencies') },
{ key: 'comments', label: t('tab_comments') },
{ key: 'documents', label: t('tab_documents') },
{ key: 'comments', label: t('tab_comments'), count: (comments || []).length },
{ key: 'documents', label: t('tab_documents'), count: (attachments || []).length },
]}
active={tab}
onChange={setTab}
+7
View File
@@ -174,11 +174,18 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
/* --- Pestañas reales --- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid rgb(var(--line)); margin-bottom: 16px; overflow-x: auto; }
.tab-item {
display: inline-flex; align-items: center; gap: 7px;
padding: 10px 14px; font-size: .82rem; font-weight: 600; color: rgb(var(--muted));
background: transparent; border: none; border-bottom: 2px solid transparent; cursor: pointer; white-space: nowrap;
}
.tab-item:hover { color: rgb(var(--body)); }
.tab-item.active { color: rgb(var(--accent)); border-bottom-color: rgb(var(--accent)); }
.tab-badge {
display: inline-flex; align-items: center; justify-content: center;
min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
background: rgb(var(--accent) / .18); color: rgb(var(--accent));
font-size: .68rem; font-weight: 700; line-height: 1;
}
/* --- Cards / KPIs --- */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin-bottom: 20px; }