feat: filtros de Estado, Prioridad y proximos a vencer en Proyectos

El backend ya soportaba filtrar por statusId/priorityId; solo faltaba
exponerlo en la UI. "Proximos a vencer" es client-side, ventana de 7
dias (misma definicion que el KPI del dashboard).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-11 09:56:09 -05:00
parent b7901693da
commit 3ab0656a3e
6 changed files with 42 additions and 5 deletions
+31 -2
View File
@@ -40,7 +40,14 @@ export default function ProyectosPage() {
const isSuperadmin = currentUser?.roleCode === 'superadmin';
const canDelete = CAN_DELETE_ROLES.includes(currentUser?.roleCode);
const { data: projects, reload } = useAsync(() => listProjects(showTrash ? { trash: true } : {}), [showTrash]);
const [filterStatusId, setFilterStatusId] = useState('');
const [filterPriorityId, setFilterPriorityId] = useState('');
const [filterDueSoon, setFilterDueSoon] = useState(false);
const { data: projects, reload } = useAsync(
() => listProjects(showTrash ? { trash: true } : { statusId: filterStatusId, priorityId: filterPriorityId }),
[showTrash, filterStatusId, filterPriorityId]
);
const { data: priorities } = useAsync(() => listCatalog('priorities'), []);
const { data: statuses } = useAsync(() => listCatalog('project-statuses'), []);
const { data: types } = useAsync(() => listCatalog('project-types'), []);
@@ -133,6 +140,14 @@ export default function ProyectosPage() {
if (!projects) return <div className="text-muted">{t('common_loading')}</div>;
const visibleProjects = filterDueSoon && !showTrash
? projects.filter((p) => {
if (p.status_is_closed || !p.estimated_end_date) return false;
const diffDays = Math.round((new Date(p.estimated_end_date) - new Date()) / 86400000);
return diffDays >= 0 && diffDays <= 7;
})
: projects;
return (
<div>
<div className="toolbar">
@@ -155,11 +170,25 @@ export default function ProyectosPage() {
<div className="toolbar">
<SearchInput value={search} onChange={setSearch} />
{!showTrash && (
<>
<div style={{ width: 160 }}>
<ComboBox options={[{ value: '', label: t('common_all') }, ...statusOptions]} value={filterStatusId} onChange={setFilterStatusId} placeholder={t('common_status')} />
</div>
<div style={{ width: 160 }}>
<ComboBox options={[{ value: '', label: t('common_all') }, ...priorityOptions]} value={filterPriorityId} onChange={setFilterPriorityId} placeholder={t('common_priority')} />
</div>
<label className="checkbox-row">
<input type="checkbox" checked={filterDueSoon} onChange={(e) => setFilterDueSoon(e.target.checked)} />
{t('project_filter_due_soon')}
</label>
</>
)}
</div>
<ListView
columns={columns}
rows={projects}
rows={visibleProjects}
searchValue={search}
getSearchHaystack={(p) => `${p.code} ${p.name} ${p.owner_name} ${p.status_name}`}
getRowKey={(p) => p.id}