Añadir funcionalidad de selección de pictogramas en el formulario del comedor y refactorizar campos de entrada

This commit is contained in:
naielv
2026-02-23 00:23:11 +01:00
parent e0da65811e
commit aa993df2bf
4 changed files with 491 additions and 10 deletions

View File

@@ -428,4 +428,19 @@ fieldset legend {
}
pre {
font-size: 15px;
}
.picto {
height: 125px;
width: 100px;
border: 2.5px solid black;
border-radius: 5px;
text-align: center;
background: white;
margin-bottom: 20px;
margin-left: auto;
margin-right: auto;
}
.picto b {
padding-top: 40px;
display: inline-block;
}

View File

@@ -35,7 +35,7 @@ function open_page(params) {
PAGES[app].index();
return;
}
PAGES[app].edit(path[1]);
PAGES[app].edit(path.slice(1).join(','));
}
function setUrlHash(hash) {

View File

@@ -55,6 +55,368 @@ const debounce = (id, callback, wait, 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 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',
@@ -922,6 +1284,11 @@ function TS_IndexElement(
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;
@@ -1037,6 +1404,33 @@ function TS_IndexElement(
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);

View File

@@ -12,7 +12,14 @@ PAGES.comedor = {
}
var nameh1 = safeuuid();
var field_fecha = safeuuid();
var field_platos = safeuuid();
var field_tipo = safeuuid();
var field_primero = safeuuid();
var field_segundo = safeuuid();
var field_postre = safeuuid();
var btn_picto_primero = safeuuid();
var btn_picto_segundo = safeuuid();
var btn_picto_postre = safeuuid();
var debounce_picto = safeuuid();
var btn_guardar = safeuuid();
var btn_borrar = safeuuid();
container.innerHTML = html`
@@ -24,18 +31,55 @@ PAGES.comedor = {
<input type="date" id="${field_fecha}" value="" /><br /><br />
</label>
<label>
Platos<br />
<textarea id="${field_platos}"></textarea><br /><br />
Tipo<br />
<input type="text" id="${field_tipo}" value="" /><br /><br />
</label>
<label>
Primero<br />
<input type="text" id="${field_primero}" value="" /><br />
<div class="picto" id="${btn_picto_primero}"></div>
</label>
<label>
Segundo<br />
<input type="text" id="${field_segundo}" value="" /><br />
<div class="picto" id="${btn_picto_segundo}"></div>
</label>
<label>
Postre<br />
<input type="text" id="${field_postre}" value="" /><br />
<div class="picto" id="${btn_picto_postre}"></div>
</label>
<button class="btn5" id="${btn_guardar}">Guardar</button>
<button class="rojo" id="${btn_borrar}">Borrar</button>
</fieldset>
`;
const pictogramSelector = TS_CreateArasaacSelector({
modal: true,
debounceId: debounce_picto,
onPick: (context, item) => {
TS_applyPictoValue(context.pictoId, {
text: item.label,
arasaacId: String(item.id),
});
},
});
document.getElementById(btn_picto_primero).onclick = () =>
pictogramSelector.open({ pictoId: btn_picto_primero });
document.getElementById(btn_picto_segundo).onclick = () =>
pictogramSelector.open({ pictoId: btn_picto_segundo });
document.getElementById(btn_picto_postre).onclick = () =>
pictogramSelector.open({ pictoId: btn_picto_postre });
DB.get('comedor', mid).then((data) => {
function load_data(data, ENC = '') {
document.getElementById(nameh1).innerText = mid;
document.getElementById(field_fecha).value = data['Fecha'] || mid || CurrentISODate();
document.getElementById(field_platos).value = data['Platos'] || '';
document.getElementById(field_tipo).value = data['Tipo'] || '';
document.getElementById(field_primero).value = data['Primero'] || '';
document.getElementById(field_segundo).value = data['Segundo'] || '';
document.getElementById(field_postre).value = data['Postre'] || '';
TS_applyPictoValue(btn_picto_primero, data['Primero_Picto'] || '');
TS_applyPictoValue(btn_picto_segundo, data['Segundo_Picto'] || '');
TS_applyPictoValue(btn_picto_postre, data['Postre_Picto'] || '');
}
if (typeof data == 'string') {
TS_decrypt(
@@ -60,18 +104,25 @@ PAGES.comedor = {
guardarBtn.style.opacity = '0.5';
const newDate = document.getElementById(field_fecha).value;
const newTipo = document.getElementById(field_tipo).value.trim();
var data = {
Fecha: newDate,
Platos: document.getElementById(field_platos).value,
Tipo: newTipo,
Primero: document.getElementById(field_primero).value.trim(),
Segundo: document.getElementById(field_segundo).value.trim(),
Postre: document.getElementById(field_postre).value.trim(),
Primero_Picto: TS_getPictoValue(btn_picto_primero),
Segundo_Picto: TS_getPictoValue(btn_picto_segundo),
Postre_Picto: TS_getPictoValue(btn_picto_postre),
};
// If the date has changed, we need to delete the old entry
if (mid !== newDate && mid !== '') {
if (mid !== newDate + "," + newTipo && mid !== '') {
DB.del('comedor', mid);
}
document.getElementById('actionStatus').style.display = 'block';
DB.put('comedor', newDate, data)
DB.put('comedor', newDate + "," + newTipo, data)
.then(() => {
toastr.success('Guardado!');
setTimeout(() => {
@@ -120,10 +171,31 @@ PAGES.comedor = {
label: 'Fecha',
},
{
key: 'Platos',
key: 'Tipo',
type: 'raw',
default: '',
label: 'Platos',
label: 'Tipo',
},
{
key: 'Primero_Picto',
type: 'picto',
default: '',
label: 'Primero',
labelkey: 'Primero',
},
{
key: 'Segundo_Picto',
type: 'picto',
default: '',
label: 'Segundo',
labelkey: 'Segundo',
},
{
key: 'Postre_Picto',
type: 'picto',
default: '',
label: 'Postre',
labelkey: 'Postre',
},
],
'comedor',