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:
+26
-3
@@ -194,9 +194,10 @@ const SI_NO = ['Sí','No'];
|
||||
const API = 'api.php';
|
||||
|
||||
let data = {adm:[],corp:[],mig:[],lit:[]};
|
||||
let currentDept = 'adm';
|
||||
let editingId = null;
|
||||
let authUser = {email:'', name:''};
|
||||
let currentDept = 'adm';
|
||||
let editingId = null;
|
||||
let authUser = {email:'', name:''};
|
||||
let firstLoadDone = false; // deep-link ?edit=CODIGO solo aplica en la primera carga
|
||||
|
||||
function esc(s){return (s==null?'':String(s)).replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>');}
|
||||
function setSync(state,label){
|
||||
@@ -288,6 +289,10 @@ async function loadAll(){
|
||||
setSync('ok', 'Sincronizado');
|
||||
setUserChip();
|
||||
renderAll();
|
||||
if (!firstLoadDone) {
|
||||
firstLoadDone = true;
|
||||
checkDeepLink();
|
||||
}
|
||||
} catch(e){
|
||||
setSync('error','Sin conexión');
|
||||
toast('Error al cargar: '+e.message,'error');
|
||||
@@ -304,6 +309,24 @@ function switchTab(dept){
|
||||
renderPanel(dept);
|
||||
}
|
||||
|
||||
// Si el usuario llegó desde el Calendario con ?edit=ADM-001, busca el dept y abre el modal
|
||||
function checkDeepLink(){
|
||||
const params = new URLSearchParams(location.search);
|
||||
const code = params.get('edit');
|
||||
if(!code) return;
|
||||
for(const dept of ['adm','corp','mig','lit']){
|
||||
const r = data[dept].find(x => x.id === code);
|
||||
if(r){
|
||||
switchTab(dept);
|
||||
openModal(dept, code);
|
||||
history.replaceState({}, '', location.pathname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
toast('Trámite ' + code + ' no encontrado','error');
|
||||
history.replaceState({}, '', location.pathname);
|
||||
}
|
||||
|
||||
function renderAll(){['adm','corp','mig','lit'].forEach(renderPanel);}
|
||||
|
||||
function renderPanel(dept){
|
||||
|
||||
Reference in New Issue
Block a user