feat: el buscador de Proyectos y Actividades tambien busca en tareas

Buscador de Proyectos: ahora busca tambien en actividades y tareas de cualquier proyecto (antes
solo nombre/codigo del proyecto). Resultados en un panel flotante con la ruta completa
"Proyecto - Actividad - Tarea"; clic navega al proyecto y abre la actividad encontrada
(pestaña Tareas si el resultado era una tarea).

Buscador de Actividades (dentro de un proyecto): busca tambien en las tareas de esa actividad,
mismo panel con "Actividad - Tarea".

Nuevo GET /api/search?q=...&projectId= (global o acotado a un proyecto). Nuevos componentes/hooks
reutilizables: SearchResultsDropdown, useDebouncedValue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-16 15:04:53 -05:00
parent 528a1e7cae
commit 67cb58ed0b
19 changed files with 267 additions and 13 deletions
+64
View File
@@ -0,0 +1,64 @@
import { pool } from '../../config/db.js';
const RESULT_LIMIT = 20;
// Busqueda global (menu Proyectos): proyectos, actividades (con su proyecto) y tareas (con su
// actividad y proyecto) — para armar la migaja de pan "Proyecto - Actividad - Tarea" en el cliente.
export async function searchGlobal(term) {
const like = `%${term}%`;
const [projects] = await pool.query(
`SELECT id, code, name FROM projects
WHERE deleted_at IS NULL AND (code LIKE ? OR name LIKE ?)
ORDER BY name LIMIT ?`,
[like, like, RESULT_LIMIT]
);
const [activities] = await pool.query(
`SELECT a.id, a.code, a.name,
p.id AS project_id, p.code AS project_code, p.name AS project_name
FROM activities a
JOIN projects p ON p.id = a.project_id
WHERE p.deleted_at IS NULL AND (a.code LIKE ? OR a.name LIKE ?)
ORDER BY a.name LIMIT ?`,
[like, like, RESULT_LIMIT]
);
const [tasks] = await pool.query(
`SELECT t.id, t.name,
a.id AS activity_id, a.code AS activity_code, a.name AS activity_name,
p.id AS project_id, p.code AS project_code, p.name AS project_name
FROM tasks t
JOIN activities a ON a.id = t.activity_id
JOIN projects p ON p.id = a.project_id
WHERE p.deleted_at IS NULL AND t.name LIKE ?
ORDER BY t.name LIMIT ?`,
[like, RESULT_LIMIT]
);
return { projects, activities, tasks };
}
// Busqueda dentro de un proyecto (pestaña Actividades): solo actividades y tareas de ESE
// proyecto — la migaja de pan ahi es "Actividad - Tarea" (el proyecto ya es el contexto visible).
export async function searchInProject(projectId, term) {
const like = `%${term}%`;
const [activities] = await pool.query(
`SELECT id, code, name FROM activities
WHERE project_id = ? AND (code LIKE ? OR name LIKE ?)
ORDER BY name LIMIT ?`,
[projectId, like, like, RESULT_LIMIT]
);
const [tasks] = await pool.query(
`SELECT t.id, t.name, a.id AS activity_id, a.code AS activity_code, a.name AS activity_name
FROM tasks t
JOIN activities a ON a.id = t.activity_id
WHERE a.project_id = ? AND t.name LIKE ?
ORDER BY t.name LIMIT ?`,
[projectId, like, RESULT_LIMIT]
);
return { activities, tasks };
}