2203 lines
68 KiB
JavaScript
2203 lines
68 KiB
JavaScript
try {
|
|
navigator.wakeLock.request('screen');
|
|
} catch {
|
|
console.log('ScreenLock Failed');
|
|
}
|
|
|
|
// Configuración de precios del café (cargado desde DB)
|
|
window.PRECIOS_CAFE = {
|
|
servicio_base: 10,
|
|
leche_pequena: 15,
|
|
leche_grande: 25,
|
|
cafe: 25,
|
|
colacao: 25,
|
|
};
|
|
|
|
// Cargar precios desde la base de datos al iniciar
|
|
if (typeof DB !== 'undefined') {
|
|
DB.get('config', 'precios_cafe').then((raw) => {
|
|
TS_decrypt(raw, SECRET, (precios) => {
|
|
if (precios) {
|
|
Object.assign(window.PRECIOS_CAFE, precios);
|
|
console.log('Precios del café cargados:', window.PRECIOS_CAFE);
|
|
}
|
|
});
|
|
}).catch(() => {
|
|
console.log('Usando precios por defecto');
|
|
});
|
|
}
|
|
|
|
const debounce = (id, callback, wait, args) => {
|
|
// debounce with trailing callback
|
|
// First call runs immediately, then locks for 'wait' ms
|
|
// If called during lock, saves the latest args and runs once after lock
|
|
// If not called during lock, does nothing
|
|
if (!debounce.timers) {
|
|
debounce.timers = {};
|
|
debounce.args = {};
|
|
}
|
|
if (!debounce.timers[id]) {
|
|
// No lock, run immediately
|
|
// Do not schedule a trailing call unless further calls arrive
|
|
callback(...(Array.isArray(args) ? args : [args]));
|
|
debounce.args[id] = null;
|
|
debounce.timers[id] = setTimeout(() => {
|
|
if (debounce.args[id]) {
|
|
callback(...debounce.args[id]);
|
|
debounce.args[id] = null;
|
|
}
|
|
debounce.timers[id] = null;
|
|
}, wait);
|
|
} else {
|
|
// Lock active, save latest args for a single trailing invocation
|
|
debounce.args[id] = Array.isArray(args) ? args : [args];
|
|
}
|
|
return id;
|
|
};
|
|
|
|
function TS_CreateSearchOverlay(parentEl, options = {}) {
|
|
const overlayId = safeuuid();
|
|
const panelId = safeuuid();
|
|
const inputId = safeuuid();
|
|
const closeId = safeuuid();
|
|
const clearId = safeuuid();
|
|
|
|
const overlay = document.createElement('div');
|
|
overlay.id = overlayId;
|
|
overlay.style.display = 'none';
|
|
overlay.style.position = 'fixed';
|
|
overlay.style.inset = '0';
|
|
overlay.style.background = 'rgba(0, 0, 0, 0.5)';
|
|
overlay.style.zIndex = '9999';
|
|
overlay.style.alignItems = 'center';
|
|
overlay.style.justifyContent = 'center';
|
|
overlay.style.padding = '16px';
|
|
overlay.innerHTML = html`
|
|
<div
|
|
id="${panelId}"
|
|
style="background: white; padding: 12px; border-radius: 8px; width: min(520px, 100%); box-shadow: 0 10px 30px rgba(0,0,0,0.2);"
|
|
>
|
|
<div style="display: flex; gap: 6px; align-items: center;">
|
|
<input
|
|
type="text"
|
|
id="${inputId}"
|
|
placeholder="${options.placeholder || 'Buscar...'}"
|
|
style="flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"
|
|
/>
|
|
<button type="button" class="btn4" id="${clearId}">Limpiar</button>
|
|
<button type="button" class="btn4" id="${closeId}">Cerrar</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const targetParent = parentEl || document.body;
|
|
targetParent.appendChild(overlay);
|
|
|
|
const inputEl = overlay.querySelector('#' + inputId);
|
|
const closeEl = overlay.querySelector('#' + closeId);
|
|
const clearEl = overlay.querySelector('#' + clearId);
|
|
const panelEl = overlay.querySelector('#' + panelId);
|
|
|
|
function open() {
|
|
overlay.style.display = 'flex';
|
|
inputEl.focus();
|
|
inputEl.select();
|
|
}
|
|
|
|
function close() {
|
|
overlay.style.display = 'none';
|
|
}
|
|
|
|
function setValue(value) {
|
|
inputEl.value = value || '';
|
|
}
|
|
|
|
function getValue() {
|
|
return inputEl.value || '';
|
|
}
|
|
|
|
inputEl.addEventListener('input', () => {
|
|
if (typeof options.onInput === 'function') {
|
|
options.onInput(getValue());
|
|
}
|
|
});
|
|
|
|
closeEl.addEventListener('click', close);
|
|
clearEl.addEventListener('click', () => {
|
|
setValue('');
|
|
if (typeof options.onInput === 'function') {
|
|
options.onInput('');
|
|
}
|
|
inputEl.focus();
|
|
});
|
|
|
|
overlay.addEventListener('click', (event) => {
|
|
if (event.target === overlay && panelEl) {
|
|
close();
|
|
}
|
|
});
|
|
|
|
return {
|
|
open,
|
|
close,
|
|
setValue,
|
|
getValue,
|
|
inputEl,
|
|
overlayEl: overlay,
|
|
};
|
|
}
|
|
|
|
function TS_InitOverlaySearch(container, openBtnId, badgeId, options = {}) {
|
|
const debounceId = options.debounceId || safeuuid();
|
|
const wait = options.wait || 200;
|
|
let currentValue = '';
|
|
const badgeEl = badgeId ? document.getElementById(badgeId) : null;
|
|
const overlay = TS_CreateSearchOverlay(container, {
|
|
placeholder: options.placeholder || 'Buscar...',
|
|
onInput: (value) => {
|
|
currentValue = (value || '').toLowerCase().trim();
|
|
if (badgeEl) {
|
|
badgeEl.textContent = currentValue ? `Filtro: "${currentValue}"` : '';
|
|
}
|
|
if (typeof options.onSearch === 'function') {
|
|
debounce(debounceId, options.onSearch, wait, [currentValue]);
|
|
}
|
|
},
|
|
});
|
|
if (openBtnId) {
|
|
const openBtn = document.getElementById(openBtnId);
|
|
if (openBtn) {
|
|
openBtn.addEventListener('click', () => overlay.open());
|
|
}
|
|
}
|
|
return {
|
|
open: overlay.open,
|
|
close: overlay.close,
|
|
setValue: overlay.setValue,
|
|
getValue: () => currentValue,
|
|
getValueRaw: overlay.getValue,
|
|
};
|
|
}
|
|
|
|
function TS_normalizePictoValue(value) {
|
|
if (!value) {
|
|
return { text: '', arasaacId: '' };
|
|
}
|
|
if (typeof value === 'string') {
|
|
return { text: value, arasaacId: '' };
|
|
}
|
|
if (typeof value === 'object') {
|
|
return {
|
|
text: value.text || value.nombre || value.name || '',
|
|
arasaacId: value.arasaacId || value.id || '',
|
|
};
|
|
}
|
|
return { text: String(value), arasaacId: '' };
|
|
}
|
|
|
|
function TS_buildArasaacPictogramUrl(id) {
|
|
return `https://static.arasaac.org/pictograms/${id}/${id}_300.png`;
|
|
}
|
|
|
|
function TS_renderPictoPreview(previewEl, value) {
|
|
const target = typeof previewEl === 'string' ? document.getElementById(previewEl) : previewEl;
|
|
if (!target) return;
|
|
target.innerHTML = '';
|
|
if (!value.text && !value.arasaacId) {
|
|
const placeholder = document.createElement('b');
|
|
placeholder.textContent = 'Seleccionar Pictograma';
|
|
target.appendChild(placeholder);
|
|
}
|
|
|
|
if (value.arasaacId) {
|
|
const img = document.createElement('img');
|
|
img.src = TS_buildArasaacPictogramUrl(value.arasaacId);
|
|
img.alt = value.text || 'Pictograma';
|
|
img.width = 100;
|
|
img.height = 100;
|
|
img.loading = 'lazy';
|
|
img.style.objectFit = 'contain';
|
|
target.appendChild(img);
|
|
}
|
|
if (value.text) {
|
|
const text = document.createElement('span');
|
|
text.textContent = value.text;
|
|
target.appendChild(text);
|
|
}
|
|
}
|
|
function makePictoStatic(picto) {
|
|
var element = document.createElement('div');
|
|
element.className = 'picto';
|
|
TS_renderPictoPreview(element, picto);
|
|
return element.outerHTML;
|
|
}
|
|
|
|
function TS_applyPictoValue(pictoEl, value) {
|
|
if (typeof pictoEl === 'string') {
|
|
pictoEl = document.getElementById(pictoEl);
|
|
}
|
|
const plate = TS_normalizePictoValue(value);
|
|
pictoEl.dataset.PictoValue = JSON.stringify(plate);
|
|
TS_renderPictoPreview(pictoEl, plate);
|
|
}
|
|
|
|
function TS_getPictoValue(pictoEl) {
|
|
if (typeof pictoEl === 'string') {
|
|
pictoEl = document.getElementById(pictoEl);
|
|
}
|
|
if (!pictoEl) return { text: '', arasaacId: '' };
|
|
const plate = pictoEl.dataset.PictoValue ? JSON.parse(pictoEl.dataset.PictoValue) : { text: '', arasaacId: '' };
|
|
return TS_normalizePictoValue(plate);
|
|
}
|
|
|
|
function TS_CreateArasaacSelector(options) {
|
|
let panelEl = options.panelEl;
|
|
let searchEl = options.searchEl;
|
|
let resultsEl = options.resultsEl;
|
|
let statusEl = options.statusEl;
|
|
let closeEl = options.closeEl;
|
|
const debounceId = options.debounceId || safeuuid();
|
|
const onPick = typeof options.onPick === 'function' ? options.onPick : () => {};
|
|
let activeContext = null;
|
|
let overlayEl = null;
|
|
|
|
if (options.modal === true && !panelEl) {
|
|
const overlayId = safeuuid();
|
|
const panelId = safeuuid();
|
|
const searchId = safeuuid();
|
|
const closeId = safeuuid();
|
|
const statusId = safeuuid();
|
|
const resultsId = safeuuid();
|
|
|
|
overlayEl = document.createElement('div');
|
|
overlayEl.id = overlayId;
|
|
overlayEl.style.display = 'none';
|
|
overlayEl.style.position = 'fixed';
|
|
overlayEl.style.inset = '0';
|
|
overlayEl.style.background = 'rgba(0, 0, 0, 0.5)';
|
|
overlayEl.style.zIndex = '10000';
|
|
overlayEl.style.alignItems = 'center';
|
|
overlayEl.style.justifyContent = 'center';
|
|
overlayEl.style.padding = '16px';
|
|
overlayEl.innerHTML = html`
|
|
<div
|
|
id="${panelId}"
|
|
style="background: white; padding: 12px; border-radius: 8px; width: min(680px, 100%); box-shadow: 0 10px 30px rgba(0,0,0,0.2);"
|
|
>
|
|
<div style="display: flex; gap: 6px; align-items: center;">
|
|
<input
|
|
type="text"
|
|
id="${searchId}"
|
|
placeholder="Buscar pictogramas ARASAAC..."
|
|
style="flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px;"
|
|
/>
|
|
<button type="button" class="btn4" id="${closeId}">Cerrar</button>
|
|
</div>
|
|
<div id="${statusId}" style="margin-top: 6px;"></div>
|
|
<div
|
|
id="${resultsId}"
|
|
style="display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; max-height: 60vh; overflow: auto;"
|
|
></div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(overlayEl);
|
|
panelEl = overlayEl.querySelector('#' + panelId);
|
|
searchEl = overlayEl.querySelector('#' + searchId);
|
|
resultsEl = overlayEl.querySelector('#' + resultsId);
|
|
statusEl = overlayEl.querySelector('#' + statusId);
|
|
closeEl = overlayEl.querySelector('#' + closeId);
|
|
|
|
overlayEl.addEventListener('click', (event) => {
|
|
if (event.target === overlayEl) {
|
|
close();
|
|
}
|
|
});
|
|
}
|
|
|
|
function open(context) {
|
|
activeContext = context || activeContext;
|
|
if (overlayEl) {
|
|
overlayEl.style.display = 'flex';
|
|
} else if (panelEl) {
|
|
panelEl.style.display = 'block';
|
|
}
|
|
if (searchEl) searchEl.focus();
|
|
}
|
|
|
|
function close() {
|
|
if (overlayEl) {
|
|
overlayEl.style.display = 'none';
|
|
} else if (panelEl) {
|
|
panelEl.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function renderResults(items, term) {
|
|
if (!resultsEl || !statusEl) return;
|
|
resultsEl.innerHTML = '';
|
|
if (!items.length) {
|
|
statusEl.textContent = `No se encontraron pictogramas para "${term}"`;
|
|
return;
|
|
}
|
|
statusEl.textContent = `${items.length} pictogramas`;
|
|
items.slice(0, 60).forEach((item) => {
|
|
const btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.style.display = 'flex';
|
|
btn.style.flexDirection = 'column';
|
|
btn.style.alignItems = 'center';
|
|
btn.style.gap = '4px';
|
|
btn.style.padding = '4px';
|
|
btn.style.border = '1px solid #ddd';
|
|
btn.style.background = 'white';
|
|
btn.style.cursor = 'pointer';
|
|
|
|
const img = document.createElement('img');
|
|
img.src = TS_buildArasaacPictogramUrl(item.id);
|
|
img.alt = item.label;
|
|
img.width = 64;
|
|
img.height = 64;
|
|
img.loading = 'lazy';
|
|
img.style.objectFit = 'contain';
|
|
|
|
const label = document.createElement('span');
|
|
label.style.fontSize = '12px';
|
|
label.textContent = item.label;
|
|
|
|
btn.appendChild(img);
|
|
btn.appendChild(label);
|
|
btn.onclick = () => {
|
|
if (!activeContext) return;
|
|
onPick(activeContext, item);
|
|
close();
|
|
};
|
|
resultsEl.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
function search(term) {
|
|
if (!statusEl || !resultsEl) return;
|
|
const trimmed = term.trim();
|
|
if (trimmed.length < 2) {
|
|
statusEl.textContent = 'Escribe al menos 2 caracteres para buscar.';
|
|
resultsEl.innerHTML = '';
|
|
return;
|
|
}
|
|
statusEl.textContent = 'Buscando...';
|
|
fetch(`https://api.arasaac.org/api/pictograms/es/search/${encodeURIComponent(trimmed)}`)
|
|
.then((res) => res.json())
|
|
.then((items) => {
|
|
const pictograms = (Array.isArray(items) ? items : [])
|
|
.map((item) => {
|
|
if (typeof item === 'string' || typeof item === 'number') {
|
|
return { id: item, label: trimmed };
|
|
}
|
|
const id = item._id || item.id;
|
|
const keywords = Array.isArray(item.keywords) ? item.keywords : [];
|
|
const keyword = keywords[0] ? keywords[0].keyword || keywords[0].name : '';
|
|
const label = keyword || item.keyword || item.name || trimmed;
|
|
return { id, label };
|
|
})
|
|
.filter((item) => item.id);
|
|
renderResults(pictograms, trimmed);
|
|
})
|
|
.catch(() => {
|
|
statusEl.textContent = 'Error al buscar pictogramas.';
|
|
resultsEl.innerHTML = '';
|
|
});
|
|
}
|
|
|
|
if (closeEl) {
|
|
closeEl.onclick = close;
|
|
}
|
|
if (searchEl) {
|
|
searchEl.addEventListener('input', () =>
|
|
debounce(debounceId, search, 300, [searchEl.value])
|
|
);
|
|
}
|
|
|
|
return {
|
|
open,
|
|
close,
|
|
};
|
|
}
|
|
|
|
const wheelcolors = [
|
|
// Your original custom colors
|
|
'#ff0000',
|
|
'#ff00ff',
|
|
'#00ff00',
|
|
'#0000ff',
|
|
'#00ffff',
|
|
'#000000',
|
|
'#69DDFF',
|
|
'#7FB800',
|
|
'#963484',
|
|
'#FF1D15',
|
|
'#FF8600',
|
|
|
|
// Precomputed 30° hue-step colors (12 steps, 70% saturation, 50% lightness)
|
|
'#bf3f3f', // 0°
|
|
'#bf9f3f', // 30°
|
|
'#bfff3f', // 60°
|
|
'#7fff3f', // 90°
|
|
'#3fff5f', // 120°
|
|
'#3fffbf', // 150°
|
|
'#3fafff', // 180°
|
|
'#3f3fff', // 210°
|
|
'#9f3fff', // 240°
|
|
'#ff3fff', // 270°
|
|
'#ff3f7f', // 300°
|
|
'#ff3f3f', // 330°
|
|
];
|
|
|
|
// String prototype using the precomputed array
|
|
String.prototype.toHex = function () {
|
|
let hash = 0;
|
|
for (let i = 0; i < this.length; i++) {
|
|
hash = (hash * 31 + this.charCodeAt(i)) >>> 0;
|
|
}
|
|
return wheelcolors[hash % wheelcolors.length];
|
|
};
|
|
|
|
function stringToColour(str) {
|
|
return str.toHex();
|
|
}
|
|
|
|
function colorIsDarkAdvanced(bgColor) {
|
|
let color = bgColor.charAt(0) === '#' ? bgColor.substring(1, 7) : bgColor;
|
|
let r = parseInt(color.substring(0, 2), 16); // hexToR
|
|
let g = parseInt(color.substring(2, 4), 16); // hexToG
|
|
let b = parseInt(color.substring(4, 6), 16); // hexToB
|
|
let uicolors = [r / 255, g / 255, b / 255];
|
|
let c = uicolors.map((col) => {
|
|
if (col <= 0.03928) {
|
|
return col / 12.92;
|
|
}
|
|
return Math.pow((col + 0.055) / 1.055, 2.4);
|
|
});
|
|
let L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
|
return L <= 0.179 ? '#FFFFFF' : '#000000';
|
|
}
|
|
|
|
function setLayeredImages(comanda, key) {
|
|
// Base paths for each layer type (adjust paths as needed)
|
|
const basePaths = {
|
|
Selección: 'static/ico/layered1/',
|
|
Café: 'static/ico/layered1/',
|
|
Endulzante: 'static/ico/layered1/',
|
|
Cafeina: 'static/ico/layered1/',
|
|
Leche: 'static/ico/layered1/',
|
|
};
|
|
|
|
// Map for Selección to filenames
|
|
const selectionMap = {
|
|
'ColaCao con leche': 'Selección-ColaCao.png',
|
|
Infusión: 'Selección-Infusion.png',
|
|
'Café con leche': 'Selección-CaféLeche.png',
|
|
'Solo Leche': 'Selección-Leche.png',
|
|
'Solo café (sin leche)': 'Selección-CaféSolo.png',
|
|
};
|
|
|
|
// Start div with relative positioning for layering
|
|
let html = `<div style="position: relative; width: 200px; height: 200px; background: white; display: inline-block; border: 1px dotted black;">`;
|
|
|
|
// Layer 1: Selección image
|
|
const selection = comanda['Selección'];
|
|
if (selectionMap[selection]) {
|
|
html += `<img id="img1-${key}" src="${
|
|
basePaths.Selección + selectionMap[selection]
|
|
}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">`;
|
|
}
|
|
|
|
// Layer 2: Café
|
|
if (comanda.Café) {
|
|
html += `<img id="img2-${key}" src="${basePaths.Café}Café-${comanda.Café}.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">`;
|
|
}
|
|
|
|
// Layer 3: Endulzante
|
|
if (comanda.Endulzante) {
|
|
html += `<img id="img3-${key}" src="${basePaths.Endulzante}Azucar-${comanda.Endulzante}.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">`;
|
|
}
|
|
|
|
// Layer 4: Cafeina
|
|
if (comanda.Cafeina) {
|
|
html += `<img id="img4-${key}" src="${basePaths.Cafeina}Cafeina-${comanda.Cafeina}.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">`;
|
|
}
|
|
// Layer 5: Leche
|
|
if (comanda.Leche) {
|
|
html += `<img id="img5-${key}" src="${basePaths.Leche}Leche-${comanda.Leche}.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">`;
|
|
}
|
|
// Layer 6: Temperatura
|
|
if (comanda.Temperatura) {
|
|
html += `<img id="img6-${key}" src="${basePaths.Leche}Temperatura-${comanda.Temperatura}.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">`;
|
|
}
|
|
// Layer 7: Tamaño
|
|
if (comanda.Tamaño) {
|
|
html += `<img id="img7-${key}" src="${basePaths.Leche}Tamaño-${comanda.Tamaño}.png" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">`;
|
|
}
|
|
|
|
// Close div
|
|
html += '</div>';
|
|
|
|
return html;
|
|
}
|
|
|
|
function addCategory(parent, name, icon, options, values, change_cb = () => {}) {
|
|
var details_0 = document.createElement('details'); // children: img_0, summary_0
|
|
//details_0.open = true;
|
|
var img_0 = document.createElement('img');
|
|
img_0.src = 'static/ico/checkbox_unchecked.png';
|
|
img_0.style.height = '30px';
|
|
if (values[name] != undefined) {
|
|
//details_0.open = true;
|
|
img_0.src = 'static/ico/checkbox.png';
|
|
}
|
|
var summary_0 = document.createElement('summary');
|
|
var span_0 = document.createElement('span');
|
|
span_0.style.float = 'right';
|
|
span_0.append(values[name] || '', ' ', img_0);
|
|
summary_0.append(name, span_0);
|
|
details_0.append(summary_0, document.createElement('br'));
|
|
details_0.style.textAlign = 'center';
|
|
details_0.style.margin = '5px';
|
|
details_0.style.padding = '5px';
|
|
details_0.style.border = '2px solid black';
|
|
details_0.style.borderRadius = '5px';
|
|
details_0.style.backgroundColor = 'white';
|
|
details_0.style.cursor = 'pointer';
|
|
details_0.style.width = 'calc(100% - 25px)';
|
|
details_0.style.display = 'inline-block';
|
|
summary_0.style.padding = '10px';
|
|
// background image at the start of summary_0:
|
|
summary_0.style.backgroundImage = "url('" + icon + "')";
|
|
summary_0.style.backgroundSize = 'contain';
|
|
summary_0.style.backgroundPosition = 'left';
|
|
summary_0.style.backgroundRepeat = 'no-repeat';
|
|
summary_0.style.textAlign = 'left';
|
|
summary_0.style.paddingLeft = '55px';
|
|
parent.append(details_0);
|
|
|
|
options.forEach((option) => {
|
|
var btn = document.createElement('button');
|
|
var br1 = document.createElement('br');
|
|
//btn.innerText = option.key + ": " + option.value
|
|
btn.append(option.value);
|
|
// for each image in option.img:
|
|
|
|
if (option.img) {
|
|
var br2 = document.createElement('br');
|
|
btn.append(br2);
|
|
option.img.forEach((imgsrc) => {
|
|
var img = document.createElement('img');
|
|
img.src = imgsrc;
|
|
img.style.height = '50px';
|
|
img.style.padding = '5px';
|
|
img.style.backgroundColor = 'white';
|
|
btn.append(img, ' ');
|
|
});
|
|
}
|
|
btn.className = option.className;
|
|
if (values[option.key] == option.value) {
|
|
btn.classList.add('activeSCButton');
|
|
}
|
|
btn.onclick = (event) => {
|
|
var items = details_0.getElementsByClassName('activeSCButton');
|
|
for (var i = 0; i < items.length; i++) {
|
|
items[i].classList.remove('activeSCButton');
|
|
}
|
|
btn.classList.add('activeSCButton');
|
|
values[option.key] = option.value;
|
|
span_0.innerText = option.value;
|
|
change_cb(values);
|
|
img_0.src = 'static/ico/checkbox.png';
|
|
//details_0.open = false; // Disabled due to request
|
|
};
|
|
btn.style.borderRadius = '20px';
|
|
//btn.style.fontSize="17.5px"
|
|
details_0.append(btn);
|
|
});
|
|
}
|
|
|
|
function addCategory_Personas(
|
|
parent,
|
|
options,
|
|
defaultval,
|
|
change_cb = () => {},
|
|
label = 'Persona',
|
|
open_default = false,
|
|
default_empty_text = '- Lista Vacia -',
|
|
show_hidden = false,
|
|
) {
|
|
var details_0 = document.createElement('details'); // children: img_0, summary_0
|
|
//details_0.open = true;
|
|
var img_0 = document.createElement('img');
|
|
img_0.src = 'static/ico/checkbox_unchecked.png';
|
|
img_0.style.height = '30px';
|
|
if (defaultval != '') {
|
|
details_0.open = false;
|
|
img_0.src = 'static/ico/checkbox.png';
|
|
}
|
|
var summary_0 = document.createElement('summary');
|
|
var span_0 = document.createElement('span');
|
|
span_0.style.float = 'right';
|
|
var p = SC_Personas[defaultval] || {};
|
|
span_0.append(p.Nombre || '', ' ', img_0);
|
|
summary_0.append(label, span_0);
|
|
details_0.append(summary_0, document.createElement('br'));
|
|
if (open_default == true) {
|
|
details_0.open = true;
|
|
}
|
|
details_0.style.textAlign = 'center';
|
|
details_0.style.margin = '5px';
|
|
details_0.style.padding = '5px';
|
|
details_0.style.border = '2px solid black';
|
|
details_0.style.borderRadius = '5px';
|
|
details_0.style.backgroundColor = 'white';
|
|
details_0.style.cursor = 'pointer';
|
|
details_0.style.width = 'calc(100% - 25px)';
|
|
details_0.style.display = 'inline-block';
|
|
summary_0.style.padding = '10px';
|
|
// background image at the start of summary_0:
|
|
summary_0.style.backgroundImage = "url('static/ico/user.png')";
|
|
summary_0.style.backgroundSize = 'contain';
|
|
summary_0.style.backgroundPosition = 'left';
|
|
summary_0.style.backgroundRepeat = 'no-repeat';
|
|
summary_0.style.textAlign = 'left';
|
|
summary_0.style.paddingLeft = '55px';
|
|
parent.append(details_0);
|
|
var lastreg = '';
|
|
Object.entries(options)
|
|
.map(([_, data]) => {
|
|
data['_key'] = _;
|
|
return data;
|
|
})
|
|
.sort(betterSorter)
|
|
.map((entry) => {
|
|
var key = entry['_key'];
|
|
var value = entry;
|
|
if (value.Oculto == true && !show_hidden) { return; }
|
|
if (lastreg != value.Region.toUpperCase()) {
|
|
lastreg = value.Region.toUpperCase();
|
|
var h3_0 = document.createElement('h2');
|
|
h3_0.style.margin = '0';
|
|
h3_0.style.marginTop = '15px';
|
|
h3_0.innerText = lastreg;
|
|
details_0.append(h3_0);
|
|
}
|
|
var option = value.Nombre;
|
|
var btn = document.createElement('button');
|
|
var br1 = document.createElement('br');
|
|
//btn.innerText = option.key + ": " + option.value
|
|
btn.append(option);
|
|
|
|
var br2 = document.createElement('br');
|
|
btn.append(br2);
|
|
var img = document.createElement('img');
|
|
img.src = value.Foto || 'static/ico/user_generic.png';
|
|
// Prefer attachment 'foto' for this persona
|
|
try {
|
|
const personaKey = key;
|
|
if (personaKey) {
|
|
DB.getAttachment('personas', personaKey, 'foto')
|
|
.then((durl) => {
|
|
if (durl) img.src = durl;
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
} catch (e) {}
|
|
img.style.height = '60px';
|
|
img.style.padding = '5px';
|
|
img.style.backgroundColor = 'white';
|
|
btn.append(img, ' ');
|
|
|
|
if (defaultval == key) {
|
|
btn.classList.add('activeSCButton');
|
|
}
|
|
btn.onclick = (event) => {
|
|
var items = details_0.getElementsByClassName('activeSCButton');
|
|
for (var i = 0; i < items.length; i++) {
|
|
items[i].classList.remove('activeSCButton');
|
|
}
|
|
btn.classList.add('activeSCButton');
|
|
defaultval = key;
|
|
span_0.innerText = '';
|
|
var img_5 = document.createElement('img');
|
|
img_5.src = value.Foto || 'static/ico/user_generic.png';
|
|
// Prefer attachment 'foto' when available
|
|
try {
|
|
const personaKey2 = key;
|
|
if (personaKey2) {
|
|
DB.getAttachment('personas', personaKey2, 'foto')
|
|
.then((durl) => {
|
|
if (durl) img_5.src = durl;
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
} catch (e) {}
|
|
img_5.style.height = '30px';
|
|
span_0.append(img_5, value.Nombre);
|
|
change_cb(defaultval);
|
|
img_0.src = 'static/ico/checkbox.png';
|
|
//details_0.open = false; // Disabled due to request
|
|
};
|
|
btn.style.borderRadius = '20px';
|
|
//btn.style.fontSize="17.5px"
|
|
details_0.append(btn);
|
|
});
|
|
if (Object.entries(options).length == 0) {
|
|
var btn = document.createElement('b');
|
|
btn.append(default_empty_text);
|
|
details_0.append(btn);
|
|
}
|
|
}
|
|
const SC_actions_icons = {
|
|
Tamaño: 'static/ico/sizes.png',
|
|
Temperatura: 'static/ico/thermometer2.png',
|
|
Leche: 'static/ico/milk.png',
|
|
Selección: 'static/ico/preferences.png',
|
|
Cafeina: 'static/ico/coffee_bean.png',
|
|
Endulzante: 'static/ico/lollipop.png',
|
|
Receta: 'static/ico/cookies.png',
|
|
};
|
|
const SC_actions = {
|
|
Selección: [
|
|
{
|
|
value: 'Solo Leche',
|
|
key: 'Selección',
|
|
className: 'btn4',
|
|
img: ['static/ico/milk.png'],
|
|
},
|
|
{
|
|
value: 'Solo café (sin leche)',
|
|
key: 'Selección',
|
|
className: 'btn4',
|
|
img: ['static/ico/coffee_bean.png'],
|
|
},
|
|
{
|
|
value: 'Café con leche',
|
|
key: 'Selección',
|
|
className: 'btn4',
|
|
img: ['static/ico/coffee_bean.png', 'static/ico/milk.png'],
|
|
},
|
|
{
|
|
value: 'ColaCao con leche',
|
|
key: 'Selección',
|
|
className: 'btn4',
|
|
img: ['static/ico/colacao.jpg', 'static/ico/milk.png'],
|
|
},
|
|
{
|
|
value: 'Leche con cereales',
|
|
key: 'Selección',
|
|
className: 'btn4',
|
|
img: ['static/ico/cereales.png', 'static/ico/milk.png'],
|
|
},
|
|
{
|
|
value: 'Infusión',
|
|
key: 'Selección',
|
|
className: 'btn4',
|
|
img: ['static/ico/tea_bag.png'],
|
|
},
|
|
],
|
|
Tamaño: [
|
|
{
|
|
value: 'Grande',
|
|
key: 'Tamaño',
|
|
className: 'btn1',
|
|
img: ['static/ico/keyboard_key_g.png'],
|
|
},
|
|
{
|
|
value: 'Pequeño',
|
|
key: 'Tamaño',
|
|
className: 'btn1',
|
|
img: ['static/ico/keyboard_key_p.png'],
|
|
},
|
|
],
|
|
Temperatura: [
|
|
{
|
|
value: 'Caliente',
|
|
key: 'Temperatura',
|
|
className: 'btn2',
|
|
img: ['static/ico/thermometer2.png', 'static/ico/arrow_up_red.png', 'static/ico/fire.png'],
|
|
},
|
|
{
|
|
value: 'Templado',
|
|
key: 'Temperatura',
|
|
className: 'btn2',
|
|
img: ['static/ico/thermometer2.png', 'static/ico/arrow_left_green.png'],
|
|
},
|
|
{
|
|
value: 'Frio',
|
|
key: 'Temperatura',
|
|
className: 'btn2',
|
|
img: [
|
|
'static/ico/thermometer2.png',
|
|
'static/ico/arrow_down_blue.png',
|
|
'static/ico/snowflake.png',
|
|
],
|
|
},
|
|
],
|
|
Leche: [
|
|
{
|
|
value: 'de Vaca',
|
|
key: 'Leche',
|
|
className: 'btn3',
|
|
img: ['static/ico/cow.png', 'static/ico/add.png'],
|
|
},
|
|
{
|
|
value: 'Sin lactosa',
|
|
key: 'Leche',
|
|
className: 'btn3',
|
|
img: ['static/ico/cow.png', 'static/ico/delete.png'],
|
|
},
|
|
{
|
|
value: 'Vegetal',
|
|
key: 'Leche',
|
|
className: 'btn3',
|
|
img: ['static/ico/milk.png', 'static/ico/wheat.png'],
|
|
},
|
|
{
|
|
value: 'Almendras',
|
|
key: 'Leche',
|
|
className: 'btn3',
|
|
img: ['static/ico/milk.png', 'static/ico/almond.svg'],
|
|
},
|
|
{
|
|
value: 'Agua',
|
|
key: 'Leche',
|
|
className: 'btn3',
|
|
img: ['static/ico/water_tap.png'],
|
|
},
|
|
],
|
|
Cafeina: [
|
|
{
|
|
value: 'Con',
|
|
key: 'Cafeina',
|
|
className: 'btn5',
|
|
img: ['static/ico/coffee_bean.png', 'static/ico/add.png'],
|
|
},
|
|
{
|
|
value: 'Sin',
|
|
key: 'Cafeina',
|
|
className: 'btn5',
|
|
img: ['static/ico/coffee_bean.png', 'static/ico/delete.png'],
|
|
},
|
|
],
|
|
Endulzante: [
|
|
{
|
|
value: 'Az. Blanco',
|
|
key: 'Endulzante',
|
|
className: 'btn6',
|
|
img: ['static/ico/azucar-blanco.jpg'],
|
|
},
|
|
{
|
|
value: 'Az. Moreno',
|
|
key: 'Endulzante',
|
|
className: 'btn6',
|
|
img: ['static/ico/azucar-moreno.png'],
|
|
},
|
|
{
|
|
value: 'Sacarina',
|
|
key: 'Endulzante',
|
|
className: 'btn6',
|
|
img: ['static/ico/sacarina.jpg'],
|
|
},
|
|
{
|
|
value: 'Stevia (Pastillas)',
|
|
key: 'Endulzante',
|
|
className: 'btn6',
|
|
img: ['static/ico/stevia.jpg'],
|
|
},
|
|
{
|
|
value: 'Stevia (Gotas)',
|
|
key: 'Endulzante',
|
|
className: 'btn6',
|
|
img: ['static/ico/stevia-gotas.webp'],
|
|
},
|
|
{
|
|
value: 'Sin',
|
|
key: 'Endulzante',
|
|
className: 'btn6',
|
|
img: ['static/ico/delete.png'],
|
|
},
|
|
],
|
|
Receta: [
|
|
{
|
|
value: 'Si',
|
|
key: 'Receta',
|
|
className: 'btn7',
|
|
img: ['static/ico/add.png'],
|
|
},
|
|
{
|
|
value: 'No',
|
|
key: 'Receta',
|
|
className: 'btn7',
|
|
img: ['static/ico/delete.png'],
|
|
},
|
|
],
|
|
};
|
|
function TS_decrypt(input, secret, callback, table, id) {
|
|
// Accept objects or plaintext strings. Support AES-encrypted entries wrapped as RSA{...}.
|
|
if (typeof input !== 'string') {
|
|
try {
|
|
callback(input, false);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Encrypted format marker: RSA{<ciphertext>} where <ciphertext> is CryptoJS AES output
|
|
if (input.startsWith('RSA{') && input.endsWith('}') && typeof CryptoJS !== 'undefined') {
|
|
try {
|
|
var data = input.slice(4, -1);
|
|
var words = CryptoJS.AES.decrypt(data, secret);
|
|
var decryptedUtf8 = null;
|
|
try {
|
|
decryptedUtf8 = words.toString(CryptoJS.enc.Utf8);
|
|
} catch (utfErr) {
|
|
try {
|
|
decryptedUtf8 = words.toString(CryptoJS.enc.Latin1);
|
|
} catch (latinErr) {
|
|
console.warn('TS_decrypt: failed to decode decrypted bytes', utfErr, latinErr);
|
|
try {
|
|
callback(input, 'error');
|
|
} catch (ee) {}
|
|
return;
|
|
}
|
|
}
|
|
var parsed = null;
|
|
try {
|
|
parsed = JSON.parse(decryptedUtf8);
|
|
} catch (pe) {
|
|
parsed = decryptedUtf8;
|
|
try {
|
|
callback(parsed, 'error2');
|
|
} catch (ee) {
|
|
console.error(ee);
|
|
}
|
|
return;
|
|
}
|
|
try {
|
|
callback(parsed, true);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
// Keep encrypted at-rest: if table/id provided, ensure DB stores encrypted payload (input)
|
|
// if (table && id && window.DB && DB.put) {
|
|
// DB.put(table, id, input).catch(() => {});
|
|
// }
|
|
return;
|
|
} catch (e) {
|
|
console.error('TS_decrypt: invalid encrypted payload', e);
|
|
try {
|
|
callback(input, 'error');
|
|
} catch (ee) {}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Plain JSON stored as text -> parse and return, then re-encrypt in DB for at-rest protection
|
|
try {
|
|
var parsed = JSON.parse(input);
|
|
try {
|
|
callback(parsed, false);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
if (table && id && window.DB && DB.put && typeof SECRET !== 'undefined') {
|
|
TS_encrypt(parsed, SECRET, function (enc) {
|
|
DB.put(table, id, enc).catch(() => {});
|
|
});
|
|
}
|
|
} catch (e) {
|
|
// Not JSON, return raw string
|
|
try {
|
|
callback(input, false);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
}
|
|
}
|
|
function TS_encrypt(input, secret, callback, mode = 'RSA') {
|
|
// Skip encryption
|
|
//callback(input);
|
|
//return;
|
|
// Encrypt given value for at-rest storage using CryptoJS AES.
|
|
// Always return string of form RSA{<ciphertext>} via callback.
|
|
try {
|
|
if (typeof CryptoJS === 'undefined') {
|
|
// CryptoJS not available — return plaintext
|
|
try {
|
|
callback(input);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
return;
|
|
}
|
|
var payload = input;
|
|
if (typeof input !== 'string') {
|
|
try {
|
|
payload = JSON.stringify(input);
|
|
} catch (e) {
|
|
payload = String(input);
|
|
}
|
|
}
|
|
var encrypted = CryptoJS.AES.encrypt(payload, secret).toString();
|
|
var out = 'RSA{' + encrypted + '}';
|
|
try {
|
|
callback(out);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
} catch (e) {
|
|
console.error('TS_encrypt: encryption failed', e);
|
|
try {
|
|
callback(input);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
}
|
|
}
|
|
// Listado precargado de personas:
|
|
DB.map('personas', (data, key) => {
|
|
function add_row(data, key) {
|
|
if (data != null) {
|
|
data['_key'] = key;
|
|
SC_Personas[key] = data;
|
|
} else {
|
|
delete SC_Personas[key];
|
|
}
|
|
}
|
|
if (typeof data == 'string') {
|
|
TS_decrypt(
|
|
data,
|
|
SECRET,
|
|
(data, wasEncrypted) => {
|
|
add_row(data, key);
|
|
},
|
|
'personas',
|
|
key
|
|
);
|
|
} else {
|
|
add_row(data, key);
|
|
}
|
|
});
|
|
|
|
function SC_parse(json) {
|
|
var out = '';
|
|
Object.entries(json).forEach((entry) => {
|
|
out += entry[0] + ': ' + entry[1] + '\n';
|
|
});
|
|
return out;
|
|
}
|
|
|
|
function SC_parse_short(json) {
|
|
const precios = window.PRECIOS_CAFE || {
|
|
servicio_base: 10,
|
|
leche_pequena: 15,
|
|
leche_grande: 25,
|
|
cafe: 25,
|
|
colacao: 25,
|
|
};
|
|
|
|
var valores = `<small style='font-size: 60%;'>Servicio base (${precios.servicio_base}c)</small>\n`;
|
|
|
|
Object.entries(json).forEach((entry) => {
|
|
valores += "<small style='font-size: 60%;'>" + entry[0] + ':</small> ' + entry[1] + ' ';
|
|
var combo = entry[0] + ';' + entry[1];
|
|
switch (entry[0]) {
|
|
case 'Leche':
|
|
// Leche pequeña
|
|
if (
|
|
json['Tamaño'] == 'Pequeño' &&
|
|
['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche'])
|
|
) {
|
|
valores += `<small>(P = ${precios.leche_pequena}c)</small>`;
|
|
}
|
|
// Leche grande
|
|
if (
|
|
json['Tamaño'] == 'Grande' &&
|
|
['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche'])
|
|
) {
|
|
valores += `<small>(G = ${precios.leche_grande}c)</small>`;
|
|
}
|
|
break;
|
|
case 'Selección':
|
|
// Café
|
|
if (['Café con leche', 'Solo café (sin leche)'].includes(json['Selección'])) {
|
|
valores += `<small>(${precios.cafe}c)</small>`;
|
|
}
|
|
// ColaCao
|
|
if (json['Selección'] == 'ColaCao con leche') {
|
|
valores += `<small>(${precios.colacao}c)</small>`;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
valores += '\n';
|
|
});
|
|
return valores;
|
|
}
|
|
|
|
function SC_priceCalc(json) {
|
|
var precio = 0;
|
|
var valores = '';
|
|
|
|
// Usar precios configurables
|
|
const precios = window.PRECIOS_CAFE || {
|
|
servicio_base: 10,
|
|
leche_pequena: 15,
|
|
leche_grande: 25,
|
|
cafe: 25,
|
|
colacao: 25,
|
|
};
|
|
|
|
// Servicio base
|
|
precio += precios.servicio_base;
|
|
valores += `Servicio base = ${precios.servicio_base}c\n`;
|
|
|
|
// Leche pequeña
|
|
if (
|
|
json['Tamaño'] == 'Pequeño' &&
|
|
['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche'])
|
|
) {
|
|
precio += precios.leche_pequena;
|
|
valores += `Leche pequeña = ${precios.leche_pequena}c\n`;
|
|
}
|
|
|
|
// Leche grande
|
|
if (
|
|
json['Tamaño'] == 'Grande' &&
|
|
['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche'])
|
|
) {
|
|
precio += precios.leche_grande;
|
|
valores += `Leche grande = ${precios.leche_grande}c\n`;
|
|
}
|
|
|
|
// Café
|
|
if (['Café con leche', 'Solo café (sin leche)'].includes(json['Selección'])) {
|
|
precio += precios.cafe;
|
|
valores += `Café = ${precios.cafe}c\n`;
|
|
}
|
|
|
|
// ColaCao
|
|
if (json['Selección'] == 'ColaCao con leche') {
|
|
precio += precios.colacao;
|
|
valores += `ColaCao = ${precios.colacao}c\n`;
|
|
}
|
|
|
|
valores += '<hr>Total: ' + precio + 'c\n';
|
|
return [precio, valores];
|
|
}
|
|
|
|
function TS_IndexElement(
|
|
pageco,
|
|
config,
|
|
ref,
|
|
container,
|
|
rowCallback = undefined,
|
|
canAddCallback = undefined,
|
|
globalSearchBar = true
|
|
) {
|
|
// Every item in config should have:
|
|
// key: string
|
|
// type: string
|
|
// default: string
|
|
// label: string
|
|
var tablebody = safeuuid();
|
|
var tablehead = safeuuid();
|
|
var scrolltable = safeuuid();
|
|
var searchKeyInput = safeuuid();
|
|
var debounce_search = safeuuid();
|
|
var debounce_load = safeuuid();
|
|
|
|
// Create the container with search bar and table
|
|
container.innerHTML = html`
|
|
<div id="${scrolltable}">
|
|
<table>
|
|
<thead>
|
|
<tr style="background: transparent;">
|
|
<th colspan="100%" style="padding: 0; background: transparent;">
|
|
<input
|
|
type="text"
|
|
id="${searchKeyInput}"
|
|
placeholder="🔍 Buscar..."
|
|
style="width: calc(100% - 18px); padding: 8px; border: 1px solid #ccc; border-radius: 4px; background-color: rebeccapurple; color: white;"
|
|
value=""
|
|
/>
|
|
</th>
|
|
</tr>
|
|
<tr id="${tablehead}"></tr>
|
|
</thead>
|
|
<tbody id="${tablebody}"></tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
tableScroll('#' + scrolltable); // id="scrolltable"
|
|
var tablehead_EL = document.getElementById(tablehead);
|
|
var tablebody_EL = document.getElementById(tablebody);
|
|
var rows = {};
|
|
config.forEach((key) => {
|
|
tablehead_EL.innerHTML += `<th>${key.label || ''}</th>`;
|
|
});
|
|
// Add search functionality
|
|
const searchKeyEl = document.getElementById(searchKeyInput);
|
|
searchKeyEl.addEventListener('input', () => debounce(debounce_search, render, 200, [rows]));
|
|
// If there is a preset search value in URL, apply it
|
|
var hashQuery = new URLSearchParams(window.location.hash.split('?')[1]);
|
|
if (hashQuery.has('search')) {
|
|
searchKeyEl.value = hashQuery.get('search');
|
|
}
|
|
function searchInData(data, searchValue, config) {
|
|
if (!searchValue) return true;
|
|
|
|
// Search in ID
|
|
if (data._key.toLowerCase().includes(searchValue)) return true;
|
|
|
|
// Search in configured fields
|
|
for (var field of config) {
|
|
const value = data[field.key] || field.default || '';
|
|
|
|
// Handle different field types
|
|
switch (field.type) {
|
|
case 'comanda':
|
|
try {
|
|
const comandaData = JSON.parse(data.Comanda);
|
|
// Search in all comanda fields
|
|
if (
|
|
Object.values(comandaData).some((v) => String(v).toLowerCase().includes(searchValue))
|
|
)
|
|
return true;
|
|
} catch (e) {
|
|
// If JSON parse fails, search in raw string
|
|
if (data.Comanda.toLowerCase().includes(searchValue)) return true;
|
|
}
|
|
break;
|
|
case 'persona':
|
|
case 'persona-nombre':
|
|
var persona = SC_Personas[value] || { Nombre: '', Region: '' };
|
|
if (field.self == true) {
|
|
persona = data || { Nombre: '', Region: '' };
|
|
}
|
|
if (persona) {
|
|
// Search in persona fields
|
|
if (persona.Nombre.toLowerCase().includes(searchValue)) return true;
|
|
if (persona.Region.toLowerCase().includes(searchValue)) return true;
|
|
}
|
|
break;
|
|
case 'fecha':
|
|
case 'fecha-iso':
|
|
// Format date as DD/MM/YYYY for searching
|
|
if (value) {
|
|
const fechaArray = value.split('-');
|
|
const formattedDate = `${fechaArray[2]}/${fechaArray[1]}/${fechaArray[0]}`;
|
|
if (formattedDate.includes(searchValue)) return true;
|
|
}
|
|
break;
|
|
case 'picto': {
|
|
const plate = TS_normalizePictoValue(value);
|
|
if (plate.text && plate.text.toLowerCase().includes(searchValue)) return true;
|
|
break;
|
|
}
|
|
default:
|
|
// For raw and other types, search in the direct value
|
|
if (String(value).toLowerCase().includes(searchValue)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- Optimized render function ---
|
|
var lastSearchValue = '';
|
|
var lastFilteredSorted = [];
|
|
|
|
function getFilteredSortedRows(searchValue) {
|
|
// Only use cache if searchValue is not empty and cache is valid
|
|
if (searchValue && searchValue === lastSearchValue && lastFilteredSorted.length > 0) {
|
|
return lastFilteredSorted;
|
|
}
|
|
const filtered = Object.entries(rows)
|
|
.filter(([_, data]) => searchInData(data, searchValue, config))
|
|
.map(([_, data]) => data)
|
|
.sort(betterSorter);
|
|
lastSearchValue = searchValue;
|
|
lastFilteredSorted = filtered;
|
|
return filtered;
|
|
}
|
|
|
|
function render(rows) {
|
|
const searchValue = searchKeyEl.value.toLowerCase().trim();
|
|
// Use document fragment for batch DOM update
|
|
const fragment = document.createDocumentFragment();
|
|
const filteredSorted = getFilteredSortedRows(searchValue);
|
|
for (let i = 0; i < filteredSorted.length; i++) {
|
|
const data = filteredSorted[i];
|
|
if (canAddCallback != undefined && canAddCallback(data) === true) {
|
|
continue;
|
|
}
|
|
const new_tr = document.createElement('tr');
|
|
if (rowCallback != undefined) {
|
|
rowCallback(data, new_tr);
|
|
}
|
|
config.forEach((key) => {
|
|
switch (key.type) {
|
|
case '_encrypted': {
|
|
const tdEncrypted = document.createElement('td');
|
|
if (data['_encrypted__'] === true) {
|
|
tdEncrypted.innerText = '🔒';
|
|
} else if (
|
|
data['_encrypted__'] === 'error' ||
|
|
data['_encrypted__'] === 'error2' ||
|
|
data['_encrypted__'] === undefined
|
|
) {
|
|
tdEncrypted.innerText = '⚠️ Error';
|
|
} else {
|
|
tdEncrypted.innerText = '';
|
|
}
|
|
new_tr.appendChild(tdEncrypted);
|
|
break;
|
|
}
|
|
case 'raw':
|
|
case 'text': {
|
|
const tdRaw = document.createElement('td');
|
|
const rawContent = (String(data[key.key]) || key.default || '').replace(/\n/g, '<br>');
|
|
tdRaw.innerHTML = rawContent;
|
|
new_tr.appendChild(tdRaw);
|
|
break;
|
|
}
|
|
case 'moneda': {
|
|
const tdMoneda = document.createElement('td');
|
|
const valor = parseFloat(data[key.key]);
|
|
if (!isNaN(valor)) {
|
|
tdMoneda.innerText = valor.toFixed(2) + ' €';
|
|
} else {
|
|
tdMoneda.innerText = key.default || '';
|
|
}
|
|
new_tr.appendChild(tdMoneda);
|
|
break;
|
|
}
|
|
case 'fecha':
|
|
case 'fecha-iso': {
|
|
const tdFechaISO = document.createElement('td');
|
|
if (data[key.key]) {
|
|
const fechaArray = data[key.key].split('-');
|
|
tdFechaISO.innerText = fechaArray[2] + '/' + fechaArray[1] + '/' + fechaArray[0];
|
|
}
|
|
new_tr.appendChild(tdFechaISO);
|
|
break;
|
|
}
|
|
case 'fecha-diff': {
|
|
const tdFechaISO = document.createElement('td');
|
|
if (data[key.key]) {
|
|
const fecha = new Date(data[key.key]);
|
|
const now = new Date();
|
|
const diffTime = Math.abs(now - fecha);
|
|
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
|
const diffMonths = Math.floor(diffDays / 30);
|
|
const diffYears = Math.floor(diffDays / 365);
|
|
let diffString = '';
|
|
if (diffYears > 0) {
|
|
diffString += diffYears + ' año' + (diffYears > 1 ? 's ' : ' ');
|
|
}
|
|
if (diffMonths % 12 > 0) {
|
|
diffString += (diffMonths % 12) + ' mes' + (diffMonths % 12 > 1 ? 'es ' : ' ');
|
|
}
|
|
|
|
// if more than 3 months, show rgb(255, 192, 192) as background
|
|
if (diffMonths >= 3) {
|
|
tdFechaISO.style.backgroundColor = 'rgb(255, 192, 192)';
|
|
} else if (diffMonths >= 1) {
|
|
tdFechaISO.style.backgroundColor = 'rgb(252, 252, 176)';
|
|
}
|
|
tdFechaISO.innerText = diffString.trim();
|
|
}
|
|
new_tr.appendChild(tdFechaISO);
|
|
break;
|
|
}
|
|
case 'picto': {
|
|
const tdPicto = document.createElement('td');
|
|
const plate = TS_normalizePictoValue(data[key.key]);
|
|
const wrapper = document.createElement('div');
|
|
wrapper.style.display = 'flex';
|
|
wrapper.style.alignItems = 'center';
|
|
wrapper.style.gap = '8px';
|
|
if (plate.arasaacId) {
|
|
const img = document.createElement('img');
|
|
img.src = TS_buildArasaacPictogramUrl(plate.arasaacId);
|
|
img.alt = plate.text || 'Pictograma';
|
|
img.width = 48;
|
|
img.height = 48;
|
|
img.loading = 'lazy';
|
|
img.style.objectFit = 'contain';
|
|
wrapper.appendChild(img);
|
|
}
|
|
if (plate.text) {
|
|
const text = document.createElement('span');
|
|
console.log('Picto data', data, 'normalized', plate);
|
|
text.textContent = data[key.labelkey] || plate.text || '';
|
|
wrapper.appendChild(text);
|
|
}
|
|
tdPicto.appendChild(wrapper);
|
|
new_tr.appendChild(tdPicto);
|
|
break;
|
|
}
|
|
case 'template': {
|
|
const tdCustomTemplate = document.createElement('td');
|
|
new_tr.appendChild(tdCustomTemplate);
|
|
key.template(data, tdCustomTemplate);
|
|
break;
|
|
}
|
|
case 'comanda': {
|
|
const tdComanda = document.createElement('td');
|
|
tdComanda.style.verticalAlign = 'top';
|
|
const parsedComanda = JSON.parse(data.Comanda);
|
|
const precio = SC_priceCalc(parsedComanda)[0];
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = setLayeredImages(parsedComanda, data._key);
|
|
tdComanda.appendChild(tempDiv.firstChild);
|
|
const pre = document.createElement('pre');
|
|
pre.style.fontSize = '15px';
|
|
pre.style.display = 'inline-block';
|
|
pre.style.margin = '0';
|
|
pre.style.verticalAlign = 'top';
|
|
pre.style.padding = '5px';
|
|
pre.style.background = 'rgba(255, 255, 0, 0.5)';
|
|
pre.style.border = '1px solid rgba(0, 0, 0, 0.2)';
|
|
pre.style.borderRadius = '5px';
|
|
pre.style.boxShadow = '2px 2px 5px rgba(0, 0, 0, 0.1)';
|
|
pre.style.height = '100%';
|
|
const spanPrecio = document.createElement('span');
|
|
spanPrecio.style.fontSize = '20px';
|
|
spanPrecio.innerHTML = html`Total: ${precio}c`;
|
|
pre.innerHTML = '<b>Ticket de compra</b> ';
|
|
pre.appendChild(document.createTextNode('\n'));
|
|
pre.innerHTML += SC_parse_short(parsedComanda) + '<hr>' + data.Notas + '<hr>';
|
|
pre.appendChild(spanPrecio);
|
|
tdComanda.appendChild(pre);
|
|
new_tr.appendChild(tdComanda);
|
|
break;
|
|
}
|
|
case 'comanda-status': {
|
|
var sc_nobtn = '';
|
|
if (urlParams.get('sc_nobtn') == 'yes') {
|
|
sc_nobtn = 'pointer-events: none; opacity: 0.5';
|
|
}
|
|
const td = document.createElement('td');
|
|
td.style.fontSize = '17px';
|
|
if (sc_nobtn) {
|
|
td.style.pointerEvents = 'none';
|
|
td.style.opacity = '0.5';
|
|
}
|
|
const createButton = (text, state) => {
|
|
const button = document.createElement('button');
|
|
button.textContent = text;
|
|
if (data.Estado === state) {
|
|
button.className = 'rojo';
|
|
}
|
|
button.onclick = (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
data.Estado = state;
|
|
if (typeof ref === 'string') {
|
|
DB.put(ref, data._key, data)
|
|
.then(() => {
|
|
toastr.success('Guardado!');
|
|
render();
|
|
})
|
|
.catch((e) => {
|
|
console.warn('DB.put error', e);
|
|
});
|
|
} else {
|
|
try {
|
|
// legacy
|
|
ref.get(data._key).put(data);
|
|
toastr.success('Guardado!');
|
|
} catch (e) {
|
|
console.warn('Could not save item', e);
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
return button;
|
|
};
|
|
const buttons = [
|
|
createButton('Pedido', 'Pedido'),
|
|
createButton('En preparación', 'En preparación'),
|
|
createButton('Listo', 'Listo'),
|
|
createButton('Entregado', 'Entregado'),
|
|
createButton('Deuda', 'Deuda'),
|
|
];
|
|
const paidButton = document.createElement('button');
|
|
paidButton.textContent = 'Pagado';
|
|
paidButton.className = 'btn5';
|
|
paidButton.onclick = (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
// Open Pagos module with pre-filled data
|
|
var precio = SC_priceCalc(JSON.parse(data.Comanda))[0];
|
|
var personaId = data.Persona;
|
|
var comandaId = data._key;
|
|
|
|
// Store prefilled data in sessionStorage for Pagos module
|
|
var sdata = JSON.stringify({
|
|
tipo: 'Gasto',
|
|
monto: precio / 100, // Convert cents to euros
|
|
persona: personaId,
|
|
notas: 'Pago de comanda SuperCafé\n' + SC_parse(JSON.parse(data.Comanda)),
|
|
origen: 'SuperCafé',
|
|
origen_id: comandaId,
|
|
});
|
|
|
|
// Navigate to datafono
|
|
setUrlHash('pagos,datafono_prefill,' + btoa(sdata));
|
|
|
|
return false;
|
|
};
|
|
td.append(data.Fecha);
|
|
td.append(document.createElement('br'));
|
|
buttons.forEach((button) => {
|
|
td.appendChild(button);
|
|
td.appendChild(document.createElement('br'));
|
|
});
|
|
td.appendChild(paidButton);
|
|
new_tr.appendChild(td);
|
|
break;
|
|
}
|
|
case 'persona': {
|
|
let persona = key.self === true ? data : SC_Personas[data[key.key]] || {};
|
|
const regco = stringToColour((persona.Region || '?').toLowerCase());
|
|
const tdPersona = document.createElement('td');
|
|
tdPersona.style.textAlign = 'center';
|
|
tdPersona.style.fontSize = '20px';
|
|
tdPersona.style.backgroundColor = regco;
|
|
tdPersona.style.color = colorIsDarkAdvanced(regco);
|
|
const regionSpan = document.createElement('span');
|
|
regionSpan.style.fontSize = '40px';
|
|
regionSpan.style.textTransform = 'capitalize';
|
|
regionSpan.textContent = (persona.Region || '?').toLowerCase();
|
|
tdPersona.appendChild(regionSpan);
|
|
tdPersona.appendChild(document.createElement('br'));
|
|
const infoSpan = document.createElement('span');
|
|
infoSpan.style.backgroundColor = 'white';
|
|
infoSpan.style.border = '2px solid black';
|
|
infoSpan.style.borderRadius = '5px';
|
|
infoSpan.style.display = 'inline-block';
|
|
infoSpan.style.padding = '5px';
|
|
infoSpan.style.color = 'black';
|
|
const img = document.createElement('img');
|
|
img.src = persona.Foto || 'static/ico/user_generic.png';
|
|
// Prefer attachment 'foto' stored in PouchDB if available
|
|
try {
|
|
const personaId =
|
|
key.self === true ? data._key || data._id || data.id : data[key.key];
|
|
if (personaId) {
|
|
DB.getAttachment('personas', personaId, 'foto')
|
|
.then((durl) => {
|
|
if (durl) img.src = durl;
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
img.height = 70;
|
|
infoSpan.appendChild(img);
|
|
infoSpan.appendChild(document.createElement('br'));
|
|
infoSpan.appendChild(document.createTextNode(persona.Nombre || ''));
|
|
infoSpan.appendChild(document.createElement('br'));
|
|
if (parseFloat(persona.Monedero_Balance || '0') != 0) {
|
|
const pointsSpan = document.createElement('span');
|
|
pointsSpan.style.fontSize = '17px';
|
|
pointsSpan.textContent =
|
|
parseFloat(persona.Monedero_Balance || '0').toPrecision(2) + ' €';
|
|
infoSpan.appendChild(pointsSpan);
|
|
}
|
|
tdPersona.appendChild(infoSpan);
|
|
new_tr.appendChild(tdPersona);
|
|
break;
|
|
}
|
|
case 'persona-nombre': {
|
|
let persona = key.self === true ? data : SC_Personas[data[key.key]] || {};
|
|
const tdPersonaNombre = document.createElement('td');
|
|
tdPersonaNombre.style.textAlign = 'center';
|
|
tdPersonaNombre.style.fontSize = '20px';
|
|
tdPersonaNombre.textContent = persona.Nombre || '';
|
|
new_tr.appendChild(tdPersonaNombre);
|
|
break;
|
|
}
|
|
case 'attachment-persona': {
|
|
const tdAttachment = document.createElement('td');
|
|
const img = document.createElement('img');
|
|
img.src = data[key.key] || 'static/ico/user_generic.png';
|
|
img.style.maxHeight = '80px';
|
|
img.style.maxWidth = '80px';
|
|
tdAttachment.appendChild(img);
|
|
new_tr.appendChild(tdAttachment);
|
|
// Prefer attachment 'foto' stored in PouchDB if available
|
|
try {
|
|
const personaId =
|
|
key.self === true ? data._key || data._id || data.id : data[key.key];
|
|
if (personaId) {
|
|
DB.getAttachment('personas', personaId, 'foto')
|
|
.then((durl) => {
|
|
if (durl) img.src = durl;
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
} catch (e) {
|
|
// ignore
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
new_tr.onclick = (event) => {
|
|
setUrlHash(pageco + ',' + data._key);
|
|
};
|
|
fragment.appendChild(new_tr);
|
|
}
|
|
// Replace tbody in one operation
|
|
tablebody_EL.innerHTML = '';
|
|
tablebody_EL.appendChild(fragment);
|
|
}
|
|
// Subscribe to dataset updates using DB.map (PouchDB) when `ref` is a table name string
|
|
if (typeof ref === 'string') {
|
|
EventListeners.DB.push(DB.map(ref, (data, key) => {
|
|
function add_row(data, key) {
|
|
if (data != null) {
|
|
data['_key'] = key;
|
|
rows[key] = data;
|
|
} else {
|
|
delete rows[key];
|
|
}
|
|
debounce(debounce_load, render, 200, [rows]);
|
|
}
|
|
if (typeof data == 'string') {
|
|
TS_decrypt(
|
|
data,
|
|
SECRET,
|
|
(data, wasEncrypted) => {
|
|
if (data != null && typeof data === 'object') {
|
|
data['_encrypted__'] = wasEncrypted;
|
|
add_row(data, key);
|
|
}
|
|
},
|
|
ref,
|
|
key
|
|
);
|
|
} else {
|
|
if (data != null && typeof data === 'object') {
|
|
data['_encrypted__'] = false;
|
|
}
|
|
add_row(data, key);
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
|
|
function BuildQR(mid, label) {
|
|
var svgNode = QRCode({
|
|
msg: mid,
|
|
dim: 150,
|
|
pad: 0,
|
|
mtx: -1,
|
|
ecl: 'S',
|
|
ecb: 0,
|
|
pal: ['#000000', '#ffffff'],
|
|
vrb: 0,
|
|
});
|
|
return `
|
|
<span style="border: 2px dashed black; padding: 10px; display: inline-block; background: white; border-radius: 7px; text-align: center; margin: 5px;">
|
|
<b>QR %%TITLE%%</b>
|
|
<br>${svgNode.outerHTML}<br>
|
|
<small>${label || mid}</small>
|
|
</span>
|
|
`;
|
|
}
|
|
|
|
var PAGES = {};
|
|
var PERMS = {
|
|
ADMIN: 'Administrador',
|
|
};
|
|
function checkRole(role) {
|
|
var roles = SUB_LOGGED_IN_DETAILS.Roles || '';
|
|
var rolesArr = roles.split(',');
|
|
if (rolesArr.includes('ADMIN') || rolesArr.includes(role) || AC_BYPASS) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
function SetPages() {
|
|
document.getElementById('appendApps2').innerHTML = '';
|
|
Object.keys(PAGES).forEach((key) => {
|
|
if (PAGES[key].Esconder == true) {
|
|
return;
|
|
}
|
|
if (PAGES[key].AccessControl == true) {
|
|
var roles = SUB_LOGGED_IN_DETAILS.Roles || '';
|
|
var rolesArr = roles.split(',');
|
|
if (rolesArr.includes('ADMIN') || rolesArr.includes(key) || AC_BYPASS) {
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
var a = document.createElement('a');
|
|
var img = document.createElement('img');
|
|
var label = document.createElement('div');
|
|
a.className = 'ribbon-button';
|
|
a.href = '#' + key;
|
|
label.innerText = PAGES[key].Title;
|
|
label.className = 'label';
|
|
img.src = PAGES[key].icon || 'static/appico/application_enterprise.png';
|
|
a.append(img, label);
|
|
document.getElementById('appendApps2').append(a);
|
|
});
|
|
var a = document.createElement('a');
|
|
var img = document.createElement('img');
|
|
var label = document.createElement('div');
|
|
a.className = 'ribbon-button';
|
|
a.href = '#index,qr';
|
|
label.innerText = 'Escanear QR';
|
|
label.className = 'label';
|
|
img.src = 'static/appico/barcode.png';
|
|
a.append(img, label);
|
|
document.getElementById('appendApps2').append(a);
|
|
}
|
|
var Booted = false;
|
|
var TimeoutBoot = 3; // in loops of 750ms
|
|
var BootLoops = 0;
|
|
|
|
// Get URL host for peer link display
|
|
var couchDatabase = localStorage.getItem('TELESEC_COUCH_DBNAME') || 'telesec';
|
|
var couchUrl = localStorage.getItem('TELESEC_COUCH_URL') || null;
|
|
var couchHost = '';
|
|
try {
|
|
var urlObj = new URL(couchUrl);
|
|
couchHost = urlObj.host;
|
|
} catch (e) {
|
|
couchHost = couchUrl;
|
|
}
|
|
if (couchHost) {
|
|
document.getElementById('peerLink').innerText = couchDatabase + '@' + couchHost;
|
|
}
|
|
|
|
const statusImg = document.getElementById('connectStatus');
|
|
function updateStatusOrb() {
|
|
const now = Date.now();
|
|
const recentSync = window.TELESEC_LAST_SYNC && now - window.TELESEC_LAST_SYNC <= 3000;
|
|
if (recentSync) {
|
|
if (statusImg) {
|
|
const syncColor = window.TELESEC_LAST_SYNC_COLOR || 'hsl(200, 70%, 50%)';
|
|
// Semicircle on the right side
|
|
statusImg.src =
|
|
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEyIDIyYzUuNTIyIDAgMTAtNC40NzggMTAtMTBTMTcuNTIyIDIgMTIgMnMtdjJoLTJWM2gtMnYyaC0ydjJoLTJWM2gtMnYyaC0ydjJoLTJWM2gtMnYyaC0ydi0yaDJWM2gydjJoMnYyaDJ2MmgyVjNoMmwyIDJ2Mmgydi0yaDJ2MmgyVjNoMnYyYzAgNS41MjIgNC40NzggMTAgMTAgMTB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+';
|
|
statusImg.style.backgroundColor = syncColor;
|
|
statusImg.style.borderRadius = '50%';
|
|
}
|
|
return;
|
|
}
|
|
if (window.navigator && window.navigator.onLine === false) {
|
|
if (statusImg) {
|
|
statusImg.src = 'static/ico/offline.svg';
|
|
statusImg.style.backgroundColor = '';
|
|
statusImg.style.borderRadius = '';
|
|
}
|
|
} else {
|
|
if (statusImg) {
|
|
// statusImg.src = "static/logo.jpg";
|
|
// statusImg.style.backgroundColor = "";
|
|
// statusImg.style.borderRadius = "";
|
|
}
|
|
}
|
|
}
|
|
updateStatusOrb();
|
|
setInterval(updateStatusOrb, 250);
|
|
|
|
var BootIntervalID = setInterval(() => {
|
|
BootLoops += 1;
|
|
|
|
const isOnline = window.navigator ? window.navigator.onLine !== false : true;
|
|
|
|
// Check if local DB is initialized and responsive
|
|
const checkLocalDB = () => {
|
|
if (window.DB && DB._internal && DB._internal.local) {
|
|
return DB._internal.local
|
|
.info()
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
}
|
|
return Promise.resolve(false);
|
|
};
|
|
|
|
checkLocalDB().then((dbReady) => {
|
|
// If offline, or DB ready, or we've waited long enough, proceed to boot the UI
|
|
if ((dbReady || !isOnline || BootLoops >= TimeoutBoot) && !Booted) {
|
|
Booted = true;
|
|
document.getElementById('loading').style.display = 'none';
|
|
|
|
if (!isOnline) {
|
|
toastr.error('Sin conexión! Los cambios se sincronizarán cuando vuelvas a estar en línea.');
|
|
}
|
|
|
|
if (!SUB_LOGGED_IN) {
|
|
if (AC_BYPASS) {
|
|
// Auto-create or load a bypass persona and log in automatically
|
|
const bypassId = localStorage.getItem('TELESEC_BYPASS_ID') || 'bypass-admin';
|
|
if (window.DB && DB.get) {
|
|
DB.get('personas', bypassId)
|
|
.then((data) => {
|
|
function finish(pdata, id) {
|
|
SUB_LOGGED_IN_ID = id || bypassId;
|
|
SUB_LOGGED_IN_DETAILS = pdata || {};
|
|
SUB_LOGGED_IN = true;
|
|
localStorage.setItem('TELESEC_BYPASS_ID', SUB_LOGGED_IN_ID);
|
|
SetPages();
|
|
open_page(location.hash.replace('#', '').split("?")[0]);
|
|
}
|
|
if (!data) {
|
|
const persona = { Nombre: 'Admin (bypass)', Roles: 'ADMIN,' };
|
|
DB.put('personas', bypassId, persona)
|
|
.then(() => finish(persona, bypassId))
|
|
.catch((e) => {
|
|
console.warn('AC_BYPASS create error', e);
|
|
open_page('login');
|
|
});
|
|
} else {
|
|
if (typeof data === 'string') {
|
|
TS_decrypt(
|
|
data,
|
|
SECRET,
|
|
(pdata) => finish(pdata, bypassId),
|
|
'personas',
|
|
bypassId
|
|
);
|
|
} else {
|
|
finish(data, bypassId);
|
|
}
|
|
}
|
|
})
|
|
.catch((e) => {
|
|
console.warn('AC_BYPASS persona check error', e);
|
|
open_page('login');
|
|
});
|
|
} else {
|
|
// DB not ready, fallback to login page
|
|
open_page('login');
|
|
}
|
|
} else {
|
|
open_page('login');
|
|
}
|
|
} else {
|
|
SetPages();
|
|
open_page(location.hash.replace('#', '').split("?")[0]);
|
|
}
|
|
clearInterval(BootIntervalID);
|
|
}
|
|
});
|
|
}, 750);
|
|
|
|
const tabs = document.querySelectorAll('.ribbon-tab');
|
|
const detailTabs = {
|
|
modulos: document.getElementById('tab-modulos'),
|
|
buscar: document.getElementById('tab-buscar'),
|
|
};
|
|
|
|
tabs.forEach((tab) => {
|
|
tab.addEventListener('click', () => {
|
|
const selected = tab.getAttribute('data-tab');
|
|
|
|
// Toggle details
|
|
for (const [key, detailsEl] of Object.entries(detailTabs)) {
|
|
if (key === selected) {
|
|
detailsEl.setAttribute('open', '');
|
|
} else {
|
|
detailsEl.removeAttribute('open');
|
|
}
|
|
}
|
|
|
|
// Toggle tab active class
|
|
tabs.forEach((t) => t.classList.remove('active'));
|
|
tab.classList.add('active');
|
|
});
|
|
});
|
|
|
|
// Global Search Functionality
|
|
function GlobalSearch() {
|
|
const searchData = {};
|
|
const allSearchableModules = [
|
|
{
|
|
role: 'personas',
|
|
key: 'personas',
|
|
title: 'Personas',
|
|
icon: 'static/appico/File_Person.svg',
|
|
fields: ['Nombre', 'Region', 'Notas', 'email'],
|
|
},
|
|
{
|
|
role: 'materiales',
|
|
key: 'materiales',
|
|
title: 'Materiales',
|
|
icon: 'static/appico/Database.svg',
|
|
fields: ['Nombre', 'Referencia', 'Ubicacion', 'Notas'],
|
|
},
|
|
{
|
|
role: 'supercafe',
|
|
key: 'supercafe',
|
|
title: 'SuperCafé',
|
|
icon: 'static/appico/Coffee.svg',
|
|
fields: ['Persona', 'Comanda', 'Estado'],
|
|
},
|
|
{
|
|
role: 'comedor',
|
|
key: 'comedor',
|
|
title: 'Comedor',
|
|
icon: 'static/appico/Meal.svg',
|
|
fields: ['Fecha', 'Platos'],
|
|
},
|
|
{
|
|
role: 'notas',
|
|
key: 'notas',
|
|
title: 'Notas',
|
|
icon: 'static/appico/Notepad.svg',
|
|
fields: ['Asunto', 'Contenido', 'Autor'],
|
|
},
|
|
{
|
|
role: 'notificaciones',
|
|
key: 'notificaciones',
|
|
title: 'Avisos',
|
|
icon: 'static/appico/Alert_Warning.svg',
|
|
fields: ['Asunto', 'Mensaje', 'Origen', 'Destino'],
|
|
},
|
|
{
|
|
role: 'aulas',
|
|
key: 'aulas_solicitudes',
|
|
title: 'Solicitudes de Aulas',
|
|
icon: 'static/appico/Classroom.svg',
|
|
fields: ['Asunto', 'Contenido', 'Solicitante'],
|
|
},
|
|
{
|
|
role: 'aulas',
|
|
key: 'aulas_informes',
|
|
title: 'Informes de Aulas',
|
|
icon: 'static/appico/Newspaper.svg',
|
|
fields: ['Asunto', 'Contenido', 'Autor', 'Fecha'],
|
|
},
|
|
];
|
|
|
|
// Filter modules based on user permissions
|
|
const searchableModules = allSearchableModules.filter((module) => {
|
|
return checkRole(module.role);
|
|
});
|
|
|
|
// Load all data from modules
|
|
function loadAllData() {
|
|
searchableModules.forEach((module) => {
|
|
searchData[module.key] = {};
|
|
DB.map(module.key, (data, key) => {
|
|
if (!data) return;
|
|
|
|
function processData(processedData) {
|
|
if (processedData && typeof processedData === 'object') {
|
|
searchData[module.key][key] = {
|
|
_key: key,
|
|
_module: module.key,
|
|
_title: module.title,
|
|
_icon: module.icon,
|
|
...processedData,
|
|
};
|
|
}
|
|
}
|
|
|
|
if (typeof data === 'string') {
|
|
TS_decrypt(data, SECRET, processData);
|
|
} else {
|
|
processData(data);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Perform search across all modules
|
|
function performSearch(searchTerm) {
|
|
if (!searchTerm || searchTerm.length < 2) return [];
|
|
|
|
const results = [];
|
|
const searchLower = searchTerm.toLowerCase();
|
|
|
|
searchableModules.forEach((module) => {
|
|
const moduleData = searchData[module.key] || {};
|
|
|
|
Object.values(moduleData).forEach((item) => {
|
|
if (!item) return;
|
|
|
|
let relevanceScore = 0;
|
|
let matchedFields = [];
|
|
|
|
// Search in key/ID
|
|
if (item._key && item._key.toLowerCase().includes(searchLower)) {
|
|
relevanceScore += 10;
|
|
matchedFields.push('ID');
|
|
}
|
|
|
|
// Search in configured fields
|
|
module.fields.forEach((field) => {
|
|
const value = item[field];
|
|
if (!value) return;
|
|
|
|
let searchValue = '';
|
|
|
|
// Handle special field types
|
|
if (field === 'Persona' && SC_Personas[value]) {
|
|
searchValue = SC_Personas[value].Nombre || '';
|
|
} else if (field === 'Comanda' && typeof value === 'string') {
|
|
try {
|
|
const comandaData = JSON.parse(value);
|
|
searchValue = Object.values(comandaData).join(' ');
|
|
} catch (e) {
|
|
searchValue = value;
|
|
}
|
|
} else {
|
|
searchValue = String(value);
|
|
}
|
|
|
|
if (searchValue.toLowerCase().includes(searchLower)) {
|
|
relevanceScore += field === 'Nombre' || field === 'Asunto' ? 5 : 2;
|
|
matchedFields.push(field);
|
|
}
|
|
});
|
|
|
|
if (relevanceScore > 0) {
|
|
results.push({
|
|
...item,
|
|
_relevance: relevanceScore,
|
|
_matchedFields: matchedFields,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
return results.sort((a, b) => b._relevance - a._relevance);
|
|
}
|
|
|
|
// Render search results
|
|
function renderResults(results, container) {
|
|
if (results.length === 0) {
|
|
container.innerHTML = html`
|
|
<fieldset>
|
|
<legend>Sin resultados</legend>
|
|
<div>🚫 No se encontraron resultados</div>
|
|
<p>Prueba con otros términos de búsqueda o usa filtros diferentes</p>
|
|
</fieldset>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
// Group by module
|
|
const groupedResults = {};
|
|
results.forEach((result) => {
|
|
if (!groupedResults[result._module]) {
|
|
groupedResults[result._module] = [];
|
|
}
|
|
groupedResults[result._module].push(result);
|
|
});
|
|
|
|
Object.entries(groupedResults).forEach(([moduleKey, moduleResults]) => {
|
|
const module = searchableModules.find((m) => m.key === moduleKey);
|
|
if (!module) return;
|
|
|
|
html += `
|
|
<fieldset>
|
|
<legend>
|
|
<img src="${module.icon}" height="20"> ${module.title} (${moduleResults.length})
|
|
</legend>
|
|
`;
|
|
|
|
moduleResults.slice(0, 5).forEach((result) => {
|
|
let title = result.Nombre || result.Asunto || result._key;
|
|
let subtitle = '';
|
|
|
|
// Handle comedor specific display
|
|
if (result._module === 'comedor') {
|
|
title = result.Fecha
|
|
? `Menú del ${result.Fecha.split('-').reverse().join('/')}`
|
|
: result._key;
|
|
if (result.Platos) {
|
|
subtitle = `🍽️ ${result.Platos.substring(0, 50)}${
|
|
result.Platos.length > 50 ? '...' : ''
|
|
}`;
|
|
}
|
|
} else {
|
|
// Default display for other modules
|
|
if (result.Persona && SC_Personas[result.Persona]) {
|
|
subtitle = `👤 ${SC_Personas[result.Persona].Nombre}`;
|
|
}
|
|
if (result.Fecha) {
|
|
const fecha = result.Fecha.split('-').reverse().join('/');
|
|
subtitle += subtitle ? ` • 📅 ${fecha}` : `📅 ${fecha}`;
|
|
}
|
|
if (result.Region) {
|
|
subtitle += subtitle ? ` • 🌍 ${result.Region}` : `🌍 ${result.Region}`;
|
|
}
|
|
}
|
|
|
|
html += `
|
|
<button onclick="navigateToResult('${moduleKey}', '${result._key}')" class="button">
|
|
<strong>${title}</strong>
|
|
${subtitle ? `<br><small>${subtitle}</small>` : ''}
|
|
<br><code>📍 ${result._matchedFields.join(', ')}</code>
|
|
</button>
|
|
`;
|
|
});
|
|
|
|
if (moduleResults.length > 5) {
|
|
let moreLink = moduleKey;
|
|
if (moduleKey === 'aulas_solicitudes') {
|
|
moreLink = 'aulas,solicitudes';
|
|
} else if (moduleKey === 'aulas_informes') {
|
|
moreLink = 'aulas,informes';
|
|
}
|
|
|
|
html += `
|
|
<hr>
|
|
<button onclick="setUrlHash('${moreLink}')" class="btn8">
|
|
Ver ${moduleResults.length - 5} resultados más en ${module.title}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
html += '</fieldset>';
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
return {
|
|
loadAllData,
|
|
performSearch,
|
|
renderResults,
|
|
getAccessibleModules: () => searchableModules,
|
|
};
|
|
}
|
|
|
|
// Helper function to navigate to search results
|
|
function navigateToResult(moduleKey, resultKey) {
|
|
switch (moduleKey) {
|
|
case 'aulas_solicitudes':
|
|
setUrlHash('aulas,solicitudes,' + resultKey);
|
|
break;
|
|
case 'aulas_informes':
|
|
setUrlHash('aulas,informes,' + resultKey);
|
|
break;
|
|
default:
|
|
setUrlHash(moduleKey + ',' + resultKey);
|
|
}
|
|
}
|