feat: version 4.4.0 -- Calendario de reuniones

Menu Calendario (Mes/Semana/Dia/Agenda) para agendar reuniones con participantes
internos y externos, RSVP, vinculo a proyecto, vencimientos de actividades,
invitaciones iCalendar por correo, recurrencia con excepciones, recordatorios
in-app y aviso de choques. Migracion 034_meetings.sql.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-28 11:04:41 -05:00
parent a6cc41666e
commit 6066f27ba0
34 changed files with 986 additions and 16 deletions
+2
View File
@@ -24,6 +24,7 @@ import ContrasenasPage from '../pages/Contrasenas/ContrasenasPage.jsx';
import EquiposPage from '../pages/Equipos/EquiposPage.jsx';
import ResetVaultPinPage from '../pages/Login/ResetVaultPinPage.jsx';
import MiCuentaPage from '../pages/MiCuenta/MiCuentaPage.jsx';
import CalendarioPage from '../pages/Calendario/CalendarioPage.jsx';
// Router de datos (createBrowserRouter): necesario para useBlocker, que avisa al salir de una
// seccion con cambios sin guardar (ver components/NavigationGuard.jsx). Mismas rutas de antes.
@@ -40,6 +41,7 @@ const router = createBrowserRouter(createRoutesFromElements(
<Route index element={<DashboardPage />} />
<Route path="mi-cuenta" element={<MiCuentaPage />} />
<Route path="proyectos" element={<ModuleRoute module="projects"><ProyectosPage /></ModuleRoute>} />
<Route path="calendario" element={<ModuleRoute module="calendar"><CalendarioPage /></ModuleRoute>} />
<Route path="proyectos/:id" element={<ModuleRoute module="projects"><ProyectoDetallePage /></ModuleRoute>} />
<Route path="tareas-recurrentes" element={<ModuleRoute module="recurring_tasks"><RecurringTasksPage /></ModuleRoute>} />
<Route path="notas" element={<ModuleRoute module="notes"><NotasPage /></ModuleRoute>} />
+2 -1
View File
@@ -67,6 +67,7 @@ export default function AppShell() {
if (!notification.is_read) await markNotificationRead(notification.id);
await reloadNotifications();
if (notification.entity_type === 'project') navigate(`/proyectos/${notification.entity_id}`);
else if (notification.entity_type === 'meeting') navigate(`/calendario?meeting=${notification.entity_id}`);
else if (notification.entity_type === 'activity') navigate(`/proyectos`);
}
@@ -94,7 +95,7 @@ export default function AppShell() {
)}
{notifData?.items?.slice(0, 8).map((n) => (
<FlyoutItem key={n.id} onClick={() => handleNotificationClick(n)}>
<span style={{ fontWeight: n.is_read ? 400 : 700 }}>{n.message}</span>
<span style={{ fontWeight: n.is_read ? 400 : 700 }}>{n.type?.startsWith('meeting_') ? `${t(`notification_${n.type}`)}: ${n.message}` : n.message}</span>
</FlyoutItem>
))}
{notifData?.items?.length > 0 && (