eventos_v2 / templates /index.html
giseldo's picture
Ajusta a visualização padrão para "cards" e oculta a tabela inicialmente
1ce703a
{% 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 %}