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.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
@@ -44,6 +46,8 @@ public class ConfiguracaoSeguranca {
|
|||||||
"/swagger-ui/**", "/v3/api-docs/**")
|
"/swagger-ui/**", "/v3/api-docs/**")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.anyRequest().authenticated())
|
.anyRequest().authenticated())
|
||||||
|
.exceptionHandling(ex -> ex
|
||||||
|
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
|
||||||
.sessionManagement(session -> session
|
.sessionManagement(session -> session
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|||||||
@@ -157,6 +157,19 @@ body {
|
|||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
#btnNovoEvento:hover { background: #a03224; }
|
#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 */
|
||||||
.calendar-area { background: #fff; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); overflow: hidden; }
|
.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"] #toast { background: #2a2a2a; }
|
||||||
|
|
||||||
[data-theme="dark"] .mais-eventos { color: var(--text-secondary); }
|
[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=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">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script src="theme.js"></script>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -54,6 +163,21 @@
|
|||||||
<span class="cargo" id="cursoUsuario">–</span>
|
<span class="cargo" id="cursoUsuario">–</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<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>
|
<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>
|
<button id="btnLogout" title="Sair">Sair</button>
|
||||||
@@ -196,34 +320,44 @@
|
|||||||
<!-- MODAL DE DISCIPLINA -->
|
<!-- MODAL DE DISCIPLINA -->
|
||||||
<div class="modal-overlay" id="modalDisciplina">
|
<div class="modal-overlay" id="modalDisciplina">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="modal-header">
|
<div class="modal-disc-header">
|
||||||
<h2 id="tituloDisciplina">Gerenciar Disciplinas</h2>
|
<h2 id="tituloDisciplina">Gerenciar Disciplinas</h2>
|
||||||
<button class="modal-fechar" id="btnFecharDisciplina">×</button>
|
<button class="modal-fechar-btn" id="btnFecharDisciplina">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-corpo">
|
|
||||||
<div class="lista-itens" id="listaDisciplinas"></div>
|
<!-- Painel 1: Lista de disciplinas -->
|
||||||
<hr style="margin: 15px 0; border: none; border-top: 1px solid var(--borda);">
|
<div id="painelListaDisciplinas">
|
||||||
<div class="modal-campo">
|
<div class="modal-corpo-lista" id="listaDisciplinas"></div>
|
||||||
<label for="discNome">Nome *</label>
|
<div class="modal-acoes">
|
||||||
<input type="text" id="discNome" placeholder="Ex: Calculo I">
|
<button class="btn-primario" id="btnNovaDisciplina">+ Nova Disciplina</button>
|
||||||
</div>
|
|
||||||
<div class="modal-campo">
|
|
||||||
<label for="discProfessor">Professor</label>
|
|
||||||
<input type="text" id="discProfessor" placeholder="Nome do professor">
|
|
||||||
</div>
|
|
||||||
<div class="modal-campo">
|
|
||||||
<label for="discSala">Sala</label>
|
|
||||||
<input type="text" id="discSala" placeholder="Ex: Sala 204">
|
|
||||||
</div>
|
|
||||||
<div class="modal-campo">
|
|
||||||
<label for="discCor">Cor</label>
|
|
||||||
<input type="color" id="discCor" value="#4a90e2">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-acoes">
|
|
||||||
<button class="btn-perigo" id="btnExcluirDisciplina" style="display:none">Excluir</button>
|
<!-- Painel 2: Formulário add/edit -->
|
||||||
<button class="btn-secundario" id="btnCancelarDisciplina">Cancelar</button>
|
<div id="painelFormDisciplina" style="display:none">
|
||||||
<button class="btn-primario" id="btnSalvarDisciplina">Salvar</button>
|
<div class="modal-corpo-form">
|
||||||
|
<div class="modal-campo">
|
||||||
|
<label for="discNome">Nome *</label>
|
||||||
|
<input type="text" id="discNome" placeholder="Ex: Calculo I">
|
||||||
|
</div>
|
||||||
|
<div class="modal-campo">
|
||||||
|
<label for="discProfessor">Professor</label>
|
||||||
|
<input type="text" id="discProfessor" placeholder="Nome do professor">
|
||||||
|
</div>
|
||||||
|
<div class="modal-campo">
|
||||||
|
<label for="discSala">Sala</label>
|
||||||
|
<input type="text" id="discSala" placeholder="Ex: Sala 204">
|
||||||
|
</div>
|
||||||
|
<div class="modal-campo">
|
||||||
|
<label for="discCor">Cor</label>
|
||||||
|
<input type="color" id="discCor" value="#4a90e2">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-acoes">
|
||||||
|
<button class="btn-perigo" id="btnExcluirDisciplina" style="display:none">Excluir</button>
|
||||||
|
<button class="btn-secundario" id="btnVoltarDisciplina">← Voltar</button>
|
||||||
|
<button class="btn-primario" id="btnSalvarDisciplina">Salvar</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -266,8 +400,7 @@ async function api(method, path, body) {
|
|||||||
const res = await fetch(path, opts);
|
const res = await fetch(path, opts);
|
||||||
|
|
||||||
if (res.status === 401 || res.status === 403) {
|
if (res.status === 401 || res.status === 403) {
|
||||||
// Token expirado ou inválido
|
logout(); return;
|
||||||
if (res.status === 401) { logout(); return; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = await res.json().catch(() => ({}));
|
const json = await res.json().catch(() => ({}));
|
||||||
@@ -305,6 +438,8 @@ let disciplinas = [];
|
|||||||
let eventoEditandoId = null;
|
let eventoEditandoId = null;
|
||||||
let tarefaEditandoId = null;
|
let tarefaEditandoId = null;
|
||||||
|
|
||||||
|
const feriadosCache = {};
|
||||||
|
|
||||||
const meses = ['Janeiro','Fevereiro','Março','Abril','Maio','Junho',
|
const meses = ['Janeiro','Fevereiro','Março','Abril','Maio','Junho',
|
||||||
'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'];
|
'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'];
|
||||||
const diasCurto = ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'];
|
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');
|
const container = document.getElementById('feriados');
|
||||||
container.replaceChildren();
|
container.replaceChildren();
|
||||||
|
|
||||||
@@ -516,18 +676,27 @@ function renderFeriados() {
|
|||||||
header.textContent = 'FERIADOS NACIONAIS';
|
header.textContent = 'FERIADOS NACIONAIS';
|
||||||
container.appendChild(header);
|
container.appendChild(header);
|
||||||
|
|
||||||
const feriadosMes = [
|
const loading = document.createElement('div');
|
||||||
{ mes: 0, dia: 1, texto: '01 Jan – Ano Novo' },
|
loading.className = 'feriado';
|
||||||
{ mes: 3, dia: 21, texto: '21 Abr – Tiradentes' },
|
loading.innerHTML = '<span style="opacity:0.5;font-size:11px">Carregando...</span>';
|
||||||
{ mes: 4, dia: 1, texto: '01 Mai – Dia do Trabalho' },
|
container.appendChild(loading);
|
||||||
{ 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());
|
|
||||||
|
|
||||||
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');
|
const item = document.createElement('div');
|
||||||
item.className = 'feriado';
|
item.className = 'feriado';
|
||||||
item.innerHTML = '<span style="opacity:0.6;font-size:12px">Nenhum este mês</span>';
|
item.innerHTML = '<span style="opacity:0.6;font-size:12px">Nenhum este mês</span>';
|
||||||
@@ -535,10 +704,12 @@ function renderFeriados() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
feriadosMes.forEach(f => {
|
doMes.forEach(f => {
|
||||||
|
const dia = f.date.substring(8, 10);
|
||||||
|
const nomeMes = nomeMeses[mes];
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'feriado';
|
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);
|
container.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -789,34 +960,21 @@ function abrirModalEvento(ev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('btnNovoEvento').addEventListener('click', () => abrirModalEvento(null));
|
document.getElementById('btnNovoEvento').addEventListener('click', () => abrirModalEvento(null));
|
||||||
document.getElementById('btnGerenciarDisciplinas').addEventListener('click', () => { renderListaDisciplinas(); abrirModalDisciplina(null); });
|
document.getElementById('btnGerenciarDisciplinas').addEventListener('click', () => abrirGerenciarDisciplinas());
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// MODAL DE DISCIPLINA — dois painéis
|
||||||
|
// =============================================
|
||||||
let disciplinaEditandoId = null;
|
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;
|
disciplinaEditandoId = disc ? disc.id : null;
|
||||||
document.getElementById('tituloDisciplina').textContent = disc ? 'Editar Disciplina' : 'Nova Disciplina';
|
document.getElementById('tituloDisciplina').textContent = disc ? 'Editar Disciplina' : 'Nova Disciplina';
|
||||||
document.getElementById('discNome').value = disc ? (disc.nome || '') : '';
|
document.getElementById('discNome').value = disc ? (disc.nome || '') : '';
|
||||||
@@ -824,10 +982,43 @@ function abrirModalDisciplina(disc) {
|
|||||||
document.getElementById('discSala').value = disc ? (disc.sala || '') : '';
|
document.getElementById('discSala').value = disc ? (disc.sala || '') : '';
|
||||||
document.getElementById('discCor').value = disc && disc.cor ? disc.cor : '#4a90e2';
|
document.getElementById('discCor').value = disc && disc.cor ? disc.cor : '#4a90e2';
|
||||||
document.getElementById('btnExcluirDisciplina').style.display = disc ? 'inline-block' : 'none';
|
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');
|
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() {
|
async function salvarDisciplina() {
|
||||||
const nome = document.getElementById('discNome').value.trim();
|
const nome = document.getElementById('discNome').value.trim();
|
||||||
if (!nome) { mostrarToast('Nome e obrigatorio.', 'erro'); return; }
|
if (!nome) { mostrarToast('Nome e obrigatorio.', 'erro'); return; }
|
||||||
@@ -853,7 +1044,8 @@ async function salvarDisciplina() {
|
|||||||
disciplinas.push(novo);
|
disciplinas.push(novo);
|
||||||
mostrarToast('Disciplina criada!');
|
mostrarToast('Disciplina criada!');
|
||||||
}
|
}
|
||||||
document.getElementById('modalDisciplina').classList.remove('aberto');
|
preencherSelectsDisciplina();
|
||||||
|
mostrarPainelLista();
|
||||||
renderCalendarioGrande();
|
renderCalendarioGrande();
|
||||||
renderAgenda();
|
renderAgenda();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
@@ -870,7 +1062,7 @@ async function excluirDisciplina() {
|
|||||||
try {
|
try {
|
||||||
await api('DELETE', `/api/disciplinas/${disciplinaEditandoId}`);
|
await api('DELETE', `/api/disciplinas/${disciplinaEditandoId}`);
|
||||||
disciplinas = disciplinas.filter(d => d.id !== disciplinaEditandoId);
|
disciplinas = disciplinas.filter(d => d.id !== disciplinaEditandoId);
|
||||||
document.getElementById('modalDisciplina').classList.remove('aberto');
|
mostrarPainelLista();
|
||||||
mostrarToast('Disciplina excluida!');
|
mostrarToast('Disciplina excluida!');
|
||||||
renderCalendarioGrande();
|
renderCalendarioGrande();
|
||||||
renderAgenda();
|
renderAgenda();
|
||||||
@@ -1075,9 +1267,130 @@ async function init() {
|
|||||||
renderMini();
|
renderMini();
|
||||||
renderCalendarioGrande();
|
renderCalendarioGrande();
|
||||||
renderAgenda();
|
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();
|
init();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user