so arrumar o botão e o bgl de diciplina
This commit is contained in:
@@ -4,6 +4,7 @@ import com.agendaestudantil.filtro.FiltroJwt;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
@@ -12,6 +13,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
@@ -44,6 +46,8 @@ public class ConfiguracaoSeguranca {
|
||||
"/swagger-ui/**", "/v3/api-docs/**")
|
||||
.permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
@@ -157,6 +157,19 @@ body {
|
||||
font-family: inherit;
|
||||
}
|
||||
#btnNovoEvento:hover { background: #a03224; }
|
||||
#btnGerenciarDisciplinas {
|
||||
background: transparent;
|
||||
color: #c0392b;
|
||||
border: 2px solid #c0392b;
|
||||
border-radius: 8px;
|
||||
padding: 6px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
#btnGerenciarDisciplinas:hover { background: #c0392b; color: #fff; }
|
||||
|
||||
/* Calendar area */
|
||||
.calendar-area { background: #fff; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); overflow: hidden; }
|
||||
@@ -444,3 +457,89 @@ body {
|
||||
[data-theme="dark"] #toast { background: #2a2a2a; }
|
||||
|
||||
[data-theme="dark"] .mais-eventos { color: var(--text-secondary); }
|
||||
|
||||
/* ===== DISCIPLINE BUTTON DARK MODE ===== */
|
||||
[data-theme="dark"] #btnGerenciarDisciplinas {
|
||||
color: #e57373;
|
||||
border-color: #e57373;
|
||||
}
|
||||
[data-theme="dark"] #btnGerenciarDisciplinas:hover {
|
||||
background: #e57373;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ===== MODAL DISCIPLINA PANELS ===== */
|
||||
.modal-disc-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.modal-disc-header h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
[data-theme="dark"] .modal-disc-header h2 { color: var(--text-primary); }
|
||||
|
||||
.modal-fechar-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
color: #9ca3af;
|
||||
padding: 2px 7px;
|
||||
border-radius: 6px;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.modal-fechar-btn:hover { background: #f3f4f6; color: #374151; }
|
||||
[data-theme="dark"] .modal-fechar-btn:hover { background: var(--hover-bg); color: var(--text-primary); }
|
||||
|
||||
.modal-corpo-lista {
|
||||
max-height: 310px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.modal-corpo-form { margin-bottom: 4px; }
|
||||
|
||||
/* Discipline list item */
|
||||
.disc-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 11px 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
.disc-item:last-child { border-bottom: none; }
|
||||
.disc-item:hover { background: #f9fafb; }
|
||||
[data-theme="dark"] .disc-item { border-bottom-color: var(--border-color); }
|
||||
[data-theme="dark"] .disc-item:hover { background: var(--hover-bg); }
|
||||
|
||||
.disc-cor {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.disc-nome {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
color: #111827;
|
||||
}
|
||||
[data-theme="dark"] .disc-nome { color: var(--text-primary); }
|
||||
.disc-info {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
[data-theme="dark"] .disc-info { color: var(--text-secondary); }
|
||||
.disc-seta {
|
||||
color: #9ca3af;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,115 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="theme.js"></script>
|
||||
<style>
|
||||
/* ===== NOTIFICACOES ===== */
|
||||
.notif-wrapper { position: relative; }
|
||||
.notif-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--borda, #333);
|
||||
border-radius: 8px;
|
||||
padding: 7px 9px;
|
||||
cursor: pointer;
|
||||
color: var(--texto, #fff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.notif-btn:hover { background: var(--hover, rgba(255,255,255,0.08)); }
|
||||
.notif-badge {
|
||||
position: absolute;
|
||||
top: -5px; right: -5px;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
min-width: 17px;
|
||||
height: 17px;
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 3px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.notif-panel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
width: 320px;
|
||||
background: var(--fundo-card, #1e1e2e);
|
||||
border: 1px solid var(--borda, #333);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.35);
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
}
|
||||
.notif-panel.aberto { display: block; }
|
||||
.notif-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--borda, #333);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--texto, #fff);
|
||||
}
|
||||
.notif-marcar-todas {
|
||||
background: none; border: none;
|
||||
color: var(--primario, #7c3aed);
|
||||
font-size: 12px; cursor: pointer; padding: 0;
|
||||
}
|
||||
.notif-marcar-todas:hover { text-decoration: underline; }
|
||||
.notif-lista { max-height: 340px; overflow-y: auto; }
|
||||
.notif-vazia {
|
||||
text-align: center;
|
||||
color: var(--texto2, #888);
|
||||
font-size: 13px;
|
||||
padding: 28px 16px;
|
||||
}
|
||||
.notif-item {
|
||||
display: flex; gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--borda, #2a2a3a);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.notif-item:hover { background: var(--hover, rgba(255,255,255,0.04)); }
|
||||
.notif-item.nao-lida { background: rgba(124,58,237,0.08); }
|
||||
.notif-item.nao-lida:hover { background: rgba(124,58,237,0.14); }
|
||||
.notif-icone {
|
||||
width: 32px; height: 32px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 15px; flex-shrink: 0; margin-top: 1px;
|
||||
}
|
||||
.notif-icone.prazo { background: rgba(245,158,11,0.18); }
|
||||
.notif-icone.atrasada { background: rgba(239,68,68,0.18); }
|
||||
.notif-icone.evento { background: rgba(59,130,246,0.18); }
|
||||
.notif-corpo { flex: 1; min-width: 0; }
|
||||
.notif-titulo-item {
|
||||
font-size: 13px; font-weight: 600;
|
||||
color: var(--texto, #fff);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.notif-msg {
|
||||
font-size: 12px; color: var(--texto2, #aaa);
|
||||
margin-top: 2px; line-height: 1.4;
|
||||
display: -webkit-box; -webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
.notif-tempo { font-size: 11px; color: var(--texto2, #888); margin-top: 4px; }
|
||||
.notif-ponto {
|
||||
width: 7px; height: 7px;
|
||||
background: var(--primario, #7c3aed);
|
||||
border-radius: 50%; flex-shrink: 0; margin-top: 6px;
|
||||
}
|
||||
.notif-item.lida .notif-ponto { visibility: hidden; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -54,6 +163,21 @@
|
||||
<span class="cargo" id="cursoUsuario">–</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notif-wrapper" id="notifWrapper">
|
||||
<button class="notif-btn" id="btnNotif" title="Notificações" aria-label="Notificações">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||||
<span class="notif-badge" id="notifBadge" style="display:none">0</span>
|
||||
</button>
|
||||
<div class="notif-panel" id="notifPanel">
|
||||
<div class="notif-panel-header">
|
||||
<span>Notificações</span>
|
||||
<button class="notif-marcar-todas" id="btnMarcarTodas">Marcar todas como lidas</button>
|
||||
</div>
|
||||
<div class="notif-lista" id="notifLista">
|
||||
<div class="notif-vazia">Nenhuma notificação</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro"><img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema"></button>
|
||||
<a href="configuracoes.html" id="btnConfig" title="Configurações"><img src="imagens/engrenagem.png" class="config-icon" alt="Configurações"></a>
|
||||
<button id="btnLogout" title="Sair">Sair</button>
|
||||
@@ -196,13 +320,22 @@
|
||||
<!-- MODAL DE DISCIPLINA -->
|
||||
<div class="modal-overlay" id="modalDisciplina">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<div class="modal-disc-header">
|
||||
<h2 id="tituloDisciplina">Gerenciar Disciplinas</h2>
|
||||
<button class="modal-fechar" id="btnFecharDisciplina">×</button>
|
||||
<button class="modal-fechar-btn" id="btnFecharDisciplina">×</button>
|
||||
</div>
|
||||
<div class="modal-corpo">
|
||||
<div class="lista-itens" id="listaDisciplinas"></div>
|
||||
<hr style="margin: 15px 0; border: none; border-top: 1px solid var(--borda);">
|
||||
|
||||
<!-- Painel 1: Lista de disciplinas -->
|
||||
<div id="painelListaDisciplinas">
|
||||
<div class="modal-corpo-lista" id="listaDisciplinas"></div>
|
||||
<div class="modal-acoes">
|
||||
<button class="btn-primario" id="btnNovaDisciplina">+ Nova Disciplina</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Painel 2: Formulário add/edit -->
|
||||
<div id="painelFormDisciplina" style="display:none">
|
||||
<div class="modal-corpo-form">
|
||||
<div class="modal-campo">
|
||||
<label for="discNome">Nome *</label>
|
||||
<input type="text" id="discNome" placeholder="Ex: Calculo I">
|
||||
@@ -222,11 +355,12 @@
|
||||
</div>
|
||||
<div class="modal-acoes">
|
||||
<button class="btn-perigo" id="btnExcluirDisciplina" style="display:none">Excluir</button>
|
||||
<button class="btn-secundario" id="btnCancelarDisciplina">Cancelar</button>
|
||||
<button class="btn-secundario" id="btnVoltarDisciplina">← Voltar</button>
|
||||
<button class="btn-primario" id="btnSalvarDisciplina">Salvar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// =============================================
|
||||
@@ -266,8 +400,7 @@ async function api(method, path, body) {
|
||||
const res = await fetch(path, opts);
|
||||
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
// Token expirado ou inválido
|
||||
if (res.status === 401) { logout(); return; }
|
||||
logout(); return;
|
||||
}
|
||||
|
||||
const json = await res.json().catch(() => ({}));
|
||||
@@ -305,6 +438,8 @@ let disciplinas = [];
|
||||
let eventoEditandoId = null;
|
||||
let tarefaEditandoId = null;
|
||||
|
||||
const feriadosCache = {};
|
||||
|
||||
const meses = ['Janeiro','Fevereiro','Março','Abril','Maio','Junho',
|
||||
'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'];
|
||||
const diasCurto = ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'];
|
||||
@@ -505,9 +640,34 @@ function renderAgenda() {
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// FERIADOS (fixos Brasil)
|
||||
// FERIADOS — BrasilAPI (gratuita, CORS habilitado)
|
||||
// =============================================
|
||||
function renderFeriados() {
|
||||
async function buscarFeriadosAno(ano) {
|
||||
if (feriadosCache[ano]) return feriadosCache[ano];
|
||||
try {
|
||||
const res = await fetch(`https://brasilapi.com.br/api/feriados/v1/${ano}`);
|
||||
if (!res.ok) throw new Error('Erro BrasilAPI: ' + res.status);
|
||||
// Retorna: [{date:"YYYY-MM-DD", name:"...", type:"national"}]
|
||||
const lista = await res.json();
|
||||
feriadosCache[ano] = lista;
|
||||
return lista;
|
||||
} catch(e) {
|
||||
console.warn('Fallback para feriados fixos:', e.message);
|
||||
feriadosCache[ano] = [
|
||||
{ date: `${ano}-01-01`, name: 'Confraternização Universal' },
|
||||
{ date: `${ano}-04-21`, name: 'Tiradentes' },
|
||||
{ date: `${ano}-05-01`, name: 'Dia do Trabalho' },
|
||||
{ date: `${ano}-09-07`, name: 'Independência do Brasil' },
|
||||
{ date: `${ano}-10-12`, name: 'Nossa Sra. Aparecida' },
|
||||
{ date: `${ano}-11-02`, name: 'Finados' },
|
||||
{ date: `${ano}-11-15`, name: 'Proclamação da República' },
|
||||
{ date: `${ano}-12-25`, name: 'Natal' },
|
||||
];
|
||||
return feriadosCache[ano];
|
||||
}
|
||||
}
|
||||
|
||||
async function renderFeriados() {
|
||||
const container = document.getElementById('feriados');
|
||||
container.replaceChildren();
|
||||
|
||||
@@ -516,18 +676,27 @@ function renderFeriados() {
|
||||
header.textContent = 'FERIADOS NACIONAIS';
|
||||
container.appendChild(header);
|
||||
|
||||
const feriadosMes = [
|
||||
{ mes: 0, dia: 1, texto: '01 Jan – Ano Novo' },
|
||||
{ mes: 3, dia: 21, texto: '21 Abr – Tiradentes' },
|
||||
{ mes: 4, dia: 1, texto: '01 Mai – Dia do Trabalho' },
|
||||
{ mes: 8, dia: 7, texto: '07 Set – Independência' },
|
||||
{ mes: 9, dia: 12, texto: '12 Out – N. Sra. Aparecida' },
|
||||
{ mes: 10, dia: 2, texto: '02 Nov – Finados' },
|
||||
{ mes: 10, dia: 15, texto: '15 Nov – República' },
|
||||
{ mes: 11, dia: 25, texto: '25 Dez – Natal' },
|
||||
].filter(f => f.mes === dataMini.getMonth());
|
||||
const loading = document.createElement('div');
|
||||
loading.className = 'feriado';
|
||||
loading.innerHTML = '<span style="opacity:0.5;font-size:11px">Carregando...</span>';
|
||||
container.appendChild(loading);
|
||||
|
||||
if (feriadosMes.length === 0) {
|
||||
const ano = dataMini.getFullYear();
|
||||
const mes = dataMini.getMonth(); // 0-11
|
||||
const todosFeriados = await buscarFeriadosAno(ano);
|
||||
|
||||
container.replaceChildren();
|
||||
container.appendChild(header);
|
||||
|
||||
// BrasilAPI usa date no formato "YYYY-MM-DD"
|
||||
const nomeMeses = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
|
||||
const doMes = todosFeriados.filter(f => {
|
||||
if (!f.date) return false;
|
||||
const m = parseInt(f.date.substring(5, 7)) - 1; // 0-indexed
|
||||
return m === mes;
|
||||
});
|
||||
|
||||
if (doMes.length === 0) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'feriado';
|
||||
item.innerHTML = '<span style="opacity:0.6;font-size:12px">Nenhum este mês</span>';
|
||||
@@ -535,10 +704,12 @@ function renderFeriados() {
|
||||
return;
|
||||
}
|
||||
|
||||
feriadosMes.forEach(f => {
|
||||
doMes.forEach(f => {
|
||||
const dia = f.date.substring(8, 10);
|
||||
const nomeMes = nomeMeses[mes];
|
||||
const item = document.createElement('div');
|
||||
item.className = 'feriado';
|
||||
item.innerHTML = `<span class="dot"></span><span>${f.texto}</span>`;
|
||||
item.innerHTML = `<span class="dot"></span><span>${dia} ${nomeMes} – ${f.name}</span>`;
|
||||
container.appendChild(item);
|
||||
});
|
||||
}
|
||||
@@ -789,34 +960,21 @@ function abrirModalEvento(ev) {
|
||||
}
|
||||
|
||||
document.getElementById('btnNovoEvento').addEventListener('click', () => abrirModalEvento(null));
|
||||
document.getElementById('btnGerenciarDisciplinas').addEventListener('click', () => { renderListaDisciplinas(); abrirModalDisciplina(null); });
|
||||
|
||||
function renderListaDisciplinas() {
|
||||
const lista = document.getElementById('listaDisciplinas');
|
||||
lista.innerHTML = '';
|
||||
if (!disciplinas.length) {
|
||||
lista.innerHTML = '<p style="color:var(--texto2); text-align:center;">Nenhuma disciplina cadastrada</p>';
|
||||
return;
|
||||
}
|
||||
disciplinas.forEach(d => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'item-lista';
|
||||
div.style.cssText = 'display:flex; align-items:center; justify-content:space-between; padding:10px; border-bottom:1px solid var(--borda); cursor:pointer;';
|
||||
div.innerHTML = `<span style="display:flex; align-items:center; gap:8px;"><span style="width:12px; height:12px; border-radius:50%; background:${d.cor||'#4a90e2'}"></span><b>${d.nome||''}</b></span>`;
|
||||
div.onclick = () => abrirModalDisciplina(d);
|
||||
lista.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
// Event Listeners Disciplina
|
||||
document.getElementById('btnFecharDisciplina').addEventListener('click', () => document.getElementById('modalDisciplina').classList.remove('aberto'));
|
||||
document.getElementById('btnCancelarDisciplina').addEventListener('click', () => document.getElementById('modalDisciplina').classList.remove('aberto'));
|
||||
document.getElementById('btnSalvarDisciplina').addEventListener('click', salvarDisciplina);
|
||||
document.getElementById('btnExcluirDisciplina').addEventListener('click', excluirDisciplina);
|
||||
document.getElementById('btnGerenciarDisciplinas').addEventListener('click', () => abrirGerenciarDisciplinas());
|
||||
|
||||
// =============================================
|
||||
// MODAL DE DISCIPLINA — dois painéis
|
||||
// =============================================
|
||||
let disciplinaEditandoId = null;
|
||||
|
||||
function abrirModalDisciplina(disc) {
|
||||
function mostrarPainelLista() {
|
||||
document.getElementById('tituloDisciplina').textContent = 'Gerenciar Disciplinas';
|
||||
document.getElementById('painelListaDisciplinas').style.display = '';
|
||||
document.getElementById('painelFormDisciplina').style.display = 'none';
|
||||
renderListaDisciplinas();
|
||||
}
|
||||
|
||||
function mostrarPainelForm(disc) {
|
||||
disciplinaEditandoId = disc ? disc.id : null;
|
||||
document.getElementById('tituloDisciplina').textContent = disc ? 'Editar Disciplina' : 'Nova Disciplina';
|
||||
document.getElementById('discNome').value = disc ? (disc.nome || '') : '';
|
||||
@@ -824,10 +982,43 @@ function abrirModalDisciplina(disc) {
|
||||
document.getElementById('discSala').value = disc ? (disc.sala || '') : '';
|
||||
document.getElementById('discCor').value = disc && disc.cor ? disc.cor : '#4a90e2';
|
||||
document.getElementById('btnExcluirDisciplina').style.display = disc ? 'inline-block' : 'none';
|
||||
renderListaDisciplinas();
|
||||
document.getElementById('painelListaDisciplinas').style.display = 'none';
|
||||
document.getElementById('painelFormDisciplina').style.display = '';
|
||||
setTimeout(() => document.getElementById('discNome').focus(), 50);
|
||||
}
|
||||
|
||||
function abrirGerenciarDisciplinas() {
|
||||
mostrarPainelLista();
|
||||
document.getElementById('modalDisciplina').classList.add('aberto');
|
||||
}
|
||||
|
||||
function renderListaDisciplinas() {
|
||||
const lista = document.getElementById('listaDisciplinas');
|
||||
lista.innerHTML = '';
|
||||
if (!disciplinas.length) {
|
||||
lista.innerHTML = '<p style="color:var(--texto2); text-align:center; padding: 20px 0;">Nenhuma disciplina cadastrada</p>';
|
||||
return;
|
||||
}
|
||||
disciplinas.forEach(d => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'disc-item';
|
||||
div.innerHTML = `
|
||||
<span class="disc-cor" style="background:${d.cor||'#4a90e2'}"></span>
|
||||
<span class="disc-nome">${d.nome||''}</span>
|
||||
${d.professor ? `<span class="disc-info">${d.professor}</span>` : ''}
|
||||
<span class="disc-seta">›</span>`;
|
||||
div.onclick = () => mostrarPainelForm(d);
|
||||
lista.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
// Event Listeners Disciplina
|
||||
document.getElementById('btnFecharDisciplina').addEventListener('click', () => document.getElementById('modalDisciplina').classList.remove('aberto'));
|
||||
document.getElementById('btnVoltarDisciplina').addEventListener('click', () => mostrarPainelLista());
|
||||
document.getElementById('btnNovaDisciplina').addEventListener('click', () => mostrarPainelForm(null));
|
||||
document.getElementById('btnSalvarDisciplina').addEventListener('click', salvarDisciplina);
|
||||
document.getElementById('btnExcluirDisciplina').addEventListener('click', excluirDisciplina);
|
||||
|
||||
async function salvarDisciplina() {
|
||||
const nome = document.getElementById('discNome').value.trim();
|
||||
if (!nome) { mostrarToast('Nome e obrigatorio.', 'erro'); return; }
|
||||
@@ -853,7 +1044,8 @@ async function salvarDisciplina() {
|
||||
disciplinas.push(novo);
|
||||
mostrarToast('Disciplina criada!');
|
||||
}
|
||||
document.getElementById('modalDisciplina').classList.remove('aberto');
|
||||
preencherSelectsDisciplina();
|
||||
mostrarPainelLista();
|
||||
renderCalendarioGrande();
|
||||
renderAgenda();
|
||||
} catch(e) {
|
||||
@@ -870,7 +1062,7 @@ async function excluirDisciplina() {
|
||||
try {
|
||||
await api('DELETE', `/api/disciplinas/${disciplinaEditandoId}`);
|
||||
disciplinas = disciplinas.filter(d => d.id !== disciplinaEditandoId);
|
||||
document.getElementById('modalDisciplina').classList.remove('aberto');
|
||||
mostrarPainelLista();
|
||||
mostrarToast('Disciplina excluida!');
|
||||
renderCalendarioGrande();
|
||||
renderAgenda();
|
||||
@@ -1075,9 +1267,130 @@ async function init() {
|
||||
renderMini();
|
||||
renderCalendarioGrande();
|
||||
renderAgenda();
|
||||
renderFeriados();
|
||||
await renderFeriados();
|
||||
}
|
||||
|
||||
|
||||
// =============================================
|
||||
// NOTIFICACOES
|
||||
// =============================================
|
||||
let notificacoes = [];
|
||||
let notifPanelAberto = false;
|
||||
|
||||
function tempoRelativo(dataStr) {
|
||||
if (!dataStr) return '';
|
||||
const agora = new Date();
|
||||
const data = new Date(dataStr);
|
||||
const diff = Math.floor((agora - data) / 1000);
|
||||
if (diff < 60) return 'agora';
|
||||
if (diff < 3600) return Math.floor(diff / 60) + 'min atrás';
|
||||
if (diff < 86400) return Math.floor(diff / 3600) + 'h atrás';
|
||||
return Math.floor(diff / 86400) + 'd atrás';
|
||||
}
|
||||
|
||||
function iconeNotif(tipo) {
|
||||
if (tipo === 'PRAZO_PROXIMO') return { cls: 'prazo', emoji: '⏰' };
|
||||
if (tipo === 'TAREFA_ATRASADA') return { cls: 'atrasada', emoji: '🔴' };
|
||||
if (tipo === 'EVENTO_PROXIMO') return { cls: 'evento', emoji: '📅' };
|
||||
return { cls: 'evento', emoji: '🔔' };
|
||||
}
|
||||
|
||||
function renderNotificacoes() {
|
||||
const lista = document.getElementById('notifLista');
|
||||
const badge = document.getElementById('notifBadge');
|
||||
|
||||
const naoLidas = notificacoes.filter(n => !n.lida);
|
||||
const qtd = naoLidas.length;
|
||||
|
||||
if (qtd > 0) {
|
||||
badge.textContent = qtd > 99 ? '99+' : qtd;
|
||||
badge.style.display = 'flex';
|
||||
} else {
|
||||
badge.style.display = 'none';
|
||||
}
|
||||
|
||||
if (!notificacoes.length) {
|
||||
lista.innerHTML = '<div class="notif-vazia">Nenhuma notificação</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const ordenadas = [...notificacoes].sort((a, b) => {
|
||||
if (!a.lida && b.lida) return -1;
|
||||
if (a.lida && !b.lida) return 1;
|
||||
return new Date(b.dataGeracao || 0) - new Date(a.dataGeracao || 0);
|
||||
});
|
||||
|
||||
lista.innerHTML = ordenadas.map(n => {
|
||||
const { cls, emoji } = iconeNotif(n.tipo);
|
||||
const lidaCls = n.lida ? 'lida' : 'nao-lida';
|
||||
return `<div class="notif-item ${lidaCls}" data-id="${n.id}" onclick="clicarNotificacao('${n.id}')">
|
||||
<div class="notif-icone ${cls}">${emoji}</div>
|
||||
<div class="notif-corpo">
|
||||
<div class="notif-titulo-item">${n.titulo || 'Notificação'}</div>
|
||||
<div class="notif-msg">${n.mensagem || ''}</div>
|
||||
<div class="notif-tempo">${tempoRelativo(n.dataGeracao)}</div>
|
||||
</div>
|
||||
<div class="notif-ponto"></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function carregarNotificacoes() {
|
||||
if (!token) return;
|
||||
try {
|
||||
const dados = await api('GET', '/api/notificacoes/me');
|
||||
notificacoes = dados || [];
|
||||
renderNotificacoes();
|
||||
} catch(e) {
|
||||
console.error('Erro ao carregar notificações:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function clicarNotificacao(id) {
|
||||
const n = notificacoes.find(x => x.id === id);
|
||||
if (!n) return;
|
||||
if (!n.lida) {
|
||||
try {
|
||||
await api('PATCH', `/api/notificacoes/${id}/ler`);
|
||||
n.lida = true;
|
||||
renderNotificacoes();
|
||||
} catch(e) { console.error(e); }
|
||||
}
|
||||
}
|
||||
|
||||
async function marcarTodasLidas() {
|
||||
if (!notificacoes.some(n => !n.lida)) return;
|
||||
try {
|
||||
await api('PATCH', '/api/notificacoes/me/ler-todas');
|
||||
notificacoes.forEach(n => n.lida = true);
|
||||
renderNotificacoes();
|
||||
} catch(e) {
|
||||
mostrarToast('Erro ao marcar notificações', 'erro');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('btnNotif').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const panel = document.getElementById('notifPanel');
|
||||
panel.classList.toggle('aberto');
|
||||
});
|
||||
|
||||
document.getElementById('btnMarcarTodas').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
marcarTodasLidas();
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const wrapper = document.getElementById('notifWrapper');
|
||||
if (!wrapper.contains(e.target)) {
|
||||
document.getElementById('notifPanel').classList.remove('aberto');
|
||||
}
|
||||
});
|
||||
|
||||
// Polling a cada 60 segundos
|
||||
setInterval(carregarNotificacoes, 60000);
|
||||
|
||||
carregarNotificacoes();
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user