Spaces:
Running
Running
{% extends "layout.html" %} | |
{% block title %}Home - Flask Supabase Auth{% endblock %} | |
{% block content %} | |
<!-- Seção de Eventos/Conferências --> | |
<div class="container mt-5"> | |
<div class="d-flex justify-content-between align-items-center mb-4"> | |
<h2 class="mb-0">Eventos Disponíveis</h2> | |
<div class="btn-group" role="group" aria-label="Opções de visualização"> | |
<button type="button" class="btn btn-outline-primary view-toggle" data-view="table"> | |
<i class="bi bi-table"></i> Tabela | |
</button> | |
<button type="button" class="btn btn-outline-primary view-toggle active" data-view="cards"> | |
<i class="bi bi-grid-3x3-gap"></i> Cards | |
</button> | |
</div> | |
</div> | |
<!-- Visualização em Tabela --> | |
<div class="table-responsive view-content" id="tableView" style="display: none;"> | |
<table class="table table-hover" id="conferencesTable"> | |
<thead class="table-dark"> | |
<tr> | |
<th class="sortable text-start align-middle bg-primary" data-sort="name">Nome<i class="sort-icon"></i></th> | |
<th class="sortable text-start align-middle bg-primary" data-sort="dates">Datas realização<i class="sort-icon"></i></th> | |
<th class="sortable text-start align-middle bg-primary" data-sort="location">Localização<i class="sort-icon"></i></th> | |
<th class="sortable text-start align-middle bg-primary" data-sort="deadline">Prazo submissão<i class="sort-icon"></i></th> | |
<th class="sortable text-start align-middle bg-primary" data-sort="countdown">Tempo restante para submissão<i class="sort-icon"></i></th> | |
<th class="text-start align-middle bg-primary">Ações</th> | |
</tr> | |
</thead> | |
<tbody> | |
{% for conference in conferences %} | |
<tr> | |
<td data-value="{{ conference.name }}" class="align-middle">{{ conference.name }} - {{ conference.full_name }}</td> | |
<td data-value="{{ conference.dates }}" class="align-middle">{{ conference.dates }}</td> | |
<td data-value="{{ conference.location }}" class="align-middle">{{ conference.location }}</td> | |
<td data-value="{{ conference.deadline }}" class="align-middle">{{ conference.deadline }}</td> | |
<td class="countdown-cell align-middle" data-deadline="{{ conference.deadline }}" data-value="{{ conference.deadline }}"> | |
<span class="countdown-value">Calculando...</span> | |
</td> | |
<td class="align-middle"> | |
<a href="{{ url_for('conference_details', conference_id=conference.id) }}" | |
class="btn btn-primary btn-sm">Ver Detalhes</a> | |
</td> | |
</tr> | |
{% endfor %} | |
</tbody> | |
</table> | |
</div> | |
<!-- Visualização em Cards --> | |
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 view-content" id="cardsView"> | |
{% for conference in conferences %} | |
<div class="col"> | |
<div class="card h-100 shadow-sm"> | |
<div class="card-header bg-primary text-white"> | |
<h5 class="card-title mb-0">{{ conference.name }}</h5> | |
</div> | |
<div class="card-body"> | |
<h6 class="card-subtitle mb-2 text-muted">{{ conference.full_name }}</h6> | |
<p class="card-text"><strong>Datas:</strong> {{ conference.dates }}</p> | |
<p class="card-text"><strong>Localização:</strong> {{ conference.location }}</p> | |
<p class="card-text"><strong>Prazo Submissão:</strong> {{ conference.deadline }}</p> | |
<p class="card-text"> | |
<strong>Tempo Restante:</strong><br> | |
<span class="countdown-value card-countdown" data-deadline="{{ conference.deadline }}">Calculando...</span> | |
</p> | |
</div> | |
<div class="card-footer bg-transparent border-top-0"> | |
<a href="{{ url_for('conference_details', conference_id=conference.id) }}" | |
class="btn btn-primary btn-sm w-100">Ver Detalhes</a> | |
</div> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Configuração de alternância de visualização | |
const viewToggles = document.querySelectorAll('.view-toggle'); | |
const tableView = document.getElementById('tableView'); | |
const cardsView = document.getElementById('cardsView'); | |
// Atualizar os contadores imediatamente para a visualização padrão (cards) | |
updateCountdowns(); | |
viewToggles.forEach(toggle => { | |
toggle.addEventListener('click', function() { | |
// Remover classe active de todos os botões | |
viewToggles.forEach(btn => btn.classList.remove('active')); | |
// Adicionar classe active ao botão clicado | |
this.classList.add('active'); | |
// Alternar visualização com base no botão clicado | |
const view = this.getAttribute('data-view'); | |
if (view === 'table') { | |
tableView.style.display = 'block'; | |
cardsView.style.display = 'none'; | |
} else { | |
tableView.style.display = 'none'; | |
cardsView.style.display = 'flex'; | |
} | |
// Atualizar os contadores | |
updateCountdowns(); | |
}); | |
}); | |
// Atualizar os contadores de tempo restante | |
function updateCountdowns() { | |
// Atualizar para células na tabela | |
updateCountdownElements('.countdown-cell .countdown-value'); | |
// Atualizar para cards | |
updateCountdownElements('.card-countdown'); | |
} | |
function updateCountdownElements(selector) { | |
const countdownElements = document.querySelectorAll(selector); | |
countdownElements.forEach(countdownElement => { | |
// Pegar o deadline do elemento pai (célula da tabela) ou do próprio elemento (card) | |
let deadlineStr; | |
if (countdownElement.classList.contains('card-countdown')) { | |
deadlineStr = countdownElement.getAttribute('data-deadline'); | |
} else { | |
deadlineStr = countdownElement.closest('.countdown-cell').getAttribute('data-deadline'); | |
} | |
// Verificar se a data é válida | |
if (!deadlineStr) { | |
countdownElement.textContent = 'Data não definida'; | |
return; | |
} | |
try { | |
// Formato esperado: YYYY-MM-DD HH:MM | |
const deadlineDate = new Date(deadlineStr); | |
const now = new Date(); | |
// Verificar se a data é válida | |
if (isNaN(deadlineDate.getTime())) { | |
countdownElement.textContent = 'Data inválida'; | |
return; | |
} | |
const timeDiff = deadlineDate - now; | |
if (timeDiff <= 0) { | |
countdownElement.textContent = 'Prazo encerrado'; | |
countdownElement.classList.add('text-danger'); | |
} else { | |
// Calcular dias, horas, minutos restantes | |
const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); | |
const hours = Math.floor((timeDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); | |
const minutes = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60)); | |
let countdownText = ''; | |
if (days > 0) countdownText += `${days} dia(s) `; | |
if (hours > 0 || days > 0) countdownText += `${hours} hora(s) `; | |
countdownText += `${minutes} minuto(s)`; | |
countdownElement.textContent = countdownText; | |
// Adicionar classes para destacar prazos próximos | |
if (days < 1) { | |
countdownElement.classList.add('text-danger', 'fw-bold'); | |
} else if (days < 7) { | |
countdownElement.classList.add('text-warning'); | |
} | |
} | |
} catch (error) { | |
countdownElement.textContent = 'Erro ao calcular'; | |
console.error("Erro ao calcular tempo restante:", error); | |
} | |
}); | |
} | |
// Executar imediatamente e depois a cada minuto | |
updateCountdowns(); | |
setInterval(updateCountdowns, 60000); | |
// Configuração da ordenação da tabela | |
const table = document.getElementById('conferencesTable'); | |
const headers = table.querySelectorAll('th.sortable'); | |
let currentSort = { column: null, direction: 'asc' }; | |
// Adicionar eventos de clique aos cabeçalhos | |
headers.forEach(header => { | |
header.addEventListener('click', () => { | |
const column = header.dataset.sort; | |
const direction = column === currentSort.column && currentSort.direction === 'asc' ? 'desc' : 'asc'; | |
// Atualizar ícones | |
headers.forEach(h => h.querySelector('.sort-icon').textContent = ''); | |
header.querySelector('.sort-icon').textContent = direction === 'asc' ? ' ▲' : ' ▼'; | |
// Ordenar tabela | |
sortTable(column, direction); | |
// Atualizar estado de ordenação atual | |
currentSort = { column, direction }; | |
}); | |
}); | |
function sortTable(column, direction) { | |
const rows = Array.from(table.querySelectorAll('tbody tr')); | |
// Ordenar linhas | |
const sortedRows = rows.sort((a, b) => { | |
const cellA = a.querySelector(`td[data-value]:nth-child(${getColumnIndex(column) + 1})`); | |
const cellB = b.querySelector(`td[data-value]:nth-child(${getColumnIndex(column) + 1})`); | |
let valueA = cellA ? cellA.getAttribute('data-value') : ''; | |
let valueB = cellB ? cellB.getAttribute('data-value') : ''; | |
// Ordenação para datas | |
if (column === 'deadline' || column === 'dates') { | |
valueA = valueA ? new Date(valueA).getTime() : 0; | |
valueB = valueB ? new Date(valueB).getTime() : 0; | |
} | |
// Ordenação para strings | |
if (typeof valueA === 'string' && typeof valueB === 'string') { | |
return direction === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA); | |
} | |
// Ordenação para números | |
return direction === 'asc' ? valueA - valueB : valueB - valueA; | |
}); | |
// Limpar e recriar a tabela | |
const tbody = table.querySelector('tbody'); | |
while (tbody.firstChild) { | |
tbody.removeChild(tbody.firstChild); | |
} | |
sortedRows.forEach(row => { | |
tbody.appendChild(row); | |
}); | |
} | |
function getColumnIndex(columnName) { | |
let index = 0; | |
headers.forEach((header, i) => { | |
if (header.dataset.sort === columnName) { | |
index = i; | |
} | |
}); | |
return index; | |
} | |
}); | |
</script> | |
<style> | |
.sortable { | |
cursor: pointer; | |
user-select: none; | |
} | |
.sortable:hover { | |
background-color: #2c3034; | |
} | |
.sort-icon { | |
display: inline-block; | |
width: 10px; | |
} | |
.view-toggle.active { | |
background-color: #0d6efd; | |
color: white; | |
} | |
#cardsView { | |
display: flex; | |
flex-wrap: wrap; | |
} | |
.card { | |
transition: transform 0.2s; | |
} | |
.card:hover { | |
transform: translateY(-5px); | |
} | |
</style> | |
{% endblock %} |