feat: version 2.17.2 -- Proyectos: selector Avance (Todos / Ocultar 100% / Solo 100%)

Reemplaza el checkbox "Ocultar proyectos 100%" para cubrir tambien "solo 100%"
sin combinaciones contradictorias. Persistido en localStorage; migra el valor
del checkbox anterior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-20 15:10:54 -05:00
parent a488965450
commit cdd49a7eff
6 changed files with 47 additions and 19 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog - project.linkedcorp.net # Changelog - project.linkedcorp.net
## [2.17.2] - 2026-10-20
### Changed
- Proyectos → listado: el checkbox "Ocultar proyectos 100%" pasa a ser el selector **Avance**
(Todos / Ocultar 100% / Solo 100%), así no hay combinaciones contradictorias. Arranca en
"Todos", se recuerda en el navegador aunque se cierre sesión, y respeta lo que ya estaba
marcado con el checkbox anterior.
## [2.17.1] - 2026-10-20 ## [2.17.1] - 2026-10-20
### Added ### Added
- Proyectos → listado: checkbox "Ocultar proyectos 100%" (desmarcado por defecto). La elección - Proyectos → listado: checkbox "Ocultar proyectos 100%" (desmarcado por defecto). La elección
+1 -1
View File
@@ -1 +1 @@
2.17.1 2.17.2
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "project-linkedcorp-backend", "name": "project-linkedcorp-backend",
"version": "2.17.1", "version": "2.17.2",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/server.js", "main": "src/server.js",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "project-linkedcorp-frontend", "name": "project-linkedcorp-frontend",
"private": true, "private": true,
"version": "2.17.1", "version": "2.17.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+6 -2
View File
@@ -186,7 +186,9 @@ export const messages = {
project_updated_at: 'Actualizado', project_updated_at: 'Actualizado',
project_deleted_at: 'Eliminado', project_deleted_at: 'Eliminado',
project_filter_due_soon: 'Solo próximos a vencer (7 días)', project_filter_due_soon: 'Solo próximos a vencer (7 días)',
project_filter_hide_completed: 'Ocultar proyectos 100%', project_filter_progress: 'Avance',
project_filter_progress_hide: 'Ocultar 100%',
project_filter_progress_only: 'Solo 100%',
nav_recurring_tasks: 'Tareas recurrentes', nav_recurring_tasks: 'Tareas recurrentes',
nav_notes: 'Notas', nav_notes: 'Notas',
@@ -669,7 +671,9 @@ export const messages = {
project_updated_at: 'Updated', project_updated_at: 'Updated',
project_deleted_at: 'Deleted', project_deleted_at: 'Deleted',
project_filter_due_soon: 'Due soon only (7 days)', project_filter_due_soon: 'Due soon only (7 days)',
project_filter_hide_completed: 'Hide 100% projects', project_filter_progress: 'Progress',
project_filter_progress_hide: 'Hide 100%',
project_filter_progress_only: 'Only 100%',
nav_recurring_tasks: 'Recurring tasks', nav_recurring_tasks: 'Recurring tasks',
nav_notes: 'Notes', nav_notes: 'Notes',
+31 -14
View File
@@ -22,7 +22,8 @@ import { DetailPopup } from '../../components/DetailPopup.jsx';
import { SharedWithIcon } from '../../components/SharedWithIcon.jsx'; import { SharedWithIcon } from '../../components/SharedWithIcon.jsx';
const CAN_DELETE_ROLES = ['superadmin', 'admin', 'it_manager', 'project_lead']; const CAN_DELETE_ROLES = ['superadmin', 'admin', 'it_manager', 'project_lead'];
const HIDE_COMPLETED_KEY = 'plc_projects_hide_completed'; const PROGRESS_FILTER_KEY = 'plc_projects_progress_filter';
const LEGACY_HIDE_COMPLETED_KEY = 'plc_projects_hide_completed';
function flatten(nodes, acc = []) { function flatten(nodes, acc = []) {
for (const n of nodes) { acc.push(n); if (n.children?.length) flatten(n.children, acc); } for (const n of nodes) { acc.push(n); if (n.children?.length) flatten(n.children, acc); }
@@ -49,13 +50,18 @@ export default function ProyectosPage() {
const [filterStatusId, setFilterStatusId] = useState(''); const [filterStatusId, setFilterStatusId] = useState('');
const [filterPriorityId, setFilterPriorityId] = useState(''); const [filterPriorityId, setFilterPriorityId] = useState('');
const [filterDueSoon, setFilterDueSoon] = useState(false); const [filterDueSoon, setFilterDueSoon] = useState(false);
// Persistido en el navegador: se conserva al cerrar sesion o recargar. // '' = todos, 'hide' = ocultar 100%, 'only' = solo 100%. Persistido en el navegador (sobrevive
const [hideCompleted, setHideCompleted] = useState(() => { // al cierre de sesion). Respeta el checkbox anterior "Ocultar 100%" (clave vieja) si existia.
try { return localStorage.getItem(HIDE_COMPLETED_KEY) === '1'; } catch { return false; } const [progressFilter, setProgressFilter] = useState(() => {
try {
const saved = localStorage.getItem(PROGRESS_FILTER_KEY);
if (saved !== null) return saved;
return localStorage.getItem(LEGACY_HIDE_COMPLETED_KEY) === '1' ? 'hide' : '';
} catch { return ''; }
}); });
function toggleHideCompleted(checked) { function changeProgressFilter(value) {
setHideCompleted(checked); setProgressFilter(value);
try { localStorage.setItem(HIDE_COMPLETED_KEY, checked ? '1' : '0'); } catch { /* sin storage: solo en memoria */ } try { localStorage.setItem(PROGRESS_FILTER_KEY, value); } catch { /* sin storage: solo en memoria */ }
} }
const [filterOwnerUserId, setFilterOwnerUserId] = useState(''); const [filterOwnerUserId, setFilterOwnerUserId] = useState('');
// Prueba exploratoria: filtro "ver todos / por usuario" solo para roles que ya ven todos los // Prueba exploratoria: filtro "ver todos / por usuario" solo para roles que ya ven todos los
@@ -194,9 +200,9 @@ export default function ProyectosPage() {
return diffDays >= 0 && diffDays <= 7; return diffDays >= 0 && diffDays <= 7;
}) })
: projects; : projects;
const visibleProjects = hideCompleted && !showTrash const visibleProjects = showTrash || !progressFilter
? dueSoonProjects.filter((p) => Number(p.progress_pct) < 100) ? dueSoonProjects
: dueSoonProjects; : dueSoonProjects.filter((p) => (Number(p.progress_pct) >= 100) === (progressFilter === 'only'));
return ( return (
<div> <div>
@@ -239,6 +245,21 @@ export default function ProyectosPage() {
<ComboBox options={[{ value: '', label: t('common_all') }, ...priorityOptions]} value={filterPriorityId} onChange={setFilterPriorityId} placeholder={t('common_priority')} /> <ComboBox options={[{ value: '', label: t('common_all') }, ...priorityOptions]} value={filterPriorityId} onChange={setFilterPriorityId} placeholder={t('common_priority')} />
</div> </div>
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span className="text-muted" style={{ fontSize: '.72rem', whiteSpace: 'nowrap' }}>{t('project_filter_progress')}</span>
<div style={{ width: 160 }}>
<ComboBox
options={[
{ value: '', label: t('common_all') },
{ value: 'hide', label: t('project_filter_progress_hide') },
{ value: 'only', label: t('project_filter_progress_only') },
]}
value={progressFilter}
onChange={changeProgressFilter}
placeholder={t('project_filter_progress')}
/>
</div>
</div>
{canFilterByUser && ( {canFilterByUser && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span className="text-muted" style={{ fontSize: '.72rem', whiteSpace: 'nowrap' }}>Usuario</span> <span className="text-muted" style={{ fontSize: '.72rem', whiteSpace: 'nowrap' }}>Usuario</span>
@@ -256,10 +277,6 @@ export default function ProyectosPage() {
<input type="checkbox" checked={filterDueSoon} onChange={(e) => setFilterDueSoon(e.target.checked)} /> <input type="checkbox" checked={filterDueSoon} onChange={(e) => setFilterDueSoon(e.target.checked)} />
{t('project_filter_due_soon')} {t('project_filter_due_soon')}
</label> </label>
<label className="checkbox-row">
<input type="checkbox" checked={hideCompleted} onChange={(e) => toggleHideCompleted(e.target.checked)} />
{t('project_filter_hide_completed')}
</label>
</> </>
)} )}
</div> </div>