2ª actualización importante
1
assets/static/appico/account-group.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z" /></svg>
|
||||
|
After Width: | Height: | Size: 655 B |
BIN
assets/static/appico/apple.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
assets/static/appico/application_enterprise.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
assets/static/appico/barcode.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
assets/static/appico/calendar.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
assets/static/appico/components.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
assets/static/appico/credit_cards.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
assets/static/appico/cup.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/static/appico/edit.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
assets/static/appico/gear_edit.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
assets/static/appico/house.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
assets/static/appico/shelf.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
1
assets/static/appico/silverware-fork-knife.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11,9H9V2H7V9H5V2H3V9C3,11.12 4.66,12.84 6.75,12.97V22H9.25V12.97C11.34,12.84 13,11.12 13,9V2H11V9M16,6V14H18.5V22H21V2C18.24,2 16,4.24 16,6Z" /></svg>
|
||||
|
After Width: | Height: | Size: 220 B |
BIN
assets/static/appico/users.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
assets/static/appico/view.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
@@ -196,6 +196,7 @@ table {
|
||||
|
||||
table tr th {
|
||||
line-break: auto;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
table tr td {
|
||||
@@ -365,7 +366,7 @@ hr {
|
||||
|
||||
.ribbon-panel {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 3px;
|
||||
background-color: #c8d4eb;
|
||||
border: 1px solid #a2a9b9;
|
||||
overflow-x: auto;
|
||||
@@ -382,10 +383,15 @@ hr {
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1px solid lightskyblue;
|
||||
background: white;
|
||||
padding: 4px;
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.ribbon-button img {
|
||||
height: 48px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -1131,7 +1131,7 @@ function SetPages() {
|
||||
a.href = "#" + key;
|
||||
label.innerText = PAGES[key].Title;
|
||||
label.className = "label";
|
||||
img.src = PAGES[key].icon || "static/appico/File_Plugin.svg";
|
||||
img.src = PAGES[key].icon || "static/appico/application_enterprise.png";
|
||||
a.append(img, label);
|
||||
document.getElementById("appendApps2").append(a);
|
||||
});
|
||||
@@ -1142,7 +1142,7 @@ function SetPages() {
|
||||
a.href = "#index,qr";
|
||||
label.innerText = "Escanear QR";
|
||||
label.className = "label";
|
||||
img.src = "static/appico/App_CodyCam.svg";
|
||||
img.src = "static/appico/barcode.png";
|
||||
a.append(img, label);
|
||||
document.getElementById("appendApps2").append(a);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ PERMS["aulas"] = "Aulas (Solo docentes!)";
|
||||
PAGES.aulas = {
|
||||
//navcss: "btn1",
|
||||
Title: "Gest-Aula",
|
||||
icon: "static/appico/Classroom.svg",
|
||||
icon: "static/appico/components.png",
|
||||
AccessControl: true,
|
||||
index: function () {
|
||||
if (!checkRole("aulas")) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PAGES.buscar = {
|
||||
navcss: "btn1",
|
||||
icon: "static/appico/File_Plugin.svg",
|
||||
icon: "static/appico/view.svg",
|
||||
Title: "Buscar",
|
||||
AccessControl: true,
|
||||
Esconder: true,
|
||||
|
||||
536
src/page/chat.js
@@ -1,536 +0,0 @@
|
||||
PERMS["chat"] = "Chat"
|
||||
PERMS["chat:edit"] = "> Escribir"
|
||||
|
||||
// Global chat notification system
|
||||
PAGES.chat_notifications = {
|
||||
listener: null,
|
||||
Esconder: true,
|
||||
lastMessageTime: null, // Start with null to allow all messages initially
|
||||
isActive: false,
|
||||
|
||||
init: function() {
|
||||
console.log("Initializing chat notifications...");
|
||||
console.log("isActive:", this.isActive);
|
||||
console.log("checkRole available:", typeof checkRole);
|
||||
console.log("checkRole('chat'):", typeof checkRole === 'function' ? checkRole("chat") : "function not available");
|
||||
|
||||
if (this.isActive) {
|
||||
console.log("Chat notifications already active");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user has chat permissions (with fallback)
|
||||
if (typeof checkRole === 'function' && !checkRole("chat")) {
|
||||
console.log("No chat permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
this.isActive = true;
|
||||
console.log("Starting chat notifications listener");
|
||||
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
var dayPath = `chat_${today}`;
|
||||
|
||||
console.log("Listening for notifications on:", dayPath);
|
||||
console.log("TABLE:", TABLE);
|
||||
|
||||
// Set initial timestamp to current time to only notify for new messages from now on
|
||||
if (!this.lastMessageTime) {
|
||||
this.lastMessageTime = new Date().toISOString();
|
||||
console.log("Set initial lastMessageTime:", this.lastMessageTime);
|
||||
}
|
||||
|
||||
// Listen for new messages on today's chat
|
||||
this.listener = gun.get(TABLE).get(dayPath).map().on((data, messageId, _msg, _ev) => {
|
||||
console.log("Notification listener received data:", data, "messageId:", messageId);
|
||||
|
||||
if (data === null) return; // Ignore deletions
|
||||
|
||||
if (typeof data === "string") {
|
||||
// Encrypted message
|
||||
console.log("Decrypting notification message...");
|
||||
TS_decrypt(data, SECRET, (decryptedData) => {
|
||||
console.log("Decrypted notification data:", decryptedData);
|
||||
this.handleNewMessage(decryptedData, messageId);
|
||||
});
|
||||
} else {
|
||||
// Unencrypted message
|
||||
console.log("Processing unencrypted notification:", data);
|
||||
this.handleNewMessage(data, messageId);
|
||||
}
|
||||
});
|
||||
|
||||
// Add to cleanup listeners
|
||||
EventListeners.GunJS.push(this.listener);
|
||||
console.log("Chat notifications initialized successfully");
|
||||
},
|
||||
|
||||
handleNewMessage: function(messageData, messageId) {
|
||||
console.log("Handling new message for notification:", messageData);
|
||||
console.log("Current lastMessageTime:", this.lastMessageTime);
|
||||
console.log("Message timestamp:", messageData.timestamp);
|
||||
console.log("Message authorId:", messageData.authorId);
|
||||
console.log("Current user ID:", SUB_LOGGED_IN_ID);
|
||||
|
||||
// Don't notify for our own messages
|
||||
if (messageData.authorId === SUB_LOGGED_IN_ID) {
|
||||
console.log("Skipping notification - own message");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't notify for old messages (only if we have a baseline)
|
||||
if (this.lastMessageTime && messageData.timestamp && messageData.timestamp <= this.lastMessageTime) {
|
||||
console.log("Skipping notification - old message");
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't notify if user is currently viewing the chat page
|
||||
var currentHash = location.hash.replace("#", "");
|
||||
console.log("Current page hash:", currentHash);
|
||||
if (currentHash === 'chat') {
|
||||
console.log("Skipping notification - user viewing chat");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update last message time
|
||||
if (messageData.timestamp) {
|
||||
this.lastMessageTime = messageData.timestamp;
|
||||
console.log("Updated lastMessageTime to:", this.lastMessageTime);
|
||||
}
|
||||
|
||||
// Show notification
|
||||
var author = messageData.author || 'Usuario Anónimo';
|
||||
var preview = messageData.text.length > 50 ?
|
||||
messageData.text.substring(0, 50) + '...' :
|
||||
messageData.text;
|
||||
|
||||
console.log("Showing notification for:", author, "-", preview);
|
||||
console.log("Testing toastr availability:", typeof toastr);
|
||||
|
||||
// Test notification to verify toastr works
|
||||
console.log("Attempting to show notification...");
|
||||
|
||||
toastr.info(
|
||||
`<strong>${author}:</strong><br>${preview}`,
|
||||
'💬 Nuevo mensaje en Chat',
|
||||
{
|
||||
onclick: function() {
|
||||
console.log("Notification clicked, navigating to chat");
|
||||
setUrlHash('chat');
|
||||
},
|
||||
timeOut: 8000,
|
||||
extendedTimeOut: 2000,
|
||||
closeButton: true,
|
||||
progressBar: true,
|
||||
positionClass: 'toast-top-right',
|
||||
preventDuplicates: true,
|
||||
newestOnTop: true,
|
||||
escapeHtml: false
|
||||
}
|
||||
);
|
||||
var msg = `Nuevo mensaje en chat de ${author}. ${preview}`;
|
||||
let utterance = new SpeechSynthesisUtterance(msg);
|
||||
utterance.rate = 0.9;
|
||||
speechSynthesis.speak(utterance);
|
||||
console.log("Notification call completed");
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this.listener) {
|
||||
this.listener.off();
|
||||
}
|
||||
this.isActive = false;
|
||||
},
|
||||
|
||||
// Test function to manually trigger a notification
|
||||
testNotification: function() {
|
||||
console.log("Testing notification manually...");
|
||||
console.log("toastr available:", typeof toastr);
|
||||
|
||||
toastr.success("¡Funciona! Esta es una notificación de prueba.", "Test de Notificación", {
|
||||
timeOut: 5000,
|
||||
closeButton: true,
|
||||
progressBar: true,
|
||||
positionClass: 'toast-top-right'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
PAGES.chat = {
|
||||
navcss: "btn4",
|
||||
icon: "static/appico/Chat.svg",
|
||||
AccessControl: true,
|
||||
Title: "Chat",
|
||||
index: function() {
|
||||
console.log("Chat index function called");
|
||||
console.log("SUB_LOGGED_IN:", SUB_LOGGED_IN);
|
||||
console.log("checkRole function exists:", typeof checkRole);
|
||||
|
||||
if (!checkRole("chat")) {
|
||||
console.log("No chat permission, redirecting to index");
|
||||
setUrlHash("index");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Chat permission granted, initializing chat");
|
||||
|
||||
// Stop global notifications when viewing chat
|
||||
PAGES.chat_notifications.destroy();
|
||||
|
||||
var messagesList = safeuuid();
|
||||
var messageInput = safeuuid();
|
||||
var sendButton = safeuuid();
|
||||
var daySelector = safeuuid();
|
||||
var currentDay = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
|
||||
|
||||
console.log("Creating chat UI with currentDay:", currentDay);
|
||||
|
||||
container.innerHTML = `
|
||||
<h1>💬 Chat - ${GROUPID}</h1>
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="${daySelector}">Seleccionar día:</label>
|
||||
<input type="date" id="${daySelector}" value="${currentDay}"
|
||||
style="margin-left: 10px; padding: 5px;">
|
||||
<button onclick="PAGES.chat.loadDay()" style="margin-left: 10px;">Cargar</button>
|
||||
</div>
|
||||
|
||||
<div style="border: 2px solid #ccc; border-radius: 8px; padding: 10px;
|
||||
height: 400px; overflow-y: auto; background-color: #f9f9f9;
|
||||
margin-bottom: 15px;" id="chat-container">
|
||||
<ul id="${messagesList}" style="list-style: none; padding: 0; margin: 0;">
|
||||
<!-- Los mensajes aparecerán aquí -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<input type="text" id="${messageInput}" placeholder="Escribe tu mensaje..."
|
||||
style="flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px;"
|
||||
onkeypress="if(event.key==='Enter') document.getElementById('${sendButton}').click()">
|
||||
<button id="${sendButton}" class="btn5" style="padding: 10px 20px;">
|
||||
Enviar
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px; font-size: 12px; color: #666;">
|
||||
<strong>Usuario:</strong> ${SUB_LOGGED_IN_DETAILS.Nombre || 'Usuario Anónimo'}
|
||||
| <strong>Conectado como:</strong> ${GROUPID}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Store element references
|
||||
PAGES.chat.messagesList = document.getElementById(messagesList);
|
||||
PAGES.chat.messageInput = document.getElementById(messageInput);
|
||||
PAGES.chat.sendButton = document.getElementById(sendButton);
|
||||
PAGES.chat.daySelector = document.getElementById(daySelector);
|
||||
PAGES.chat.currentDay = currentDay;
|
||||
|
||||
// Set up event listeners
|
||||
PAGES.chat.sendButton.onclick = PAGES.chat.sendMessage;
|
||||
|
||||
// Load today's messages
|
||||
PAGES.chat.loadDay();
|
||||
},
|
||||
|
||||
loadDay: function() {
|
||||
var selectedDay = PAGES.chat.daySelector.value;
|
||||
PAGES.chat.currentDay = selectedDay;
|
||||
|
||||
console.log("Loading chat for day:", selectedDay);
|
||||
console.log("TABLE:", TABLE);
|
||||
console.log("SECRET:", SECRET ? "SET" : "NOT SET");
|
||||
|
||||
// Clear current messages
|
||||
PAGES.chat.messagesList.innerHTML = '<li style="text-align: center; color: #666; padding: 10px;">Cargando mensajes...</li>';
|
||||
|
||||
// Clear any existing listeners
|
||||
if (PAGES.chat.currentListener) {
|
||||
PAGES.chat.currentListener.off();
|
||||
}
|
||||
|
||||
// Listen for messages from the selected day
|
||||
var dayPath = `chat_${selectedDay}`;
|
||||
console.log("Listening on path:", dayPath);
|
||||
|
||||
PAGES.chat.currentListener = gun.get(TABLE).get(dayPath).map().on((data, messageId, _msg, _ev) => {
|
||||
console.log("Received chat data:", data, "messageId:", messageId);
|
||||
|
||||
if (data === null) {
|
||||
// Message deleted
|
||||
PAGES.chat.removeMessage(messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof data === "string") {
|
||||
// Encrypted message
|
||||
console.log("Decrypting message...");
|
||||
TS_decrypt(data, SECRET, (decryptedData) => {
|
||||
console.log("Decrypted data:", decryptedData);
|
||||
PAGES.chat.displayMessage(decryptedData, messageId);
|
||||
});
|
||||
} else {
|
||||
// Unencrypted message (shouldn't happen in production)
|
||||
console.log("Displaying unencrypted message:", data);
|
||||
PAGES.chat.displayMessage(data, messageId);
|
||||
}
|
||||
});
|
||||
|
||||
// Add listener to cleanup
|
||||
EventListeners.GunJS.push(PAGES.chat.currentListener);
|
||||
|
||||
// Clear loading message after a short delay
|
||||
setTimeout(() => {
|
||||
var loadingMsg = PAGES.chat.messagesList.querySelector('li');
|
||||
if (loadingMsg && loadingMsg.textContent.includes('Cargando')) {
|
||||
console.log("Clearing loading message");
|
||||
loadingMsg.remove();
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
sendMessage: function() {
|
||||
var messageText = PAGES.chat.messageInput.value.trim();
|
||||
|
||||
console.log("Sending message:", messageText);
|
||||
console.log("User details:", SUB_LOGGED_IN_DETAILS);
|
||||
console.log("User ID:", SUB_LOGGED_IN_ID);
|
||||
|
||||
if (!messageText) {
|
||||
toastr.warning("Por favor escribe un mensaje");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkRole("chat:edit")) {
|
||||
toastr.error("No tienes permisos para escribir en el chat");
|
||||
console.log("Permission denied for chat:edit");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create message object
|
||||
var messageData = {
|
||||
text: messageText,
|
||||
author: SUB_LOGGED_IN_DETAILS.Nombre || 'Usuario Anónimo',
|
||||
authorId: SUB_LOGGED_IN_ID || 'unknown',
|
||||
timestamp: new Date().toISOString(),
|
||||
day: PAGES.chat.currentDay
|
||||
};
|
||||
|
||||
console.log("Message data:", messageData);
|
||||
|
||||
// Generate unique message ID
|
||||
var messageId = safeuuid();
|
||||
console.log("Generated message ID:", messageId);
|
||||
|
||||
// Encrypt and save message
|
||||
TS_encrypt(messageData, SECRET, (encrypted) => {
|
||||
var dayPath = `chat_${PAGES.chat.currentDay}`;
|
||||
console.log("Saving to path:", dayPath);
|
||||
console.log("Encrypted data:", encrypted);
|
||||
|
||||
betterGunPut(gun.get(TABLE).get(dayPath).get(messageId), encrypted);
|
||||
|
||||
// Clear input
|
||||
PAGES.chat.messageInput.value = '';
|
||||
|
||||
// Show success feedback
|
||||
toastr.success("Mensaje enviado");
|
||||
});
|
||||
},
|
||||
|
||||
displayMessage: function(messageData, messageId) {
|
||||
// Check if message already exists
|
||||
var existingMessage = document.getElementById(`msg-${messageId}`);
|
||||
if (existingMessage) {
|
||||
existingMessage.remove();
|
||||
}
|
||||
|
||||
// Create message element
|
||||
var messageItem = document.createElement('li');
|
||||
messageItem.id = `msg-${messageId}`;
|
||||
messageItem.style.cssText = `
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
border-left: 4px solid #007cba;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
`;
|
||||
|
||||
// Format timestamp
|
||||
var timestamp = new Date(messageData.timestamp);
|
||||
var timeString = timestamp.toLocaleTimeString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
// Check if message is from current user
|
||||
var isOwnMessage = messageData.authorId === SUB_LOGGED_IN_ID;
|
||||
var messageColor = isOwnMessage ? '#e3f2fd' : 'white';
|
||||
var borderColor = isOwnMessage ? '#2196f3' : '#007cba';
|
||||
|
||||
messageItem.style.backgroundColor = messageColor;
|
||||
messageItem.style.borderLeftColor = borderColor;
|
||||
|
||||
// Create message content
|
||||
var messageContent = `
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 5px;">
|
||||
<strong style="color: #333; font-size: 14px;">${messageData.author}</strong>
|
||||
<span style="color: #666; font-size: 12px;">${timeString}</span>
|
||||
</div>
|
||||
<div style="color: #444; line-height: 1.4;">
|
||||
${messageData.text.replace(/\n/g, '<br>')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add delete button for own messages or if user has admin permissions
|
||||
if (isOwnMessage || checkRole("admin")) {
|
||||
messageContent += `
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<button onclick="PAGES.chat.deleteMessage('${messageId}')"
|
||||
style="background: none; border: none; color: #f44336;
|
||||
cursor: pointer; font-size: 12px; padding: 2px 5px;"
|
||||
title="Borrar mensaje">
|
||||
🗑️ Borrar
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
messageItem.innerHTML = messageContent;
|
||||
|
||||
// Insert message in chronological order
|
||||
var inserted = false;
|
||||
var existingMessages = Array.from(PAGES.chat.messagesList.children);
|
||||
|
||||
for (var i = 0; i < existingMessages.length; i++) {
|
||||
var existingMsg = existingMessages[i];
|
||||
var existingId = existingMsg.id.replace('msg-', '');
|
||||
var existingData = PAGES.chat.messageCache && PAGES.chat.messageCache[existingId];
|
||||
|
||||
if (existingData && new Date(messageData.timestamp) < new Date(existingData.timestamp)) {
|
||||
PAGES.chat.messagesList.insertBefore(messageItem, existingMsg);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
PAGES.chat.messagesList.appendChild(messageItem);
|
||||
}
|
||||
|
||||
// Cache message data for sorting
|
||||
if (!PAGES.chat.messageCache) {
|
||||
PAGES.chat.messageCache = {};
|
||||
}
|
||||
PAGES.chat.messageCache[messageId] = messageData;
|
||||
|
||||
// Auto-scroll to bottom if user is viewing current day
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
if (PAGES.chat.currentDay === today) {
|
||||
var chatContainer = document.getElementById('chat-container');
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
},
|
||||
|
||||
removeMessage: function(messageId) {
|
||||
var messageElement = document.getElementById(`msg-${messageId}`);
|
||||
if (messageElement) {
|
||||
messageElement.remove();
|
||||
}
|
||||
|
||||
// Remove from cache
|
||||
if (PAGES.chat.messageCache) {
|
||||
delete PAGES.chat.messageCache[messageId];
|
||||
}
|
||||
},
|
||||
|
||||
deleteMessage: function(messageId) {
|
||||
if (confirm("¿Estás seguro de que quieres borrar este mensaje?")) {
|
||||
var dayPath = `chat_${PAGES.chat.currentDay}`;
|
||||
betterGunPut(gun.get(TABLE).get(dayPath).get(messageId), null);
|
||||
toastr.success("Mensaje borrado");
|
||||
}
|
||||
},
|
||||
|
||||
// Cleanup when leaving the page
|
||||
cleanup: function() {
|
||||
if (PAGES.chat.currentListener) {
|
||||
PAGES.chat.currentListener.off();
|
||||
}
|
||||
PAGES.chat.messageCache = {};
|
||||
|
||||
// Restart global notifications when leaving chat
|
||||
setTimeout(() => {
|
||||
PAGES.chat_notifications.init();
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// Add cleanup to the global page change handler
|
||||
(function() {
|
||||
var originalOpenPage = open_page;
|
||||
open_page = function(params) {
|
||||
// Cleanup chat if we're leaving it
|
||||
if (PAGES.chat && PAGES.chat.cleanup) {
|
||||
PAGES.chat.cleanup();
|
||||
}
|
||||
return originalOpenPage(params);
|
||||
};
|
||||
})();
|
||||
|
||||
// Initialize global chat notifications when user is logged in
|
||||
(function() {
|
||||
function initChatNotifications() {
|
||||
console.log("Attempting to initialize chat notifications...");
|
||||
console.log("SUB_LOGGED_IN:", SUB_LOGGED_IN);
|
||||
console.log("checkRole available:", typeof checkRole);
|
||||
|
||||
// More robust check for login status and permissions
|
||||
if (SUB_LOGGED_IN === true) {
|
||||
console.log("User is logged in, checking permissions...");
|
||||
if (typeof checkRole === 'function') {
|
||||
var hasPermission = checkRole("chat");
|
||||
console.log("User has chat permission:", hasPermission);
|
||||
if (hasPermission) {
|
||||
setTimeout(() => {
|
||||
console.log("Calling PAGES.chat_notifications.init()");
|
||||
PAGES.chat_notifications.init();
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
// Fallback if checkRole is not available yet
|
||||
console.log("checkRole not available, trying to initialize anyway...");
|
||||
setTimeout(() => {
|
||||
PAGES.chat_notifications.init();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to initialize immediately if already logged in
|
||||
console.log("Initial check for chat notifications...");
|
||||
if (typeof SUB_LOGGED_IN !== 'undefined') {
|
||||
initChatNotifications();
|
||||
}
|
||||
|
||||
// Also listen for login events by checking periodically
|
||||
var initInterval = setInterval(() => {
|
||||
if (SUB_LOGGED_IN === true && !PAGES.chat_notifications.isActive) {
|
||||
console.log("Periodic check - attempting notification init");
|
||||
initChatNotifications();
|
||||
}
|
||||
|
||||
// Stop checking after user is logged in and notifications are active
|
||||
if (SUB_LOGGED_IN === true && PAGES.chat_notifications.isActive) {
|
||||
console.log("Notifications active, stopping periodic checks");
|
||||
clearInterval(initInterval);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
// Also try to initialize when page loads
|
||||
setTimeout(() => {
|
||||
if (!PAGES.chat_notifications.isActive) {
|
||||
console.log("Final attempt to initialize notifications");
|
||||
initChatNotifications();
|
||||
}
|
||||
}, 10000);
|
||||
})();
|
||||
@@ -1,10 +1,10 @@
|
||||
PERMS["comedor"] = "Menú comedor"
|
||||
PERMS["comedor"] = "Comedor"
|
||||
PERMS["comedor:edit"] = "> Editar"
|
||||
PAGES.comedor = {
|
||||
navcss: "btn6",
|
||||
icon: "static/appico/Meal.svg",
|
||||
icon: "static/appico/apple.png",
|
||||
AccessControl: true,
|
||||
Title: "Menú comedor",
|
||||
Title: "Comedor",
|
||||
edit: function (mid) {
|
||||
if (!checkRole("comedor:edit")) {setUrlHash("comedor");return}
|
||||
var nameh1 = safeuuid();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PAGES.dataman = {
|
||||
navcss: "btn1",
|
||||
icon: "static/appico/Cogs.svg",
|
||||
icon: "static/appico/gear_edit.png",
|
||||
AccessControl: true,
|
||||
Title: "Ajustes",
|
||||
edit: function (mid) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
PAGES.index = {
|
||||
//navcss: "btn1",
|
||||
Title: "Inicio",
|
||||
icon: "static/appico/house.png",
|
||||
index: function() {
|
||||
container.innerHTML = `
|
||||
<h1>¡Hola, ${SUB_LOGGED_IN_DETAILS.Nombre}!<br>Bienvenidx a %%TITLE%%</h1>
|
||||
<h2>Tienes ${parseFloat(SUB_LOGGED_IN_DETAILS.Monedero_Balance).toString()} € en el monedero.</h2>
|
||||
<h2>Tienes ${fixfloat(parseFloat(SUB_LOGGED_IN_DETAILS.Monedero_Balance)).toString()} € en el monedero.</h2>
|
||||
<em>Utiliza el menú superior para abrir un modulo</em>
|
||||
<br><br>
|
||||
<button class="btn1" onclick="LogOutTeleSec()">Cerrar sesión</button>
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
PERMS["materiales"] = "Materiales"
|
||||
PERMS["materiales:edit"] = "> Editar"
|
||||
PERMS["materiales"] = "Almacén";
|
||||
PERMS["materiales:edit"] = "> Editar";
|
||||
PAGES.materiales = {
|
||||
navcss: "btn2",
|
||||
icon: "static/appico/App_Dropbox.svg",
|
||||
icon: "static/appico/shelf.png",
|
||||
AccessControl: true,
|
||||
Title: "Materiales",
|
||||
Title: "Almacén",
|
||||
edit: function (mid) {
|
||||
if (!checkRole("materiales:edit")) {setUrlHash("materiales");return}
|
||||
if (!checkRole("materiales:edit")) {
|
||||
setUrlHash("materiales");
|
||||
return;
|
||||
}
|
||||
var nameh1 = safeuuid();
|
||||
var field_nombre = safeuuid();
|
||||
var field_revision = safeuuid();
|
||||
var field_cantidad = safeuuid();
|
||||
var field_unidad = safeuuid();
|
||||
var field_cantidad_min = safeuuid();
|
||||
var field_ubicacion = safeuuid();
|
||||
var field_referencia = safeuuid();
|
||||
var field_notas = safeuuid();
|
||||
var btn_guardar = safeuuid();
|
||||
var btn_borrar = safeuuid();
|
||||
var FECHA_ISO = new Date().toISOString().split("T")[0];
|
||||
container.innerHTML = `
|
||||
<h1>Material <code id="${nameh1}"></code></h1>
|
||||
${BuildQR("materiales," + mid, "Este Material")}
|
||||
<fieldset>
|
||||
<label>
|
||||
Referencia<br>
|
||||
<input type="text" id="${field_referencia}" value="?"><br><br>
|
||||
Fecha Revisión<br>
|
||||
<input type="date" id="${field_revision}"> <a onclick='document.getElementById("${field_revision}").value = "${FECHA_ISO}";'>Hoy - Contado todas las existencias</a><br><br>
|
||||
</label>
|
||||
<label>
|
||||
Nombre<br>
|
||||
@@ -61,15 +65,16 @@ PAGES.materiales = {
|
||||
function load_data(data, ENC = "") {
|
||||
document.getElementById(nameh1).innerText = key;
|
||||
document.getElementById(field_nombre).value = data["Nombre"] || "";
|
||||
document.getElementById(field_unidad).value = data["Unidad"] || "unidad(es)";
|
||||
document.getElementById(field_unidad).value =
|
||||
data["Unidad"] || "unidad(es)";
|
||||
document.getElementById(field_cantidad).value =
|
||||
data["Cantidad"] || "";
|
||||
document.getElementById(field_cantidad_min).value =
|
||||
data["Cantidad_Minima"] || "";
|
||||
document.getElementById(field_ubicacion).value =
|
||||
data["Ubicacion"] || "-";
|
||||
document.getElementById(field_referencia).value =
|
||||
data["Referencia"] || "?";
|
||||
document.getElementById(field_revision).value =
|
||||
data["Revision"] || "-";
|
||||
document.getElementById(field_notas).value = data["Notas"] || "";
|
||||
}
|
||||
if (typeof data == "string") {
|
||||
@@ -87,7 +92,7 @@ PAGES.materiales = {
|
||||
Cantidad: document.getElementById(field_cantidad).value,
|
||||
Cantidad_Minima: document.getElementById(field_cantidad_min).value,
|
||||
Ubicacion: document.getElementById(field_ubicacion).value,
|
||||
Referencia: document.getElementById(field_referencia).value,
|
||||
Revision: document.getElementById(field_revision).value,
|
||||
Notas: document.getElementById(field_notas).value,
|
||||
};
|
||||
var enc = TS_encrypt(data, SECRET, (encrypted) => {
|
||||
@@ -111,12 +116,20 @@ PAGES.materiales = {
|
||||
};
|
||||
},
|
||||
index: function () {
|
||||
if (!checkRole("materiales")) {setUrlHash("index");return}
|
||||
if (!checkRole("materiales")) {
|
||||
setUrlHash("index");
|
||||
return;
|
||||
}
|
||||
var btn_new = safeuuid();
|
||||
var select_ubicacion = safeuuid();
|
||||
var check_lowstock = safeuuid();
|
||||
var tableContainer = safeuuid();
|
||||
container.innerHTML = `
|
||||
<h1>Materiales</h1>
|
||||
<h1>Materiales del Almacén</h1>
|
||||
<label>
|
||||
<b>Solo lo que falta:</b>
|
||||
<input type="checkbox" id="${check_lowstock}" style="height: 25px;width: 25px;">
|
||||
</label><br>
|
||||
<label>Filtrar por ubicación:
|
||||
<select id="${select_ubicacion}">
|
||||
<option value="">(Todas)</option>
|
||||
@@ -127,62 +140,68 @@ PAGES.materiales = {
|
||||
`;
|
||||
|
||||
const config = [
|
||||
{ key: "Referencia", label: "Referencia", type: "text", default: "?" },
|
||||
{ key: "Nombre", label: "Nombre", type: "text", default: "?" },
|
||||
{ key: "Ubicacion", label: "Ubicación", type: "text", default: "?" },
|
||||
{
|
||||
key: "Cantidad",
|
||||
label: "Cantidad",
|
||||
{ key: "Revision", label: "F. Revisión", type: "fecha", default: "" },
|
||||
{ key: "Nombre", label: "Nombre", type: "text", default: "" },
|
||||
{ key: "Ubicacion", label: "Ubicación", type: "text", default: "--" },
|
||||
{
|
||||
key: "Cantidad",
|
||||
label: "Cantidad",
|
||||
type: "template",
|
||||
template: (data, element) => {
|
||||
const min = parseFloat(data.Cantidad_Minima);
|
||||
const act = parseFloat(data.Cantidad);
|
||||
const style = act < min ? 'style="background-color: lightcoral;"' : '';
|
||||
element.setAttribute("style", style);
|
||||
element.innerHTML = `${data.Cantidad || "?"} ${data.Unidad || "?"} - (min. ${data.Cantidad_Minima || "?"})`;
|
||||
const sma = act < min ? `<small>- min. ${data.Cantidad_Minima || "?"}</small>` : ""
|
||||
element.innerHTML = `${data.Cantidad || "?"} ${
|
||||
data.Unidad || "?"
|
||||
} ${sma}`;
|
||||
},
|
||||
default: "?"
|
||||
default: "?",
|
||||
},
|
||||
{ key: "Notas", label: "Notas", type: "text", default: "?" }
|
||||
{ key: "Notas", label: "Notas", type: "text", default: "" },
|
||||
];
|
||||
|
||||
// Obtener todas las ubicaciones únicas y poblar el <select>, desencriptando si es necesario
|
||||
gun.get(TABLE).get("materiales").map().once((data, key) => {
|
||||
try {
|
||||
if (!data) return;
|
||||
gun
|
||||
.get(TABLE)
|
||||
.get("materiales")
|
||||
.map()
|
||||
.once((data, key) => {
|
||||
try {
|
||||
if (!data) return;
|
||||
|
||||
function addUbicacion(d) {
|
||||
const ubicacion = d.Ubicacion || "-";
|
||||
const select = document.getElementById(select_ubicacion);
|
||||
function addUbicacion(d) {
|
||||
const ubicacion = d.Ubicacion || "-";
|
||||
const select = document.getElementById(select_ubicacion);
|
||||
|
||||
if (!select) {
|
||||
console.warn(`Element with ID "${select_ubicacion}" not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const optionExists = Array.from(select.options).some(opt => opt.value === ubicacion);
|
||||
if (!optionExists) {
|
||||
const option = document.createElement("option");
|
||||
option.value = ubicacion;
|
||||
option.textContent = ubicacion;
|
||||
select.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data === "string") {
|
||||
TS_decrypt(data, SECRET, (dec) => {
|
||||
if (dec && typeof dec === "object") {
|
||||
addUbicacion(dec);
|
||||
if (!select) {
|
||||
console.warn(`Element with ID "${select_ubicacion}" not found.`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
addUbicacion(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Error processing ubicacion:", error);
|
||||
}
|
||||
});
|
||||
|
||||
const optionExists = Array.from(select.options).some(
|
||||
(opt) => opt.value === ubicacion
|
||||
);
|
||||
if (!optionExists) {
|
||||
const option = document.createElement("option");
|
||||
option.value = ubicacion;
|
||||
option.textContent = ubicacion;
|
||||
select.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data === "string") {
|
||||
TS_decrypt(data, SECRET, (dec) => {
|
||||
if (dec && typeof dec === "object") {
|
||||
addUbicacion(dec);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
addUbicacion(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Error processing ubicacion:", error);
|
||||
}
|
||||
});
|
||||
|
||||
// Función para renderizar la tabla filtrada
|
||||
function renderTable(filtroUbicacion) {
|
||||
@@ -191,15 +210,29 @@ PAGES.materiales = {
|
||||
config,
|
||||
gun.get(TABLE).get("materiales"),
|
||||
document.getElementById(tableContainer),
|
||||
function(data, new_tr) {
|
||||
function (data, new_tr) {
|
||||
if (parseFloat(data.Cantidad) < parseFloat(data.Cantidad_Minima)) {
|
||||
new_tr.style.background = "lightcoral"
|
||||
new_tr.style.background = "#fcfcb0";
|
||||
}
|
||||
if (parseFloat(data.Cantidad) <= 0) {
|
||||
new_tr.style.background = "#ffc0c0";
|
||||
}
|
||||
if ((data.Cantidad || "?") == "?") {
|
||||
new_tr.style.background = "#d0d0ff";
|
||||
}
|
||||
if ((data.Revision || "?") == "?") {
|
||||
new_tr.style.background = "#d0d0ff";
|
||||
}
|
||||
},
|
||||
function(data) {
|
||||
if (data.Ubicacion == filtroUbicacion) {return false}
|
||||
if (filtroUbicacion == "") {return false}
|
||||
return true
|
||||
function (data) {
|
||||
var is_low_stock =
|
||||
!document.getElementById(check_lowstock).checked ||
|
||||
parseFloat(data.Cantidad) < parseFloat(data.Cantidad_Minima);
|
||||
|
||||
var is_region =
|
||||
filtroUbicacion === "" || data.Ubicacion === filtroUbicacion;
|
||||
|
||||
return !(is_low_stock && is_region);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -211,9 +244,13 @@ PAGES.materiales = {
|
||||
document.getElementById(select_ubicacion).onchange = function () {
|
||||
renderTable(this.value);
|
||||
};
|
||||
// Recargar al cambiar filtro
|
||||
document.getElementById(check_lowstock).onchange = function () {
|
||||
renderTable(document.getElementById(select_ubicacion).value);
|
||||
};
|
||||
|
||||
if (!checkRole("materiales:edit")) {
|
||||
document.getElementById(btn_new).style.display = "none"
|
||||
document.getElementById(btn_new).style.display = "none";
|
||||
} else {
|
||||
document.getElementById(btn_new).onclick = () => {
|
||||
setUrlHash("materiales," + safeuuid(""));
|
||||
|
||||
@@ -2,7 +2,7 @@ PERMS["notas"] = "Notas"
|
||||
PERMS["notas:edit"] = "> Editar"
|
||||
PAGES.notas = {
|
||||
navcss: "btn5",
|
||||
icon: "static/appico/Notepad.svg",
|
||||
icon: "static/appico/edit.png",
|
||||
AccessControl: true,
|
||||
Title: "Notas",
|
||||
edit: function (mid) {
|
||||
|
||||
@@ -2,7 +2,7 @@ PERMS["pagos"] = "Pagos";
|
||||
PERMS["pagos:edit"] = "> Editar";
|
||||
PAGES.pagos = {
|
||||
navcss: "btn7",
|
||||
icon: "static/appico/Database.svg",
|
||||
icon: "static/appico/credit_cards.png",
|
||||
AccessControl: true,
|
||||
Title: "Pagos",
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ PERMS["personas"] = "Personas";
|
||||
PERMS["personas:edit"] = "> Editar";
|
||||
PAGES.personas = {
|
||||
navcss: "btn3",
|
||||
icon: "static/appico/File_Person.svg",
|
||||
icon: "static/appico/users.png",
|
||||
AccessControl: true,
|
||||
Title: "Personas",
|
||||
edit: function (mid) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
PERMS["resumen_diario"] = "Resumen diario (Solo docentes!)";
|
||||
PAGES.resumen_diario = {
|
||||
icon: "static/appico/Newspaper.svg",
|
||||
icon: "static/appico/calendar.png",
|
||||
navcss: "btn3",
|
||||
AccessControl: true,
|
||||
Title: "Resumen Diario",
|
||||
Title: "Hoy y mañana",
|
||||
index: function () {
|
||||
var data_Comedor = safeuuid();
|
||||
var data_Tareas = safeuuid();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
PERMS["supercafe"] = "SuperCafé";
|
||||
PERMS["supercafe"] = "Cafetería";
|
||||
PERMS["supercafe:edit"] = "> Editar";
|
||||
PAGES.supercafe = {
|
||||
navcss: "btn4",
|
||||
icon: "static/appico/Coffee.svg",
|
||||
icon: "static/appico/cup.png",
|
||||
AccessControl: true,
|
||||
Title: "SuperCafé",
|
||||
Title: "Cafetería",
|
||||
edit: function (mid) {
|
||||
if (!checkRole("supercafe:edit")) {
|
||||
setUrlHash("supercafe");
|
||||
@@ -17,10 +17,7 @@ PAGES.supercafe = {
|
||||
var field_notas = safeuuid();
|
||||
var field_estado = safeuuid();
|
||||
var div_actions = safeuuid();
|
||||
var btn_pagos = safeuuid();
|
||||
var btn_cocina = safeuuid();
|
||||
var btn_guardar = safeuuid();
|
||||
var btn_guardar2 = safeuuid();
|
||||
var btn_borrar = safeuuid();
|
||||
container.innerHTML = `
|
||||
<h1>Comanda <code id="${nameh1}"></code></h1>
|
||||
@@ -165,7 +162,7 @@ PAGES.supercafe = {
|
||||
var tts_check = safeuuid();
|
||||
var old = {};
|
||||
container.innerHTML = `
|
||||
<h1>SuperCafé - Total: <span id="${totalprecio}">0</span>c</h1>
|
||||
<h1>Cafetería - Total: <span id="${totalprecio}">0</span>c</h1>
|
||||
<button id="${btn_new}" style="${sc_nobtn};">Nueva comanda</button>
|
||||
<br>
|
||||
<label>
|
||||
|
||||