feat: Add panel page with daily quiz and update functionality
- Introduced a new panel page that includes a daily quiz based on the menu and tasks for the day. - Implemented functions to fetch and decrypt daily data, build quiz questions, and render the quiz interface. - Added a button to refresh the application, clearing the cache and updating the service worker. - Enhanced service worker to manage application version checks and handle CouchDB URL prefix. - Created a version.json file to manage application versioning.
This commit is contained in:
3
build.py
3
build.py
@@ -2,6 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
def get_all_files(directory):
|
def get_all_files(directory):
|
||||||
files = []
|
files = []
|
||||||
@@ -14,7 +15,7 @@ def get_all_files(directory):
|
|||||||
return files
|
return files
|
||||||
|
|
||||||
PREFETCH = ""
|
PREFETCH = ""
|
||||||
VERSIONCO = "2026-02"
|
VERSIONCO = "2026-02-23_" + time.strftime("%Y%m%d%H%M%S")
|
||||||
HANDLEPARSE = get_all_files("src")
|
HANDLEPARSE = get_all_files("src")
|
||||||
TITLE = os.environ.get("TELESEC_TITLE", "TeleSec")
|
TITLE = os.environ.get("TELESEC_TITLE", "TeleSec")
|
||||||
HOSTER = os.environ.get("TELESEC_HOSTER", "EuskadiTech")
|
HOSTER = os.environ.get("TELESEC_HOSTER", "EuskadiTech")
|
||||||
|
|||||||
@@ -86,10 +86,10 @@
|
|||||||
<!-- <script src="page/avisos.js"></script> -->
|
<!-- <script src="page/avisos.js"></script> -->
|
||||||
<script src="page/comedor.js"></script>
|
<script src="page/comedor.js"></script>
|
||||||
<script src="page/notas.js"></script>
|
<script src="page/notas.js"></script>
|
||||||
|
<script src="page/panel.js"></script>
|
||||||
<!-- <script src="page/chat.js"></script> -->
|
<!-- <script src="page/chat.js"></script> -->
|
||||||
<script src="page/buscar.js"></script>
|
<script src="page/buscar.js"></script>
|
||||||
<script src="page/pagos.js"></script>
|
<script src="page/pagos.js"></script>
|
||||||
<script src="page/cajas.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,950 +0,0 @@
|
|||||||
PERMS['cajas'] = 'Cajas';
|
|
||||||
PERMS['cajas:edit'] = '> Editar';
|
|
||||||
PAGES.cajas = {
|
|
||||||
navcss: 'btn8',
|
|
||||||
icon: 'static/appico/piggy_bank.png',
|
|
||||||
AccessControl: true,
|
|
||||||
Title: 'Cajas',
|
|
||||||
|
|
||||||
// View/edit a specific transaction (movimiento)
|
|
||||||
movimiento: function (cajaId, movimientoId) {
|
|
||||||
if (!checkRole('cajas')) {
|
|
||||||
setUrlHash('cajas');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var field_fecha = safeuuid();
|
|
||||||
var field_tipo = safeuuid();
|
|
||||||
var field_monto = safeuuid();
|
|
||||||
var field_persona = safeuuid();
|
|
||||||
var field_notas = safeuuid();
|
|
||||||
var field_foto = safeuuid();
|
|
||||||
var render_foto = safeuuid();
|
|
||||||
var btn_volver = safeuuid();
|
|
||||||
var btn_borrar = safeuuid();
|
|
||||||
var btn_editar = safeuuid();
|
|
||||||
var btn_guardar = safeuuid();
|
|
||||||
var btn_cancelar = safeuuid();
|
|
||||||
var div_buttons = safeuuid();
|
|
||||||
var isEditMode = false;
|
|
||||||
|
|
||||||
container.innerHTML = html`
|
|
||||||
<h1>Movimiento de Caja</h1>
|
|
||||||
<fieldset>
|
|
||||||
<label>
|
|
||||||
Fecha<br>
|
|
||||||
<input type="datetime-local" id="${field_fecha}" disabled><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Tipo<br>
|
|
||||||
<input type="text" id="${field_tipo}" disabled><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Monto<br>
|
|
||||||
<input type="number" step="0.01" id="${field_monto}" disabled><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Persona<br>
|
|
||||||
<input type="text" id="${field_persona}" disabled><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Foto del Ticket<br>
|
|
||||||
<img id="${render_foto}" height="200px" style="border: 3px solid #ddd; display: none; margin: 10px 0; cursor: pointer;">
|
|
||||||
<input type="file" accept="image/*" id="${field_foto}" style="display: none;">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Notas<br>
|
|
||||||
<textarea id="${field_notas}" disabled rows="4"></textarea><br><br>
|
|
||||||
</label>
|
|
||||||
<hr>
|
|
||||||
<div id="${div_buttons}">
|
|
||||||
<button class="btn5" id="${btn_volver}">Volver a Caja</button>
|
|
||||||
<button class="btn5" id="${btn_editar}" style="display: none;">Editar</button>
|
|
||||||
<button class="btn5" id="${btn_guardar}" style="display: none;">Guardar</button>
|
|
||||||
<button class="rojo" id="${btn_cancelar}" style="display: none;">Cancelar</button>
|
|
||||||
<button class="rojo" id="${btn_borrar}" style="display: none;">Borrar</button>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Load transaction data
|
|
||||||
var movimientoData = null;
|
|
||||||
var resized = '';
|
|
||||||
|
|
||||||
DB.get('cajas_movimientos', movimientoId).then((data) => {
|
|
||||||
function load_data(data) {
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
movimientoData = data;
|
|
||||||
|
|
||||||
// Format datetime for datetime-local input
|
|
||||||
var fechaValue = data['Fecha'] || '';
|
|
||||||
if (fechaValue) {
|
|
||||||
// Convert ISO string to datetime-local format (YYYY-MM-DDTHH:mm)
|
|
||||||
fechaValue = fechaValue.substring(0, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById(field_fecha).value = fechaValue;
|
|
||||||
document.getElementById(field_tipo).value = data['Tipo'] || '';
|
|
||||||
document.getElementById(field_monto).value = data['Monto'] || 0;
|
|
||||||
|
|
||||||
// Get persona name
|
|
||||||
var personaId = data['Persona'] || '';
|
|
||||||
var personaName = personaId;
|
|
||||||
if (SC_Personas[personaId]) {
|
|
||||||
personaName = SC_Personas[personaId].Nombre || personaId;
|
|
||||||
}
|
|
||||||
document.getElementById(field_persona).value = personaName;
|
|
||||||
document.getElementById(field_notas).value = data['Notas'] || '';
|
|
||||||
|
|
||||||
// Load photo attachment if present
|
|
||||||
if (DB.getAttachment) {
|
|
||||||
DB.getAttachment('cajas_movimientos', movimientoId, 'foto')
|
|
||||||
.then((durl) => {
|
|
||||||
try {
|
|
||||||
if (durl) {
|
|
||||||
var fotoElement = document.getElementById(render_foto);
|
|
||||||
if (fotoElement) {
|
|
||||||
fotoElement.src = durl;
|
|
||||||
fotoElement.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Error setting foto:', e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn('Error loading foto:', e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
TS_decrypt(
|
|
||||||
data,
|
|
||||||
SECRET,
|
|
||||||
(data, wasEncrypted) => {
|
|
||||||
load_data(data);
|
|
||||||
},
|
|
||||||
'cajas_movimientos',
|
|
||||||
movimientoId
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
load_data(data || {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enable edit mode
|
|
||||||
function enableEditMode() {
|
|
||||||
isEditMode = true;
|
|
||||||
document.getElementById(field_fecha).disabled = false;
|
|
||||||
document.getElementById(field_tipo).disabled = false;
|
|
||||||
document.getElementById(field_monto).disabled = false;
|
|
||||||
document.getElementById(field_persona).disabled = false;
|
|
||||||
document.getElementById(field_notas).disabled = false;
|
|
||||||
document.getElementById(render_foto).style.cursor = 'pointer';
|
|
||||||
|
|
||||||
document.getElementById(btn_editar).style.display = 'none';
|
|
||||||
document.getElementById(btn_volver).style.display = 'none';
|
|
||||||
document.getElementById(btn_guardar).style.display = 'inline-block';
|
|
||||||
document.getElementById(btn_cancelar).style.display = 'inline-block';
|
|
||||||
document.getElementById(btn_borrar).style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable edit mode
|
|
||||||
function disableEditMode() {
|
|
||||||
isEditMode = false;
|
|
||||||
document.getElementById(field_fecha).disabled = true;
|
|
||||||
document.getElementById(field_tipo).disabled = true;
|
|
||||||
document.getElementById(field_monto).disabled = true;
|
|
||||||
document.getElementById(field_persona).disabled = true;
|
|
||||||
document.getElementById(field_notas).disabled = true;
|
|
||||||
document.getElementById(render_foto).style.cursor = 'default';
|
|
||||||
|
|
||||||
document.getElementById(btn_editar).style.display = checkRole('cajas:edit') ? 'inline-block' : 'none';
|
|
||||||
document.getElementById(btn_volver).style.display = 'inline-block';
|
|
||||||
document.getElementById(btn_guardar).style.display = 'none';
|
|
||||||
document.getElementById(btn_cancelar).style.display = 'none';
|
|
||||||
document.getElementById(btn_borrar).style.display = checkRole('cajas:edit') ? 'inline-block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Button handlers
|
|
||||||
document.getElementById(btn_volver).onclick = () => {
|
|
||||||
setUrlHash('cajas,' + cajaId);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById(btn_editar).onclick = () => {
|
|
||||||
enableEditMode();
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById(btn_cancelar).onclick = () => {
|
|
||||||
disableEditMode();
|
|
||||||
// Reload data to discard changes
|
|
||||||
DB.get('cajas_movimientos', movimientoId).then((data) => {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
TS_decrypt(data, SECRET, (d) => { load_data(d); }, 'cajas_movimientos', movimientoId);
|
|
||||||
} else {
|
|
||||||
load_data(data || {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Photo click handler
|
|
||||||
document.getElementById(render_foto).onclick = () => {
|
|
||||||
if (isEditMode) {
|
|
||||||
// In edit mode: upload new photo
|
|
||||||
document.getElementById(field_foto).click();
|
|
||||||
} else {
|
|
||||||
// In read mode: open photo in new tab
|
|
||||||
var fotoElement = document.getElementById(render_foto);
|
|
||||||
if (fotoElement && fotoElement.src) {
|
|
||||||
window.open(fotoElement.src, '_blank');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById(field_foto).addEventListener('change', function (e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function (ev) {
|
|
||||||
const url = ev.target.result;
|
|
||||||
document.getElementById(render_foto).src = url;
|
|
||||||
resized = url;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save handler
|
|
||||||
document.getElementById(btn_guardar).onclick = () => {
|
|
||||||
var guardarBtn = document.getElementById(btn_guardar);
|
|
||||||
if (guardarBtn.disabled) return;
|
|
||||||
|
|
||||||
var tipo = document.getElementById(field_tipo).value;
|
|
||||||
var monto = parseFloat(document.getElementById(field_monto).value);
|
|
||||||
var fecha = document.getElementById(field_fecha).value;
|
|
||||||
var notas = document.getElementById(field_notas).value;
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
if (!tipo) {
|
|
||||||
alert('Por favor selecciona el tipo de movimiento');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!monto || monto <= 0) {
|
|
||||||
alert('Por favor ingresa un monto válido');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!fecha) {
|
|
||||||
alert('Por favor selecciona una fecha');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
guardarBtn.disabled = true;
|
|
||||||
guardarBtn.style.opacity = '0.5';
|
|
||||||
|
|
||||||
var fechaISO = new Date(fecha).toISOString();
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
Caja: movimientoData.Caja,
|
|
||||||
Fecha: fechaISO,
|
|
||||||
Tipo: tipo,
|
|
||||||
Monto: monto,
|
|
||||||
Persona: movimientoData.Persona,
|
|
||||||
Notas: notas,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Preserve transfer destination if applicable
|
|
||||||
if (movimientoData.CajaDestino) {
|
|
||||||
data.CajaDestino = movimientoData.CajaDestino;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('actionStatus').style.display = 'block';
|
|
||||||
DB.put('cajas_movimientos', movimientoId, data)
|
|
||||||
.then(() => {
|
|
||||||
// Save photo attachment if a new one was provided
|
|
||||||
var attachPromise = Promise.resolve(true);
|
|
||||||
if (resized && resized.indexOf('data:') === 0) {
|
|
||||||
attachPromise = DB.putAttachment(
|
|
||||||
'cajas_movimientos',
|
|
||||||
movimientoId,
|
|
||||||
'foto',
|
|
||||||
resized,
|
|
||||||
'image/png'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachPromise
|
|
||||||
.then(() => {
|
|
||||||
toastr.success('Movimiento actualizado!');
|
|
||||||
disableEditMode();
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
// Reload to show updates
|
|
||||||
setTimeout(() => {
|
|
||||||
PAGES.cajas.movimiento(cajaId, movimientoId);
|
|
||||||
}, SAVE_WAIT);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn('Error saving:', e);
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
guardarBtn.disabled = false;
|
|
||||||
guardarBtn.style.opacity = '1';
|
|
||||||
toastr.error('Error al guardar el movimiento');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn('DB.put error', e);
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
guardarBtn.disabled = false;
|
|
||||||
guardarBtn.style.opacity = '1';
|
|
||||||
toastr.error('Error al guardar el movimiento');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show/hide edit button based on permissions
|
|
||||||
if (checkRole('cajas:edit')) {
|
|
||||||
document.getElementById(btn_editar).style.display = 'inline-block';
|
|
||||||
document.getElementById(btn_borrar).style.display = 'inline-block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete handler
|
|
||||||
document.getElementById(btn_borrar).onclick = () => {
|
|
||||||
if (confirm('¿Quieres borrar este movimiento?')) {
|
|
||||||
DB.del('cajas_movimientos', movimientoId).then(() => {
|
|
||||||
toastr.success('Movimiento borrado!');
|
|
||||||
setTimeout(() => {
|
|
||||||
setUrlHash('cajas,' + cajaId);
|
|
||||||
}, SAVE_WAIT);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Create new transaction (movimiento)
|
|
||||||
nuevo_movimiento: function (cajaId) {
|
|
||||||
if (!checkRole('cajas:edit')) {
|
|
||||||
setUrlHash('cajas,' + cajaId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var field_fecha = safeuuid();
|
|
||||||
var field_tipo = safeuuid();
|
|
||||||
var field_monto = safeuuid();
|
|
||||||
var field_persona = safeuuid();
|
|
||||||
var field_notas = safeuuid();
|
|
||||||
var field_foto = safeuuid();
|
|
||||||
var render_foto = safeuuid();
|
|
||||||
var field_caja_destino = safeuuid();
|
|
||||||
var div_caja_destino = safeuuid();
|
|
||||||
var btn_guardar = safeuuid();
|
|
||||||
var btn_cancelar = safeuuid();
|
|
||||||
|
|
||||||
var resized = '';
|
|
||||||
|
|
||||||
container.innerHTML = html`
|
|
||||||
<h1>Nuevo Movimiento</h1>
|
|
||||||
<fieldset>
|
|
||||||
<label>
|
|
||||||
Fecha y Hora<br>
|
|
||||||
<input type="datetime-local" id="${field_fecha}"><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Tipo de Movimiento<br>
|
|
||||||
<select id="${field_tipo}">
|
|
||||||
<option value="">-- Seleccionar --</option>
|
|
||||||
<option value="Ingreso">+ Ingreso</option>
|
|
||||||
<option value="Gasto">- Gasto</option>
|
|
||||||
<option value="Transferencia">> Transferencia</option>
|
|
||||||
</select><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Monto (€)<br>
|
|
||||||
<input type="number" step="0.01" min="0" id="${field_monto}" placeholder="0.00"><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Persona<br>
|
|
||||||
<input type="hidden" id="${field_persona}">
|
|
||||||
<div id="personaSelector"></div>
|
|
||||||
</label>
|
|
||||||
<div id="${div_caja_destino}" style="display: none;">
|
|
||||||
<label>
|
|
||||||
Caja Destino (para transferencias)<br>
|
|
||||||
<select id="${field_caja_destino}">
|
|
||||||
<option value="">-- Seleccionar Caja Destino --</option>
|
|
||||||
</select><br><br>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label>
|
|
||||||
Foto del Ticket<br>
|
|
||||||
<small>Obligatorio para gastos</small><br>
|
|
||||||
<img id="${render_foto}" height="150px" style="border: 3px dashed #ccc; min-width: 100px; cursor: pointer; display: block; margin: 10px 0; background: #f5f5f5;" title="Haz clic para subir foto">
|
|
||||||
<input type="file" accept="image/*" id="${field_foto}" style="display: none;"><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Notas<br>
|
|
||||||
<textarea id="${field_notas}" rows="4" placeholder="Descripción del movimiento..."></textarea><br><br>
|
|
||||||
</label>
|
|
||||||
<hr>
|
|
||||||
<button class="btn5" id="${btn_guardar}">Guardar Movimiento</button>
|
|
||||||
<button class="rojo" id="${btn_cancelar}">Cancelar</button>
|
|
||||||
</fieldset>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Set current datetime
|
|
||||||
var now = new Date();
|
|
||||||
var tzOffset = now.getTimezoneOffset() * 60000;
|
|
||||||
var localISOTime = new Date(now - tzOffset).toISOString().slice(0, 16);
|
|
||||||
document.getElementById(field_fecha).value = localISOTime;
|
|
||||||
|
|
||||||
// Load personas for selection
|
|
||||||
var selectedPersona = '';
|
|
||||||
var container_personas = document.querySelector('#personaSelector');
|
|
||||||
addCategory_Personas(
|
|
||||||
container_personas,
|
|
||||||
SC_Personas,
|
|
||||||
selectedPersona,
|
|
||||||
(personaId) => {
|
|
||||||
document.getElementById(field_persona).value = personaId;
|
|
||||||
selectedPersona = personaId;
|
|
||||||
},
|
|
||||||
'Persona',
|
|
||||||
false,
|
|
||||||
'- No hay personas registradas -'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Load cajas for destination selection
|
|
||||||
DB.map('cajas', (data, key) => {
|
|
||||||
function addCajaOption(cajaData, cajaKey) {
|
|
||||||
if (cajaKey === cajaId) return; // Don't show current caja
|
|
||||||
var select = document.getElementById(field_caja_destino);
|
|
||||||
if (!select) return;
|
|
||||||
var option = document.createElement('option');
|
|
||||||
option.value = cajaKey;
|
|
||||||
option.textContent = cajaData.Nombre || cajaKey;
|
|
||||||
select.appendChild(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
TS_decrypt(
|
|
||||||
data,
|
|
||||||
SECRET,
|
|
||||||
(cajaData, wasEncrypted) => {
|
|
||||||
addCajaOption(cajaData, key);
|
|
||||||
},
|
|
||||||
'cajas',
|
|
||||||
key
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
addCajaOption(data, key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show/hide destination caja based on transaction type
|
|
||||||
document.getElementById(field_tipo).addEventListener('change', function () {
|
|
||||||
var tipo = this.value;
|
|
||||||
var divDestino = document.getElementById(div_caja_destino);
|
|
||||||
if (tipo === 'Transferencia') {
|
|
||||||
divDestino.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
divDestino.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Photo upload handler (click image to upload)
|
|
||||||
document.getElementById(render_foto).onclick = () => {
|
|
||||||
document.getElementById(field_foto).click();
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById(field_foto).addEventListener('change', function (e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function (ev) {
|
|
||||||
const url = ev.target.result;
|
|
||||||
document.getElementById(render_foto).src = url;
|
|
||||||
resized = url;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById(btn_guardar).onclick = () => {
|
|
||||||
var guardarBtn = document.getElementById(btn_guardar);
|
|
||||||
if (guardarBtn.disabled) return;
|
|
||||||
|
|
||||||
var tipo = document.getElementById(field_tipo).value;
|
|
||||||
var monto = parseFloat(document.getElementById(field_monto).value);
|
|
||||||
var personaId = document.getElementById(field_persona).value;
|
|
||||||
var fecha = document.getElementById(field_fecha).value;
|
|
||||||
var notas = document.getElementById(field_notas).value;
|
|
||||||
var cajaDestinoId = document.getElementById(field_caja_destino).value;
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
if (!tipo) {
|
|
||||||
alert('Por favor selecciona el tipo de movimiento');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!monto || monto <= 0) {
|
|
||||||
alert('Por favor ingresa un monto válido');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!personaId) {
|
|
||||||
alert('Por favor selecciona una persona');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!fecha) {
|
|
||||||
alert('Por favor selecciona una fecha');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate destination caja for transfers
|
|
||||||
if (tipo === 'Transferencia') {
|
|
||||||
if (!cajaDestinoId) {
|
|
||||||
alert('Por favor selecciona la caja destino para la transferencia');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (cajaDestinoId === cajaId) {
|
|
||||||
alert('No puedes transferir a la misma caja');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate photo for expenses
|
|
||||||
if (tipo === 'Gasto' && !resized) {
|
|
||||||
alert('La foto del ticket es obligatoria para gastos');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
guardarBtn.disabled = true;
|
|
||||||
guardarBtn.style.opacity = '0.5';
|
|
||||||
|
|
||||||
var movimientoId = safeuuid('');
|
|
||||||
var fechaISO = new Date(fecha).toISOString();
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
Caja: cajaId,
|
|
||||||
Fecha: fechaISO,
|
|
||||||
Tipo: tipo,
|
|
||||||
Monto: monto,
|
|
||||||
Persona: personaId,
|
|
||||||
Notas: notas,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add destination caja for transfers
|
|
||||||
if (tipo === 'Transferencia') {
|
|
||||||
data.CajaDestino = cajaDestinoId;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('actionStatus').style.display = 'block';
|
|
||||||
DB.put('cajas_movimientos', movimientoId, data)
|
|
||||||
.then(() => {
|
|
||||||
// Save photo attachment if present
|
|
||||||
var attachPromise = Promise.resolve(true);
|
|
||||||
if (resized && resized.indexOf('data:') === 0) {
|
|
||||||
attachPromise = DB.putAttachment(
|
|
||||||
'cajas_movimientos',
|
|
||||||
movimientoId,
|
|
||||||
'foto',
|
|
||||||
resized,
|
|
||||||
'image/png'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachPromise
|
|
||||||
.then(() => {
|
|
||||||
// Update source caja balance
|
|
||||||
return updateCajaBalance(cajaId, tipo, monto);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// If transfer, update destination caja balance
|
|
||||||
if (tipo === 'Transferencia' && cajaDestinoId) {
|
|
||||||
return updateCajaBalance(cajaDestinoId, 'Ingreso', monto);
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
toastr.success('Movimiento guardado!');
|
|
||||||
setTimeout(() => {
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
setUrlHash('cajas,' + cajaId);
|
|
||||||
}, SAVE_WAIT);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn('Error saving:', e);
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
guardarBtn.disabled = false;
|
|
||||||
guardarBtn.style.opacity = '1';
|
|
||||||
toastr.error('Error al guardar el movimiento');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn('DB.put error', e);
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
guardarBtn.disabled = false;
|
|
||||||
guardarBtn.style.opacity = '1';
|
|
||||||
toastr.error('Error al guardar el movimiento');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById(btn_cancelar).onclick = () => {
|
|
||||||
setUrlHash('cajas,' + cajaId);
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateCajaBalance(cajaId, tipo, monto) {
|
|
||||||
return DB.get('cajas', cajaId).then((caja) => {
|
|
||||||
function updateBalance(cajaData) {
|
|
||||||
var currentBalance = parseFloat(cajaData.Balance || 0);
|
|
||||||
var newBalance = currentBalance;
|
|
||||||
|
|
||||||
if (tipo === 'Ingreso') {
|
|
||||||
newBalance = currentBalance + monto;
|
|
||||||
} else if (tipo === 'Gasto' || tipo === 'Transferencia') {
|
|
||||||
// For transfers, this updates the source caja (deduct amount)
|
|
||||||
newBalance = currentBalance - monto;
|
|
||||||
}
|
|
||||||
|
|
||||||
cajaData.Balance = fixfloat(newBalance);
|
|
||||||
return DB.put('cajas', cajaId, cajaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof caja === 'string') {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
TS_decrypt(
|
|
||||||
caja,
|
|
||||||
SECRET,
|
|
||||||
(cajaData, wasEncrypted) => {
|
|
||||||
updateBalance(cajaData).then(resolve).catch(reject);
|
|
||||||
},
|
|
||||||
'cajas',
|
|
||||||
cajaId
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return updateBalance(caja || {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// View/edit a cash register (caja)
|
|
||||||
edit: function (mid) {
|
|
||||||
if (!checkRole('cajas')) {
|
|
||||||
setUrlHash('cajas');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for special routes
|
|
||||||
var parts = location.hash.split("?")[0].split(',');
|
|
||||||
if (parts[2] === 'movimientos' && parts[3] === '_nuevo') {
|
|
||||||
PAGES.cajas.nuevo_movimiento(parts[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (parts[2] === 'movimiento' && parts[3]) {
|
|
||||||
PAGES.cajas.movimiento(parts[1], parts[3]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nameh1 = safeuuid();
|
|
||||||
var field_nombre = safeuuid();
|
|
||||||
var field_balance = safeuuid();
|
|
||||||
var field_notas = safeuuid();
|
|
||||||
var btn_guardar = safeuuid();
|
|
||||||
var btn_borrar = safeuuid();
|
|
||||||
var btn_nuevo_movimiento = safeuuid();
|
|
||||||
var movimientos_container = safeuuid();
|
|
||||||
|
|
||||||
var isMonederos = mid === 'monederos';
|
|
||||||
|
|
||||||
container.innerHTML = html`
|
|
||||||
<h1>${isMonederos ? 'Monederos' : 'Caja'} <code id="${nameh1}"></code></h1>
|
|
||||||
${isMonederos ? '' : BuildQR('cajas,' + mid, 'Esta Caja')}
|
|
||||||
<fieldset>
|
|
||||||
<label>
|
|
||||||
Nombre de la Caja<br>
|
|
||||||
<input type="text" id="${field_nombre}" ${isMonederos ? 'disabled' : ''}><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Balance Actual (€)<br>
|
|
||||||
<input type="number" step="0.01" id="${field_balance}" disabled
|
|
||||||
style="font-size: 28px; font-weight: bold; color: #1976d2;"><br>
|
|
||||||
<small>Se actualiza automáticamente con los movimientos</small><br><br>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Notas<br>
|
|
||||||
<textarea id="${field_notas}" ${isMonederos ? 'disabled' : ''} rows="3"></textarea><br><br>
|
|
||||||
</label>
|
|
||||||
<hr>
|
|
||||||
${
|
|
||||||
isMonederos
|
|
||||||
? ''
|
|
||||||
: html`
|
|
||||||
<button class="btn5" id="${btn_guardar}">Guardar</button>
|
|
||||||
<button class="rojo" id="${btn_borrar}">Borrar Caja</button>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<h2>Movimientos de ${isMonederos ? 'Monederos' : 'esta Caja'}</h2>
|
|
||||||
${
|
|
||||||
isMonederos
|
|
||||||
? html`<p><small>Aquí se muestran todas las transacciones de los monederos (módulo Pagos)</small></p>`
|
|
||||||
: html`<button class="btn5" id="${btn_nuevo_movimiento}">+ Nuevo Movimiento</button>`
|
|
||||||
}
|
|
||||||
<div id="${movimientos_container}"></div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Load caja data
|
|
||||||
if (!isMonederos) {
|
|
||||||
DB.get('cajas', mid).then((data) => {
|
|
||||||
function load_data(data) {
|
|
||||||
document.getElementById(nameh1).innerText = mid;
|
|
||||||
document.getElementById(field_nombre).value = data['Nombre'] || '';
|
|
||||||
document.getElementById(field_balance).value = data['Balance'] || 0;
|
|
||||||
document.getElementById(field_notas).value = data['Notas'] || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
TS_decrypt(
|
|
||||||
data,
|
|
||||||
SECRET,
|
|
||||||
(data, wasEncrypted) => {
|
|
||||||
load_data(data);
|
|
||||||
},
|
|
||||||
'cajas',
|
|
||||||
mid
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
load_data(data || {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById(btn_guardar).onclick = () => {
|
|
||||||
var guardarBtn = document.getElementById(btn_guardar);
|
|
||||||
if (guardarBtn.disabled) return;
|
|
||||||
|
|
||||||
guardarBtn.disabled = true;
|
|
||||||
guardarBtn.style.opacity = '0.5';
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
Nombre: document.getElementById(field_nombre).value,
|
|
||||||
Balance: parseFloat(document.getElementById(field_balance).value) || 0,
|
|
||||||
Notas: document.getElementById(field_notas).value,
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('actionStatus').style.display = 'block';
|
|
||||||
DB.put('cajas', mid, data)
|
|
||||||
.then(() => {
|
|
||||||
toastr.success('Guardado!');
|
|
||||||
setTimeout(() => {
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
setUrlHash('cajas');
|
|
||||||
}, SAVE_WAIT);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.warn('DB.put error', e);
|
|
||||||
guardarBtn.disabled = false;
|
|
||||||
guardarBtn.style.opacity = '1';
|
|
||||||
document.getElementById('actionStatus').style.display = 'none';
|
|
||||||
toastr.error('Error al guardar la caja');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById(btn_borrar).onclick = () => {
|
|
||||||
if (confirm('¿Quieres borrar esta caja? Los movimientos no se borrarán.')) {
|
|
||||||
DB.del('cajas', mid).then(() => {
|
|
||||||
toastr.error('Caja borrada!');
|
|
||||||
setTimeout(() => {
|
|
||||||
setUrlHash('cajas');
|
|
||||||
}, SAVE_WAIT);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById(btn_nuevo_movimiento).onclick = () => {
|
|
||||||
setUrlHash('cajas,' + mid + ',_nuevo');
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Monederos - show aggregated wallet data
|
|
||||||
document.getElementById(nameh1).innerText = 'Monederos';
|
|
||||||
document.getElementById(field_nombre).value = 'Monederos (Tarjetas)';
|
|
||||||
|
|
||||||
// Calculate total balance from all personas
|
|
||||||
var totalBalance = 0;
|
|
||||||
Object.values(SC_Personas).forEach((persona) => {
|
|
||||||
totalBalance += parseFloat(persona.Monedero_Balance || 0);
|
|
||||||
});
|
|
||||||
document.getElementById(field_balance).value = fixfloat(totalBalance);
|
|
||||||
document.getElementById(field_notas).value = 'Movimientos de todos los monederos del sistema';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load movements for this caja (or all pagos for monederos)
|
|
||||||
if (isMonederos) {
|
|
||||||
// Show pagos transactions
|
|
||||||
const config = [
|
|
||||||
{
|
|
||||||
key: 'Fecha',
|
|
||||||
label: 'Fecha',
|
|
||||||
type: 'template',
|
|
||||||
template: (data, element) => {
|
|
||||||
var fecha = data.Fecha || '';
|
|
||||||
if (fecha) {
|
|
||||||
var d = new Date(fecha);
|
|
||||||
element.innerText = d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{ key: 'Tipo', label: 'Tipo', type: 'text', default: '' },
|
|
||||||
{
|
|
||||||
key: 'Monto',
|
|
||||||
label: 'Monto',
|
|
||||||
type: 'template',
|
|
||||||
template: (data, element) => {
|
|
||||||
var tipo = data.Tipo || '';
|
|
||||||
var monto = parseFloat(data.Monto || 0);
|
|
||||||
var sign = tipo === 'Ingreso' ? '+' : '-';
|
|
||||||
var color = tipo === 'Ingreso' ? 'green' : 'red';
|
|
||||||
element.innerHTML = html`<span style="color: ${color}; font-weight: bold;">${sign}${monto.toFixed(2)}€</span>`;
|
|
||||||
},
|
|
||||||
default: '0.00€',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Persona',
|
|
||||||
label: 'Monedero',
|
|
||||||
type: 'persona-nombre',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{ key: 'Metodo', label: 'Método', type: 'text', default: '' },
|
|
||||||
{ key: 'Notas', label: 'Notas', type: 'text', default: '' },
|
|
||||||
];
|
|
||||||
|
|
||||||
TS_IndexElement(
|
|
||||||
'pagos',
|
|
||||||
config,
|
|
||||||
'pagos',
|
|
||||||
document.getElementById(movimientos_container),
|
|
||||||
function (data, new_tr) {
|
|
||||||
new_tr.onclick = () => {
|
|
||||||
setUrlHash('pagos,' + data._key);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Show cajas_movimientos for this specific caja
|
|
||||||
const config = [
|
|
||||||
{
|
|
||||||
key: 'Fecha',
|
|
||||||
label: 'Fecha',
|
|
||||||
type: 'template',
|
|
||||||
template: (data, element) => {
|
|
||||||
var fecha = data.Fecha || '';
|
|
||||||
if (fecha) {
|
|
||||||
var d = new Date(fecha);
|
|
||||||
element.innerText = d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{ key: 'Tipo', label: 'Tipo', type: 'text', default: '' },
|
|
||||||
{
|
|
||||||
key: 'Monto',
|
|
||||||
label: 'Monto',
|
|
||||||
type: 'template',
|
|
||||||
template: (data, element) => {
|
|
||||||
var tipo = data.Tipo || '';
|
|
||||||
var monto = parseFloat(data.Monto || 0);
|
|
||||||
var sign = tipo === 'Ingreso' ? '+' : tipo === 'Gasto' ? '-' : '<->';
|
|
||||||
var color = tipo === 'Ingreso' ? 'green' : tipo === 'Gasto' ? 'red' : 'blue';
|
|
||||||
element.innerHTML = html`<span style="color: ${color}; font-weight: bold;">${sign}${monto.toFixed(2)}€</span>`;
|
|
||||||
},
|
|
||||||
default: '0.00€',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'Persona',
|
|
||||||
label: 'Persona',
|
|
||||||
type: 'persona-nombre',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{ key: 'Notas', label: 'Notas', type: 'text', default: '' },
|
|
||||||
];
|
|
||||||
|
|
||||||
TS_IndexElement(
|
|
||||||
'cajas,' + mid + ',movimiento',
|
|
||||||
config,
|
|
||||||
'cajas_movimientos',
|
|
||||||
document.getElementById(movimientos_container),
|
|
||||||
function (data, new_tr) {
|
|
||||||
new_tr.onclick = () => {
|
|
||||||
setUrlHash('cajas,' + mid + ',movimiento,' + data._key);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
function (data) {
|
|
||||||
// Filter: only show movements for this caja (return true to HIDE the row)
|
|
||||||
return data.Caja !== mid;
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// List all cash registers
|
|
||||||
index: function () {
|
|
||||||
if (!checkRole('cajas')) {
|
|
||||||
setUrlHash('index');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var btn_new = safeuuid();
|
|
||||||
var btn_monederos = safeuuid();
|
|
||||||
var tableContainer = safeuuid();
|
|
||||||
|
|
||||||
container.innerHTML = html`
|
|
||||||
<h1>Cajas</h1>
|
|
||||||
<button class="btn5" id="${btn_monederos}" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; margin-bottom: 10px;">
|
|
||||||
Ver Monederos
|
|
||||||
</button>
|
|
||||||
<button id="${btn_new}">Nueva Caja</button>
|
|
||||||
<div id="${tableContainer}"></div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const config = [
|
|
||||||
{ key: 'Nombre', label: 'Nombre', type: 'text', default: '' },
|
|
||||||
{
|
|
||||||
key: 'Balance',
|
|
||||||
label: 'Balance',
|
|
||||||
type: 'template',
|
|
||||||
template: (data, element) => {
|
|
||||||
var balance = parseFloat(data.Balance || 0);
|
|
||||||
var color = balance >= 0 ? 'green' : 'red';
|
|
||||||
element.innerHTML = html`<span style="color: ${color}; font-weight: bold; font-size: 18px;">${balance.toFixed(2)}€</span>`;
|
|
||||||
},
|
|
||||||
default: '0.00€',
|
|
||||||
},
|
|
||||||
{ key: 'Notas', label: 'Notas', type: 'text', default: '' },
|
|
||||||
];
|
|
||||||
|
|
||||||
TS_IndexElement(
|
|
||||||
'cajas',
|
|
||||||
config,
|
|
||||||
'cajas',
|
|
||||||
document.getElementById(tableContainer),
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
document.getElementById(btn_monederos).onclick = () => {
|
|
||||||
setUrlHash('cajas,monederos');
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!checkRole('cajas:edit')) {
|
|
||||||
document.getElementById(btn_new).style.display = 'none';
|
|
||||||
} else {
|
|
||||||
document.getElementById(btn_new).onclick = () => {
|
|
||||||
setUrlHash('cajas,' + safeuuid(''));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -11,6 +11,8 @@ PAGES.index = {
|
|||||||
</h2>
|
</h2>
|
||||||
<em>Utiliza el menú superior para abrir un modulo</em>
|
<em>Utiliza el menú superior para abrir un modulo</em>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
<button class="btn1" onclick="ActualizarProgramaTeleSec()">Actualizar programa</button>
|
||||||
|
<br /><br />
|
||||||
<button class="btn1" onclick="LogOutTeleSec()">Cerrar sesión</button>
|
<button class="btn1" onclick="LogOutTeleSec()">Cerrar sesión</button>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|||||||
357
src/page/panel.js
Normal file
357
src/page/panel.js
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
PERMS['panel'] = 'Panel';
|
||||||
|
PAGES.panel = {
|
||||||
|
navcss: 'btn2',
|
||||||
|
icon: 'static/appico/calendar.png',
|
||||||
|
AccessControl: true,
|
||||||
|
Title: 'Panel',
|
||||||
|
|
||||||
|
index: function () {
|
||||||
|
if (!checkRole('panel')) {
|
||||||
|
setUrlHash('index');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentId = safeuuid();
|
||||||
|
container.innerHTML = html`
|
||||||
|
<h1>Panel de acogida del día</h1>
|
||||||
|
<p>Quiz de aprendizaje con retroalimentación para empezar la jornada.</p>
|
||||||
|
<div id="${contentId}">Cargando datos del día...</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
PAGES.panel
|
||||||
|
.__buildDailyContext()
|
||||||
|
.then((ctx) => {
|
||||||
|
var questions = PAGES.panel.__buildQuestions(ctx);
|
||||||
|
PAGES.panel.__renderQuiz(contentId, ctx, questions);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn('Panel load error', e);
|
||||||
|
document.getElementById(contentId).innerHTML =
|
||||||
|
'<b>No se pudo cargar el Panel ahora mismo.</b>';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
__decryptIfNeeded: function (table, id, raw) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (typeof raw !== 'string') {
|
||||||
|
resolve(raw || {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TS_decrypt(
|
||||||
|
raw,
|
||||||
|
SECRET,
|
||||||
|
(data) => {
|
||||||
|
resolve(data || {});
|
||||||
|
},
|
||||||
|
table,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
__getTodayComedor: async function () {
|
||||||
|
var rows = await DB.list('comedor');
|
||||||
|
var today = CurrentISODate();
|
||||||
|
var items = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
var row = rows[i];
|
||||||
|
var data = await PAGES.panel.__decryptIfNeeded('comedor', row.id, row.data);
|
||||||
|
if ((data.Fecha || '') === today) {
|
||||||
|
items.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return {
|
||||||
|
Primero: '',
|
||||||
|
Segundo: '',
|
||||||
|
Postre: '',
|
||||||
|
Tipo: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
items.sort((a, b) => {
|
||||||
|
var ta = (a.Tipo || '').toLowerCase();
|
||||||
|
var tb = (b.Tipo || '').toLowerCase();
|
||||||
|
return ta < tb ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return items[0] || {};
|
||||||
|
},
|
||||||
|
|
||||||
|
__getNotaById: async function (id) {
|
||||||
|
var data = await DB.get('notas', id);
|
||||||
|
if (!data) return {};
|
||||||
|
return await PAGES.panel.__decryptIfNeeded('notas', id, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
__getDiarioHoy: async function () {
|
||||||
|
var did = 'diario-' + CurrentISODate();
|
||||||
|
var data = await DB.get('aulas_informes', did);
|
||||||
|
if (!data) return {};
|
||||||
|
return await PAGES.panel.__decryptIfNeeded('aulas_informes', did, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
__extractFirstLine: function (text) {
|
||||||
|
var lines = String(text || '')
|
||||||
|
.split('\n')
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.filter((x) => x !== '');
|
||||||
|
return lines[0] || '';
|
||||||
|
},
|
||||||
|
|
||||||
|
__buildDailyContext: async function () {
|
||||||
|
var comedor = await PAGES.panel.__getTodayComedor();
|
||||||
|
var tareas = await PAGES.panel.__getNotaById('tareas');
|
||||||
|
var diario = await PAGES.panel.__getDiarioHoy();
|
||||||
|
|
||||||
|
var planHoy =
|
||||||
|
PAGES.panel.__extractFirstLine(tareas.Contenido) ||
|
||||||
|
PAGES.panel.__extractFirstLine(diario.Contenido) ||
|
||||||
|
'Revisar rutinas, colaborar y participar en las actividades del aula.';
|
||||||
|
|
||||||
|
return {
|
||||||
|
fecha: CurrentISODate(),
|
||||||
|
comedor: {
|
||||||
|
primero: (comedor.Primero || '').trim(),
|
||||||
|
segundo: (comedor.Segundo || '').trim(),
|
||||||
|
postre: (comedor.Postre || '').trim(),
|
||||||
|
tipo: (comedor.Tipo || '').trim(),
|
||||||
|
},
|
||||||
|
planHoy: planHoy,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
__pickDistractors: function (correct, pool, count) {
|
||||||
|
var options = [];
|
||||||
|
var seen = {};
|
||||||
|
var cleanCorrect = (correct || '').trim();
|
||||||
|
|
||||||
|
pool.forEach((item) => {
|
||||||
|
var text = String(item || '').trim();
|
||||||
|
if (text === '' || text === cleanCorrect || seen[text]) return;
|
||||||
|
seen[text] = true;
|
||||||
|
options.push(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
var out = [];
|
||||||
|
for (var i = 0; i < options.length && out.length < count; i++) {
|
||||||
|
out.push(options[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (out.length < count) {
|
||||||
|
out.push('No aplica hoy');
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
|
||||||
|
__shuffle: function (arr) {
|
||||||
|
var copy = arr.slice();
|
||||||
|
for (var i = copy.length - 1; i > 0; i--) {
|
||||||
|
var j = Math.floor(Math.random() * (i + 1));
|
||||||
|
var tmp = copy[i];
|
||||||
|
copy[i] = copy[j];
|
||||||
|
copy[j] = tmp;
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
},
|
||||||
|
|
||||||
|
__buildQuestions: function (ctx) {
|
||||||
|
var c = ctx.comedor || {};
|
||||||
|
var poolComedor = [c.primero, c.segundo, c.postre, 'No hay menú registrado'];
|
||||||
|
var questions = [];
|
||||||
|
|
||||||
|
if (c.primero) {
|
||||||
|
var opts1 = [c.primero].concat(PAGES.panel.__pickDistractors(c.primero, poolComedor, 3));
|
||||||
|
questions.push({
|
||||||
|
id: 'q-comida-primero',
|
||||||
|
text: '¿Qué hay de comer hoy de primero?',
|
||||||
|
options: PAGES.panel.__shuffle(opts1),
|
||||||
|
correct: c.primero,
|
||||||
|
ok: '¡Correcto! Ya sabes el primer plato de hoy.',
|
||||||
|
bad: 'Repasa el menú del día para anticipar la comida.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.segundo) {
|
||||||
|
var opts2 = [c.segundo].concat(PAGES.panel.__pickDistractors(c.segundo, poolComedor, 3));
|
||||||
|
questions.push({
|
||||||
|
id: 'q-comida-segundo',
|
||||||
|
text: '¿Y de segundo, qué toca?',
|
||||||
|
options: PAGES.panel.__shuffle(opts2),
|
||||||
|
correct: c.segundo,
|
||||||
|
ok: '¡Bien! Segundo identificado.',
|
||||||
|
bad: 'Casi. Mira el módulo Comedor para recordar el segundo plato.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.postre) {
|
||||||
|
var opts3 = [c.postre].concat(PAGES.panel.__pickDistractors(c.postre, poolComedor, 3));
|
||||||
|
questions.push({
|
||||||
|
id: 'q-comida-postre',
|
||||||
|
text: '¿Cuál es el postre de hoy?',
|
||||||
|
options: PAGES.panel.__shuffle(opts3),
|
||||||
|
correct: c.postre,
|
||||||
|
ok: '¡Perfecto! Postre acertado.',
|
||||||
|
bad: 'No pasa nada, revisa el postre en el menú diario.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var plan = ctx.planHoy || '';
|
||||||
|
var distractPlan = [
|
||||||
|
'No hay actividades planificadas hoy',
|
||||||
|
'Solo descanso todo el día',
|
||||||
|
'Actividad libre sin objetivos',
|
||||||
|
];
|
||||||
|
var planOptions = [plan].concat(PAGES.panel.__pickDistractors(plan, distractPlan, 3));
|
||||||
|
questions.push({
|
||||||
|
id: 'q-plan-hoy',
|
||||||
|
text: '¿Qué vamos a hacer hoy?',
|
||||||
|
options: PAGES.panel.__shuffle(planOptions),
|
||||||
|
correct: plan,
|
||||||
|
ok: '¡Muy bien! Tienes claro el plan del día.',
|
||||||
|
bad: 'Revisa las tareas/diario para conocer el plan del día.',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (questions.length === 0) {
|
||||||
|
questions.push({
|
||||||
|
id: 'q-fallback',
|
||||||
|
text: 'No hay menú cargado. ¿Qué acción es correcta ahora?',
|
||||||
|
options: [
|
||||||
|
'Consultar el módulo Comedor y las Notas del día',
|
||||||
|
'Ignorar la planificación diaria',
|
||||||
|
'Esperar sin revisar información',
|
||||||
|
'Saltar la acogida',
|
||||||
|
],
|
||||||
|
correct: 'Consultar el módulo Comedor y las Notas del día',
|
||||||
|
ok: 'Correcto. Ese es el siguiente paso recomendado.',
|
||||||
|
bad: 'La acogida mejora si revisamos menú y planificación diaria.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return questions;
|
||||||
|
},
|
||||||
|
|
||||||
|
__renderQuiz: function (contentId, ctx, questions) {
|
||||||
|
var target = document.getElementById(contentId);
|
||||||
|
var state = {
|
||||||
|
idx: 0,
|
||||||
|
answers: {},
|
||||||
|
score: 0,
|
||||||
|
feedback: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function saveResult() {
|
||||||
|
var rid = CurrentISOTime() + '-' + safeuuid('');
|
||||||
|
var payload = {
|
||||||
|
Fecha: ctx.fecha,
|
||||||
|
Persona: SUB_LOGGED_IN_ID || '',
|
||||||
|
Aciertos: state.score,
|
||||||
|
Total: questions.length,
|
||||||
|
Respuestas: state.answers,
|
||||||
|
};
|
||||||
|
DB.put('panel_respuestas', rid, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCurrent() {
|
||||||
|
var q = questions[state.idx];
|
||||||
|
if (!q) return;
|
||||||
|
|
||||||
|
var selected = state.answers[q.id] || '';
|
||||||
|
var optionsHtml = q.options
|
||||||
|
.map((option, i) => {
|
||||||
|
var oid = safeuuid();
|
||||||
|
var checked = selected === option ? 'checked' : '';
|
||||||
|
return `
|
||||||
|
<label for="${oid}" style="display:block; margin: 8px 0; padding: 8px; border: 1px solid #ccc; border-radius: 6px; cursor:pointer;">
|
||||||
|
<input id="${oid}" type="radio" name="panel-question" value="${option.replace(/"/g, '"')}" ${checked} />
|
||||||
|
${option}
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
target.innerHTML = html`
|
||||||
|
<fieldset style="max-width: 800px;">
|
||||||
|
<legend>Pregunta ${state.idx + 1} de ${questions.length}</legend>
|
||||||
|
<div style="margin-bottom: 10px;"><b>${q.text}</b></div>
|
||||||
|
<small>
|
||||||
|
Menú hoy: ${ctx.comedor.primero || '—'} / ${ctx.comedor.segundo || '—'} /
|
||||||
|
${ctx.comedor.postre || '—'}
|
||||||
|
</small>
|
||||||
|
<div style="margin-top: 10px;">${optionsHtml}</div>
|
||||||
|
<div id="panel-feedback" style="margin-top: 12px;"><i>${state.feedback || ''}</i></div>
|
||||||
|
<div style="margin-top: 12px; display:flex; gap:8px;">
|
||||||
|
<button class="btn5" id="panel-next">Comprobar y continuar</button>
|
||||||
|
<button id="panel-cancel">Salir</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('panel-cancel').onclick = () => setUrlHash('index');
|
||||||
|
document.getElementById('panel-next').onclick = () => {
|
||||||
|
var checked = document.querySelector('input[name="panel-question"]:checked');
|
||||||
|
if (!checked) {
|
||||||
|
state.feedback = 'Selecciona una opción antes de continuar.';
|
||||||
|
renderCurrent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var answer = checked.value;
|
||||||
|
state.answers[q.id] = answer;
|
||||||
|
|
||||||
|
var wasCorrect = answer === q.correct;
|
||||||
|
if (wasCorrect) {
|
||||||
|
state.score++;
|
||||||
|
state.feedback = '✅ ' + q.ok;
|
||||||
|
} else {
|
||||||
|
state.feedback = '❌ ' + q.bad + ' Respuesta esperada: ' + q.correct;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.idx < questions.length - 1) {
|
||||||
|
state.idx++;
|
||||||
|
setTimeout(() => {
|
||||||
|
state.feedback = '';
|
||||||
|
renderCurrent();
|
||||||
|
}, 350);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveResult();
|
||||||
|
renderFinal();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFinal() {
|
||||||
|
var total = questions.length;
|
||||||
|
var ratio = total > 0 ? Math.round((state.score / total) * 100) : 0;
|
||||||
|
var msg = 'Buen trabajo. Sigue reforzando la acogida diaria.';
|
||||||
|
if (ratio >= 80) msg = 'Excelente acogida: gran comprensión del día.';
|
||||||
|
else if (ratio >= 50) msg = 'Buen avance. Revisa comedor/tareas para reforzar.';
|
||||||
|
|
||||||
|
target.innerHTML = html`
|
||||||
|
<fieldset style="max-width: 800px;">
|
||||||
|
<legend>Resultado del Panel</legend>
|
||||||
|
<h2>${state.score} / ${total} aciertos (${ratio}%)</h2>
|
||||||
|
<p>${msg}</p>
|
||||||
|
<p><b>Plan de hoy:</b> ${ctx.planHoy}</p>
|
||||||
|
<button class="btn5" id="panel-repeat">Repetir quiz</button>
|
||||||
|
<button id="panel-home">Volver al inicio</button>
|
||||||
|
</fieldset>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('panel-repeat').onclick = () => {
|
||||||
|
state.idx = 0;
|
||||||
|
state.answers = {};
|
||||||
|
state.score = 0;
|
||||||
|
state.feedback = '';
|
||||||
|
renderCurrent();
|
||||||
|
};
|
||||||
|
document.getElementById('panel-home').onclick = () => setUrlHash('index');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCurrent();
|
||||||
|
},
|
||||||
|
};
|
||||||
166
src/pwa.js
166
src/pwa.js
@@ -1,5 +1,130 @@
|
|||||||
let newWorker;
|
let newWorker;
|
||||||
|
const APP_VERSION = '%%VERSIONCO%%';
|
||||||
|
const VERSION_CHECK_INTERVAL_MS = 10 * 60 * 1000;
|
||||||
|
let lastVersionCheckTs = 0;
|
||||||
|
let updatePromptShown = false;
|
||||||
|
|
||||||
|
function sendCouchUrlPrefixToServiceWorker(registration) {
|
||||||
|
if (!registration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const couchUrlPrefix = (localStorage.getItem('TELESEC_COUCH_URL') || '').trim();
|
||||||
|
const message = {
|
||||||
|
type: 'SET_COUCH_URL_PREFIX',
|
||||||
|
url: couchUrlPrefix
|
||||||
|
};
|
||||||
|
|
||||||
|
if (registration.active) {
|
||||||
|
registration.active.postMessage(message);
|
||||||
|
}
|
||||||
|
if (registration.waiting) {
|
||||||
|
registration.waiting.postMessage(message);
|
||||||
|
}
|
||||||
|
if (registration.installing) {
|
||||||
|
registration.installing.postMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAppVersion(force = false) {
|
||||||
|
const now = Date.now();
|
||||||
|
if (!force && now - lastVersionCheckTs < VERSION_CHECK_INTERVAL_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!navigator.onLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastVersionCheckTs = now;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/version.json?t=${Date.now()}`, {
|
||||||
|
cache: 'no-cache'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (!data || !data.version) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.version !== APP_VERSION) {
|
||||||
|
const registration = await navigator.serviceWorker.getRegistration();
|
||||||
|
if (registration) {
|
||||||
|
await registration.update();
|
||||||
|
}
|
||||||
|
if (!updatePromptShown) {
|
||||||
|
showUpdateBar();
|
||||||
|
updatePromptShown = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatePromptShown = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('No se pudo comprobar la versión remota:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ActualizarProgramaTeleSec() {
|
||||||
|
if (!confirm('Se borrará la caché local del programa y se recargará la aplicación. ¿Continuar?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cacheCleared = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
const registration = await navigator.serviceWorker.getRegistration();
|
||||||
|
if (registration) {
|
||||||
|
await registration.update();
|
||||||
|
const sendSkipWaiting = (worker) => {
|
||||||
|
worker.postMessage({ type: 'SKIP_WAITING' });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (registration.waiting) {
|
||||||
|
sendSkipWaiting(registration.waiting);
|
||||||
|
} else if (registration.installing) {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
const onStateChange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
installingWorker.removeEventListener('statechange', onStateChange);
|
||||||
|
sendSkipWaiting(installingWorker);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
installingWorker.addEventListener('statechange', onStateChange);
|
||||||
|
onStateChange();
|
||||||
|
setTimeout(resolve, 2500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('caches' in window) {
|
||||||
|
const cacheNames = await caches.keys();
|
||||||
|
await Promise.all(cacheNames.map((cacheName) => caches.delete(cacheName)));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
cacheCleared = false;
|
||||||
|
console.error('No se pudo limpiar la caché completamente:', error);
|
||||||
|
if (typeof toastr !== 'undefined') {
|
||||||
|
toastr.error('No se pudo limpiar toda la caché. Recargando igualmente...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheCleared && typeof toastr !== 'undefined') {
|
||||||
|
toastr.success('Caché limpiada. Recargando aplicación...');
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload();
|
||||||
|
}, 700);
|
||||||
|
}
|
||||||
function showUpdateBar() {
|
function showUpdateBar() {
|
||||||
let snackbar = document.getElementById('snackbar');
|
let snackbar = document.getElementById('snackbar');
|
||||||
snackbar.className = 'show';
|
snackbar.className = 'show';
|
||||||
@@ -8,30 +133,41 @@ function showUpdateBar() {
|
|||||||
// The click event on the pop up notification
|
// The click event on the pop up notification
|
||||||
document.getElementById('reload').addEventListener('click', function () {
|
document.getElementById('reload').addEventListener('click', function () {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
removeCache();
|
ActualizarProgramaTeleSec();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
newWorker.postMessage({ action: 'skipWaiting' });
|
if (newWorker) {
|
||||||
|
newWorker.postMessage({ type: 'SKIP_WAITING' });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('sw.js').then((reg) => {
|
const wireRegistration = (reg) => {
|
||||||
reg.addEventListener('updatefound', () => {
|
reg.addEventListener('updatefound', () => {
|
||||||
// A wild service worker has appeared in reg.installing!
|
|
||||||
newWorker = reg.installing;
|
newWorker = reg.installing;
|
||||||
|
|
||||||
newWorker.addEventListener('statechange', () => {
|
newWorker.addEventListener('statechange', () => {
|
||||||
// Has network.state changed?
|
|
||||||
switch (newWorker.state) {
|
switch (newWorker.state) {
|
||||||
case 'installed':
|
case 'installed':
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
// new update available
|
|
||||||
showUpdateBar();
|
showUpdateBar();
|
||||||
}
|
}
|
||||||
// No update available
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
navigator.serviceWorker.getRegistration().then(async (reg) => {
|
||||||
|
if (!reg) {
|
||||||
|
reg = await navigator.serviceWorker.register('sw.js');
|
||||||
|
} else {
|
||||||
|
await reg.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
wireRegistration(reg);
|
||||||
|
sendCouchUrlPrefixToServiceWorker(reg);
|
||||||
|
checkAppVersion(true);
|
||||||
|
setInterval(checkAppVersion, VERSION_CHECK_INTERVAL_MS);
|
||||||
});
|
});
|
||||||
|
|
||||||
let refreshing;
|
let refreshing;
|
||||||
@@ -40,4 +176,20 @@ if ('serviceWorker' in navigator) {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
refreshing = true;
|
refreshing = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
checkAppVersion();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('storage', (event) => {
|
||||||
|
if (event.key !== 'TELESEC_COUCH_URL') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.serviceWorker.getRegistration().then((registration) => {
|
||||||
|
sendCouchUrlPrefixToServiceWorker(registration);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/sw.js
32
src/sw.js
@@ -1,10 +1,29 @@
|
|||||||
var CACHE = 'telesec_%%VERSIONCO%%';
|
var CACHE = 'telesec_%%VERSIONCO%%';
|
||||||
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
|
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
|
||||||
|
|
||||||
|
let couchUrlPrefix = '';
|
||||||
|
|
||||||
|
function normalizePrefix(url) {
|
||||||
|
if (!url || typeof url !== 'string') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedUrl = url.trim();
|
||||||
|
if (!trimmedUrl) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmedUrl.replace(/\/+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
self.addEventListener('message', (event) => {
|
self.addEventListener('message', (event) => {
|
||||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.data && event.data.type === 'SET_COUCH_URL_PREFIX') {
|
||||||
|
couchUrlPrefix = normalizePrefix(event.data.url);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// workbox.routing.registerRoute(
|
// workbox.routing.registerRoute(
|
||||||
@@ -16,8 +35,17 @@ self.addEventListener('message', (event) => {
|
|||||||
|
|
||||||
// All but couchdb
|
// All but couchdb
|
||||||
workbox.routing.registerRoute(
|
workbox.routing.registerRoute(
|
||||||
({ url }) => !url.pathname.startsWith('/_couchdb/') && url.origin === self.location.origin,
|
({ request, url }) => {
|
||||||
new workbox.strategies.NetworkFirst({
|
const requestUrl = request && request.url ? request.url : url.href;
|
||||||
|
const normalizedRequestUrl = normalizePrefix(requestUrl);
|
||||||
|
|
||||||
|
if (couchUrlPrefix && normalizedRequestUrl.startsWith(couchUrlPrefix)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !url.pathname.startsWith('/_couchdb/') && url.origin === self.location.origin;
|
||||||
|
},
|
||||||
|
new workbox.strategies.CacheFirst({
|
||||||
cacheName: CACHE,
|
cacheName: CACHE,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
3
src/version.json
Normal file
3
src/version.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "%%VERSIONCO%%"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user