feat(calendario): app Calendario con 4 vistas + deep-link a apps origen
Cliente-side: el calendario hace fetch a /escrituras/api.php?action=list y /tramites/api.php?action=list y agrega los eventos en memoria. Sin backend nuevo (cero costo de mantenimiento, datos siempre frescos). Eventos generados automáticamente: * Escrituras → fecha_solicitud * Trámites adm/corp/mig/lit → fecha_solicitud * Trámites mig → fecha_vencimiento (resaltado como deadline) * Trámites lit → fecha_termino (resaltado como deadline) Vistas: Año (12 mini-meses con dots por evento + marca roja para vencimientos), Mes (grid con chips ≤3 por día), Semana (7 columnas), Día (lista de cards). Hoy resaltado en todas. Resaltado de vencimientos: * Vencido (días<0, status abierto) → rojo + animación flash * ≤7d → naranja * >7d → color por fuente Funcionalidad adicional: * Strip "Próximos vencimientos" con los 8 deadlines más urgentes * KPIs: total · ≤7d · ≤30d · vencidos abiertos * Filtros por fuente (chips) + búsqueda por texto * Atajos: ←/→ mueve período, T hoy, Y/M/W/D cambia vista, Esc cierra * Auto-refresh cada 60s en segundo plano (pausa si la modal está abierta) * Misma barra superior estandarizada que Escrituras y Trámites Popup ViewOnly al hacer click en un evento muestra ficha completa con todos los campos relevantes según fuente y dept. Dos botones: "Regresar al calendario" cierra · "Editar evento" navega a /escrituras/?edit=ID o /tramites/?edit=CODIGO. Deep-link: escrituras.html y tramites.html detectan ?edit=X al cargar y auto-abren la modal de edición sobre esa fila. URL se limpia con history.replaceState para que un refresh no re-abra la modal. Apps.php: card placeholder "Agenda" reemplazada por "Calendario" live.
This commit is contained in:
@@ -612,6 +612,7 @@ let pollTimer = null;
|
||||
let editModalOpen = false; // modal de edición/nuevo abierta
|
||||
let editingId = null; // id del trámite siendo editado (null = nuevo)
|
||||
let pendingRefresh = false; // poll trajo datos durante una edición
|
||||
let firstLoadDone = false; // deep-link ?edit=ID solo aplica en la primera carga
|
||||
|
||||
function esc(s) { return (s||'').replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>'); }
|
||||
function fmtDate(dt) {
|
||||
@@ -672,6 +673,10 @@ async function loadAll(showOverlay=false) {
|
||||
} else {
|
||||
render();
|
||||
setSyncState('ok','Sincronizado');
|
||||
if (!firstLoadDone) {
|
||||
firstLoadDone = true;
|
||||
checkDeepLink();
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
setSyncState('error','Sin conexión');
|
||||
@@ -729,6 +734,23 @@ function doLogout() {
|
||||
if (confirm('¿Cerrar sesión?')) window.location.href = 'api.php?action=logout';
|
||||
}
|
||||
|
||||
// Si el usuario llegó desde el Calendario con ?edit=ID, abre la modal sobre esa fila
|
||||
function checkDeepLink() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const raw = params.get('edit');
|
||||
if (!raw) return;
|
||||
const id = parseInt(raw, 10);
|
||||
if (Number.isNaN(id)) return;
|
||||
const r = allRows.find(x => x.id === id);
|
||||
if (r) {
|
||||
openEditModal(id);
|
||||
} else {
|
||||
toast('Trámite #' + id + ' no encontrado','error');
|
||||
}
|
||||
// Limpiar la URL para no re-disparar en refresh
|
||||
history.replaceState({}, '', location.pathname);
|
||||
}
|
||||
|
||||
// ── EDIT MODAL ───────────────────────────────────────────────────────────
|
||||
function openEditModal(id) {
|
||||
editingId = id;
|
||||
|
||||
Reference in New Issue
Block a user