From c9c28e68c758dd27c600df6995d2d96b2ec8bdd9 Mon Sep 17 00:00:00 2001 From: Eder Moraes <54563944+edermcastro@users.noreply.github.com> Date: Mon, 27 Apr 2026 02:31:05 -0300 Subject: [PATCH] nova interface da tela de atendimento --- index.html | 93 ++++++++-- main.js | 33 ++++ preload.js | 3 + renderer.js | 208 ++++++++++++++++------ style.css | 487 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 742 insertions(+), 82 deletions(-) diff --git a/index.html b/index.html index 19f4d2d..3d39164 100644 --- a/index.html +++ b/index.html @@ -15,34 +15,91 @@ +
-

Fila

-

- - - - +
+ +
+
+

Fila de Espera

+ 0 +
+
+ + + + + + + + + + + + +
SENHACLIENTESERVIÇOTIPO
+ +
+
+ +
+ +
+ +

Nenhum atendimento chamado

+ Clique no botão flutuante para chamar o próximo +
+
+
+ +
+ +
+ diff --git a/main.js b/main.js index a63023a..12130eb 100644 --- a/main.js +++ b/main.js @@ -733,6 +733,39 @@ ipcMain.on('iniciar-atendimento', async (event, itemId) => { request.end(); }); +// Ouvir clique no botão "Rechamar" - chama novamente o mesmo atendimento +ipcMain.on('rechamar-atendimento', async (event, itemId) => { + + const token = await getAuthToken(); + const tenantId = await getTenantId(); + const colabId = await getSelectedOperatorId(); + const url = apiUrl + 'attendance/call-next/' + colabId; + + const request = net.request({ + method: 'POST', + url: url, + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + token, + 'x-tenant-id': tenantId + } + }); + + request.on('response', (response) => { + response.on('data', (chunk) => { + console.log(`Rechamar BODY: ${chunk}`); + }); + response.on('end', () => { + console.log('Rechamada concluída.'); + }); + }); + request.on('error', (error) => { + console.error(`Erro na rechamada: ${error}`); + }); + + request.end(); +}); + // Ouve quando um atendimento é iniciado e notifica a janela flutuante ipcMain.on('atendimento-iniciado', (event, itemId) => { if (floatingWin) { diff --git a/preload.js b/preload.js index 5037998..696e404 100644 --- a/preload.js +++ b/preload.js @@ -14,6 +14,9 @@ contextBridge.exposeInMainWorld('electronAPI', { //inicia o atendimento atual iniciaAtendimento: (itemId) => ipcRenderer.send('iniciar-atendimento', itemId), + //rechama o atendimento atual + rechamarAtendimento: (itemId) => ipcRenderer.send('rechamar-atendimento', itemId), + //notifica sobre o status do atendimento atendimentoIniciado: (itemId) => ipcRenderer.send('atendimento-iniciado', itemId), atendimentoFinalizado: () => ipcRenderer.send('atendimento-finalizado'), diff --git a/renderer.js b/renderer.js index 5f061b2..b7f2a21 100644 --- a/renderer.js +++ b/renderer.js @@ -1,14 +1,24 @@ const listView = document.getElementById('list-view'); const observationView = document.getElementById('obs-view'); const encaminharView = document.getElementById('encaminhar-view'); -const itemList = document.getElementById('item-list'); + +// Novos elementos do layout redesenhado +const queueTableBody = document.getElementById('queue-table-body'); +const queueEmpty = document.getElementById('queue-empty'); +const queueCount = document.getElementById('queue-count'); +const currentCard = document.getElementById('current-card'); +const noCurrentCard = document.getElementById('no-current'); +const cardTicket = document.getElementById('card-ticket'); +const cardStatusBadge = document.getElementById('card-status-badge'); +const cardClientName = document.getElementById('card-client-name'); +const cardService = document.getElementById('card-service'); +const cardType = document.getElementById('card-type'); const nextButton = document.getElementById('next-button'); +const recallButton = document.getElementById('recall-button'); const logoutButton = document.getElementById('logout-button'); const observationText = document.getElementById('observation-text'); -const encObservationText = document.getElementById('enc-observation-text'); const saveButton = document.getElementById('save-button'); const selectedItemNameSpan = document.getElementById('selected-item-name'); -const queueNumber = document.getElementById('queue-number'); const idAtend = document.getElementById('idAtend'); const counterStart = document.getElementById('counter-start'); @@ -16,16 +26,22 @@ let currentData = []; let selectedItemId = null; let selectedItemName = ''; +// =========================== +// FLAG que trava o ID do atendimento chamado. +// Quando o call-next retorna e define o atendimento atual, +// este flag impede que atualizações da fila sobrescrevam o selectedItemId. +// =========================== +let calledAtendimentoData = null; // Guarda os dados completos do atendimento chamado + window.electronAPI.onLoadData((data) => { // Se já estiver em atendimento, ignora atualizações da lista para evitar flickering no botão if (localStorage.getItem('atendimentoAtual')) { return; } - nextButton.disabled = true; if (!data) { return; } - populateList(data); + populateQueueTable(data); }); async function initializeSocket() { @@ -75,20 +91,22 @@ initializeSocket(); window.electronAPI.selectAtendID((data) => { nextButton.disabled = true; if (!data) { - queueNumber.innerHTML = 'Ninguem aguardando atendimento, fechando a janela em alguns segundos...'; + showNoCurrentCard('Ninguém aguardando atendimento'); return; } - // Reseta a view para a lista sempre que os dados são carregados ao clicar no botão para abrir a janela - populateList(data); - showListView(); - // Garante que o item selecionado (ID e Nome) seja o que veio da chamada, sobrescrevendo o da lista. + // Trava os dados do atendimento chamado para que atualizações da fila + // NÃO sobrescrevam quem foi chamado. + calledAtendimentoData = data; selectedItemId = data._id ?? null; selectedItemName = data.clientName ?? ''; - //data.senhaGen - queueNumber.innerHTML = data ? `NA VEZ: ${data.clientName.toUpperCase()}` : 'Ninguem aguardando atendimento'; - selectedItemNameSpan.innerHTML = data ? ` ${data.clientName.toUpperCase()} [ ${data.ticketNumber} ]` : 'Ninguem aguardando atendimento'; + // Atualiza a tabela da fila (sem afetar o card atual) + // populateQueueTable já NÃO mexe no selectedItemId quando calledAtendimentoData está travado + showListView(); + + // Mostra o card do atendimento chamado + showCurrentCard(data); }); window.electronAPI.showObservation(() => { @@ -96,15 +114,29 @@ window.electronAPI.showObservation(() => { showObservationView(); // Muda para a tela de observação }); -// Função para popular a lista de itens -function populateList(proximos) { +// Função para popular a tabela da fila +function populateQueueTable(proximos) { const atendimentoEmAndamentoId = localStorage.getItem('atendimentoAtual'); const atendimentoEmAndamentoNome = localStorage.getItem('atendimentoAtualNome'); if (atendimentoEmAndamentoId) { - itemList.innerHTML = `
  • Atendimento com ${(atendimentoEmAndamentoNome || '').toUpperCase()} em andamento.
  • `; - queueNumber.innerHTML = `EM ATENDIMENTO: ${(atendimentoEmAndamentoNome || '').toUpperCase()}`; + // Em atendimento: mostra card de atendimento no painel direito + queueTableBody.innerHTML = ''; + queueCount.textContent = '0'; + queueEmpty.style.display = 'flex'; + document.querySelector('.queue-table').style.display = 'none'; + + // Mostra card como "Atendendo" + currentCard.style.display = 'flex'; + noCurrentCard.style.display = 'none'; + cardTicket.textContent = ''; + cardStatusBadge.textContent = 'Atendendo'; + cardStatusBadge.className = 'card-badge badge-atendendo'; + cardClientName.textContent = (atendimentoEmAndamentoNome || '').toUpperCase(); + cardService.textContent = ''; + cardType.textContent = ''; nextButton.disabled = true; + recallButton.disabled = true; return; } @@ -114,36 +146,81 @@ function populateList(proximos) { proximos = JSON.parse(datastorage || '[]'); } - itemList.innerHTML = ''; + // Limpa e popula a tabela + queueTableBody.innerHTML = ''; - setTimeout(() => { - nextButton.disabled = !proximos || proximos.length === 0; - }, 1000); - - // Seleciona o primeiro item por padrão (ou o próximo disponível) - // Aqui, vamos apenas pegar o primeiro da lista atual - const itemToProcess = proximos[0]; // Pega o primeiro item - if (itemToProcess) { - selectedItemId = itemToProcess._id; - selectedItemName = itemToProcess.clientName; - const li = document.createElement('li'); - li.innerHTML = /*${itemToProcess.ticketNumber}: */ `${itemToProcess.clientName.toUpperCase()} - ${itemToProcess.ticketNumber}`; - li.dataset.id = itemToProcess._id; // Armazena o ID no elemento - li.classList.add('selected'); // Marca como selecionado visualmente (precisa de CSS) - itemList.appendChild(li); + if (!proximos || proximos.length === 0) { + queueEmpty.style.display = 'flex'; + document.querySelector('.queue-table').style.display = 'none'; + queueCount.textContent = '0'; } else { - itemList.innerHTML = '
  • Fila vazia!
  • '; - nextButton.disabled = true; - selectedItemId = null; - selectedItemName = ''; + queueEmpty.style.display = 'none'; + document.querySelector('.queue-table').style.display = 'table'; + queueCount.textContent = proximos.length; + + proximos.forEach((item, index) => { + const tr = document.createElement('tr'); + const isPreferencial = item.ticketNumber && item.ticketNumber.startsWith('P'); + + tr.innerHTML = ` + ${item.ticketNumber || '---'} + ${(item.clientName || '---').toUpperCase()} + ${item.serviceName || 'Atendimento'} + ${isPreferencial ? 'PREFERENCIAL' : 'NORMAL'} + `; + + queueTableBody.appendChild(tr); + }); } - // Adiciona os outros itens apenas para visualização (opcional) - proximos.slice(1).forEach(item => { - const li = document.createElement('li'); - li.textContent = `${item.clientName.toUpperCase()} - ${item.ticketNumber}`; - itemList.appendChild(li); - }); + // CORREÇÃO DO BUG: Só atualiza selectedItemId se NÃO houver um atendimento já chamado (travado) + if (!calledAtendimentoData) { + // Nenhum atendimento foi chamado ainda, pode selecionar o primeiro + const firstItem = proximos && proximos[0]; + if (firstItem) { + selectedItemId = firstItem._id; + selectedItemName = firstItem.clientName; + } else { + selectedItemId = null; + selectedItemName = ''; + } + // Sem atendimento chamado: não mostra card + if (!currentCard.style.display || currentCard.style.display === 'none') { + noCurrentCard.style.display = 'flex'; + } + } + // Se calledAtendimentoData está definido, NÃO toca no selectedItemId — mantém o que foi chamado. +} + +// Mostra o card do atendimento atual (chamado) +function showCurrentCard(data) { + currentCard.style.display = 'flex'; + noCurrentCard.style.display = 'none'; + + cardTicket.textContent = data.ticketNumber || '---'; + + const statusText = data.status === 'Atendendo' ? 'Atendendo' : 'Chamado'; + cardStatusBadge.textContent = statusText; + cardStatusBadge.className = `card-badge ${data.status === 'Atendendo' ? 'badge-atendendo' : 'badge-chamado'}`; + + cardClientName.textContent = (data.clientName || '---').toUpperCase(); + cardService.textContent = (data.serviceName || 'ATENDIMENTO').toUpperCase(); + + const isPreferencial = data.ticketNumber && data.ticketNumber.startsWith('P'); + cardType.textContent = isPreferencial ? 'PREFERENCIAL' : 'NORMAL'; + cardType.className = `card-type ${isPreferencial ? 'type-preferencial' : ''}`; + + // Habilita os botões + setTimeout(() => { + nextButton.disabled = false; + recallButton.disabled = false; + }, 500); +} + +function showNoCurrentCard(message) { + currentCard.style.display = 'none'; + noCurrentCard.style.display = 'flex'; + noCurrentCard.querySelector('p').textContent = message || 'Nenhum atendimento chamado'; } @@ -178,23 +255,51 @@ function showObservationView() { observationView.style.display = 'block'; } -// // Evento do botão "Iniciar atendimento" +// // Evento do botão "Iniciar atendimento" (INICIAR) nextButton.addEventListener('click', () => { - if (selectedItemId !== null) { + // Usa calledAtendimentoData para garantir que estamos iniciando O ATENDIMENTO CORRETO + const idToStart = calledAtendimentoData ? calledAtendimentoData._id : selectedItemId; + const nameToStart = calledAtendimentoData ? calledAtendimentoData.clientName : selectedItemName; + + if (idToStart !== null) { // Salva o estado de atendimento no localStorage - localStorage.setItem('atendimentoAtual', selectedItemId); - localStorage.setItem('atendimentoAtualNome', selectedItemName); + localStorage.setItem('atendimentoAtual', idToStart); + localStorage.setItem('atendimentoAtualNome', nameToStart); + + // Atualiza o span da tela de observação + selectedItemId = idToStart; + selectedItemName = nameToStart; + selectedItemNameSpan.innerHTML = ` ${(nameToStart || '').toUpperCase()} `; // Notifica o main process e muda a view // IMPORTANTE: iniciaAtendimento deve apenas mudar o status na API, não chamar o próximo. - window.electronAPI.atendimentoIniciado(selectedItemId); - window.electronAPI.iniciaAtendimento(selectedItemId); + window.electronAPI.atendimentoIniciado(idToStart); + window.electronAPI.iniciaAtendimento(idToStart); + + // Limpa o travamento — atendimento foi iniciado + calledAtendimentoData = null; + showObservationView(); // Muda para a tela de observação } else { - console.warn("Nenhum item selecionado para 'Próximo'"); + console.warn("Nenhum item selecionado para 'Iniciar'"); } }); +// Evento do botão RECHAMAR +recallButton.addEventListener('click', () => { + if (calledAtendimentoData) { + // Re-chama o mesmo atendimento (pode tocar som, exibir no painel, etc.) + window.electronAPI.rechamarAtendimento(calledAtendimentoData._id); + + // Feedback visual + recallButton.textContent = 'RECHAMANDO...'; + recallButton.disabled = true; + setTimeout(() => { + recallButton.textContent = 'RECHAMAR'; + recallButton.disabled = false; + }, 2000); + } +}); logoutButton.addEventListener('click', () => { @@ -211,6 +316,9 @@ saveButton.addEventListener('click', () => { localStorage.removeItem('atendimentoAtualNome'); window.electronAPI.atendimentoFinalizado(); + // Limpa o travamento + calledAtendimentoData = null; + window.electronAPI.saveObservation({ itemId: selectedItemId, observation: observation }); window.location.reload(); } diff --git a/style.css b/style.css index fd62a23..5bb7e8e 100644 --- a/style.css +++ b/style.css @@ -34,7 +34,7 @@ /* Estilos Gerais */ body { - font-family: sans-serif; + font-family: 'Segoe UI', sans-serif; color: #FFF; margin: 0; padding: 10px; @@ -125,16 +125,465 @@ body#floating{ } -/* Janela Principal */ -#list-view, #obs-view { +/* ================================================ + NOVO LAYOUT: Tela de Atendimento Redesenhada + ================================================ */ + +#list-view { + padding: 16px; + background-color: var(--primary-color); + height: 96vh; + box-sizing: border-box; + display: flex; + flex-direction: column; +} + +#obs-view { padding: 20px; - background-color: var(--secondary-color); /* Fundo branco para a janela principal */ + background-color: var(--secondary-color); border-radius: var(--border-radius); box-shadow: var(--box-shadow); - height: 96vh; /* Ocupa a altura da viewport */ + height: 96vh; box-sizing: border-box; } +.attendance-layout { + display: flex; + gap: 16px; + flex: 1; + min-height: 0; +} + +/* PAINEL ESQUERDO: Tabela da fila */ +.queue-panel { + flex: 1.6; + background-color: rgba(11, 44, 80, 0.4); + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.06); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.panel-header h2 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: rgba(255, 255, 255, 0.85); + letter-spacing: 0.3px; +} + +.queue-badge { + background: var(--info-color); + color: white; + padding: 2px 10px; + border-radius: 12px; + font-size: 13px; + font-weight: 600; + min-width: 20px; + text-align: center; +} + +.queue-table-wrapper { + flex: 1; + overflow-y: auto; + padding: 0; +} + +/* Tabela da fila */ +.queue-table { + width: 100%; + border-collapse: collapse; + table-layout: fixed; +} + +.queue-table thead th { + padding: 10px 20px; + text-align: left; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + color: rgba(255, 255, 255, 0.4); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + position: sticky; + top: 0; + background: rgba(11, 44, 80, 0.95); + z-index: 1; +} + +.queue-table tbody tr { + transition: background-color 0.2s ease; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +.queue-table tbody tr:hover { + background-color: rgba(255, 255, 255, 0.03); +} + +.queue-table tbody td { + padding: 12px 20px; + font-size: 13px; + color: rgba(255, 255, 255, 0.7); +} + +.ticket-cell { + font-weight: 700; + font-size: 14px !important; +} + +.ticket-normal { + color: var(--light-info-color) !important; +} + +.ticket-preferencial { + color: var(--warning-color) !important; +} + +.type-normal { + color: rgba(255, 255, 255, 0.5) !important; + font-size: 11px !important; + font-weight: 600; + letter-spacing: 0.5px; +} + +.type-preferencial { + color: var(--warning-color) !important; + font-size: 11px !important; + font-weight: 600; + letter-spacing: 0.5px; +} + +.queue-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px; + color: rgba(255, 255, 255, 0.3); + gap: 8px; +} + +.queue-empty .empty-icon { + font-size: 36px; + opacity: 0.5; +} + +.queue-empty p { + margin: 0; + font-size: 14px; +} + + +/* PAINEL DIREITO: Card do atendimento atual */ +.current-card-panel { + flex: 0.8; + display: flex; + align-items: stretch; +} + +.current-card { + background: linear-gradient(145deg, rgba(11, 44, 80, 0.6), rgba(8, 30, 60, 0.8)); + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.08); + display: flex; + flex-direction: column; + padding: 24px; + width: 100%; + box-sizing: border-box; + animation: cardFadeIn 0.3s ease-out; +} + +@keyframes cardFadeIn { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.card-ticket { + font-size: 32px; + font-weight: 800; + color: #fff; + letter-spacing: 1px; +} + +.card-badge { + padding: 4px 14px; + border-radius: 16px; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.badge-chamado { + background: var(--warning-color); + color: #fff; +} + +.badge-atendendo { + background: var(--success-color); + color: #fff; + animation: pulse-badge 1.5s infinite; +} + +@keyframes pulse-badge { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + +.card-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 20px; +} + +.card-client-name { + font-size: 18px; + font-weight: 700; + color: #fff; + margin: 0; + line-height: 1.3; +} + +.card-service { + font-size: 13px; + color: rgba(255, 255, 255, 0.5); + margin: 0; + font-weight: 500; + letter-spacing: 0.3px; +} + +.card-type { + font-size: 12px; + color: rgba(255, 255, 255, 0.35); + margin: 4px 0 0 0; + font-weight: 600; + letter-spacing: 0.5px; +} + +.card-type.type-preferencial { + color: var(--warning-color); +} + +.card-actions { + display: flex; + gap: 10px; + margin-top: auto; +} + +.btn-recall { + flex: 1; + padding: 12px 16px; + border: 2px solid var(--warning-color); + background: transparent; + color: var(--warning-color); + border-radius: 8px; + font-size: 13px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + letter-spacing: 0.5px; + -webkit-app-region: no-drag; +} + +.btn-recall:hover:not(:disabled) { + background: var(--warning-color); + color: #fff; +} + +.btn-recall:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.btn-start { + flex: 1; + padding: 12px 16px; + border: none; + background: var(--info-color); + color: #fff; + border-radius: 8px; + font-size: 13px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + letter-spacing: 0.5px; + -webkit-app-region: no-drag; +} + +.btn-start:hover:not(:disabled) { + background: var(--light-info-color); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(41, 128, 185, 0.4); +} + +.btn-start:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.no-current-card { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + background: rgba(11, 44, 80, 0.2); + border-radius: 12px; + border: 1px dashed rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.3); + gap: 8px; + padding: 20px; + box-sizing: border-box; +} + +.no-current-card .empty-icon { + font-size: 36px; + opacity: 0.4; +} + +.no-current-card p { + margin: 0; + font-size: 14px; + font-weight: 500; +} + +.no-current-card small { + font-size: 12px; + opacity: 0.6; +} + + +/* Barra inferior */ +.bottom-bar { + padding: 10px 0 0 0; + display: flex; + justify-content: flex-start; + gap: 10px; +} + +.btn-logout { + padding: 8px 20px; + background: transparent; + border: 1px solid var(--danger-color); + color: var(--danger-color); + border-radius: 6px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + -webkit-app-region: no-drag; +} + +.btn-logout:hover { + background: var(--danger-color); + color: #fff; +} + + +/* ================================================ + TELA DE OBSERVAÇÕES (Atendimento em andamento) + ================================================ */ +.obs-layout { + display: flex; + flex-direction: column; + height: 100%; +} + +.obs-header { + margin-bottom: 20px; +} + +.obs-header h2 { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 600; + color: rgba(255, 255, 255, 0.9); +} + +.obs-client-info { + display: flex; + align-items: center; + gap: 12px; +} + +.obs-client-name { + font-size: 16px; + color: var(--light-info-color); + font-weight: 600; +} + +#observation-text { + width: calc(100% - 22px) !important; + padding: 10px; + background-color: var(--secondary-color); + border: 1px solid var(--tertiary-color); + border-radius: 6px; + color: var(--medium-gray); + font-size: 14px; + flex: 1; + resize: vertical; + min-height: 120px; +} + +#observation-text:focus-visible { + box-shadow: var(--box-shadow-inputs); + outline: none; +} + +.obs-actions { + display: flex; + justify-content: flex-end; + padding-top: 12px; +} + +.btn-finish { + padding: 12px 28px; + background: var(--warning-color); + border: none; + color: #fff; + border-radius: 8px; + font-size: 14px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + -webkit-app-region: no-drag; +} + +.btn-finish:hover { + background: var(--light-warning-color); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(243, 156, 18, 0.3); +} + + +/* ================================================ + ESTILOS LEGADOS (mantidos para outras telas) + ================================================ */ + #item-list { list-style: none; padding: 0; @@ -227,15 +676,6 @@ textarea { font-size: 16px; } -textarea#observation-text{ - width: calc(100% -20px) !important; - padding: 10px; - background-color: var(--secondary-color); - border: 1px solid var(--tertiary-color); - border-radius: 4px; - color: var(--medium-gray); -} - .error-message { background-color: #ffebee; color: #c62828; @@ -363,4 +803,23 @@ input:checked + .slider:before { .slider.round:before { border-radius: 50%; +} + + +/* Scrollbar personalizada */ +.queue-table-wrapper::-webkit-scrollbar { + width: 6px; +} + +.queue-table-wrapper::-webkit-scrollbar-track { + background: transparent; +} + +.queue-table-wrapper::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + border-radius: 3px; +} + +.queue-table-wrapper::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.25); } \ No newline at end of file