feat: version 4.5.0 -- Calendario personal con filtros y capas por tipo

Calendario personal para todos los roles: reuniones que organizo o me invitaron,
proyectos donde soy dueno/miembro, actividades y tareas donde soy responsable y
tareas recurrentes creadas o compartidas conmigo (solo lectura si no soy el creador).
Filtros Ver (Todo / Solo lo mio / Compartido conmigo) y Mostrar (lista de tipos con
Todo); colores por tipo, vencidos en rojo, compartido marcado con franja.
La pestana Calendario del proyecto muestra todas sus reuniones vinculadas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-28 11:14:13 -05:00
parent 6066f27ba0
commit d562351242
18 changed files with 269 additions and 38 deletions
@@ -1,4 +1,5 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Plus, Save, Trash2, RotateCcw, Pencil } from 'lucide-react';
import { useUi } from '../../context/ui-context.jsx';
import { useAsync, useAction } from '../../hooks/useAsync.js';
@@ -44,6 +45,7 @@ export default function RecurringTasksPage() {
const [editing, setEditing] = useState(false);
const [selected, setSelected] = useState(null);
const [searchParams, setSearchParams] = useSearchParams();
// Borrador del popup (nueva o edicion de una existente).
const draft = useDraftForm({
formId: selected ? 'recurring-edit' : 'recurring-new', entityId: selected?.id ?? null,
@@ -68,6 +70,15 @@ export default function RecurringTasksPage() {
];
const unitLabel = { days: t('recurring_task_unit_days'), months: t('recurring_task_unit_months'), years: t('recurring_task_unit_years') };
// Llegada desde el Calendario (?task=id): abre esa tarea como un clic en su fila.
useEffect(() => {
const id = Number(searchParams.get('task'));
if (!id || !items) return;
const found = items.find((r) => r.id === id);
if (found) startEdit(found);
setSearchParams((prev) => { const next = new URLSearchParams(prev); next.delete('task'); return next; }, { replace: true });
}, [searchParams, items]); // eslint-disable-line react-hooks/exhaustive-deps
function startNew() {
setSelected(null);
setEditing(true);