Files
FrontFocusAgenda/calendario.html
T
Gustavo b1dd3a8b01 fodase :)
meco meram, meco meram, meco meram
2026-05-12 23:27:37 +00:00

961 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Focus Agenda</title>
<link rel="icon" href="imagens/icone.png">
<link rel="stylesheet" href="calendario.css">
<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">
<style>
@import url('https://fonts.googleapis.com/css2?family=Nixie+One&display=swap');
</style>
</head>
<body>
<!-- HEADER -->
<div id="header">
<h1 id="title">Focus Agenda</h1>
</div>
<!-- BARRA ESQUERDA -->
<div id="barraesquerda">
<div id="calendario">
<div class="calendariotop">
<div id="mes"></div>
<div id="calendarseta">
<button class="ant" aria-label="Mês anterior">&lsaquo;</button>
<button class="prox" aria-label="Próximo mês">&rsaquo;</button>
</div>
</div>
<table class="calendariodia">
<thead>
<tr>
<th>DOM</th><th>SEG</th><th>TER</th><th>QUA</th>
<th>QUI</th><th>SEX</th><th>SAB</th>
</tr>
</thead>
<tbody id="dias"></tbody>
</table>
</div>
<div id="agenda"></div>
<div id="feriados"></div>
</div>
<!-- CONTEUDO PRINCIPAL -->
<div class="main">
<div class="topbar">
<h1>Calendário</h1>
<div class="user-area">
<div class="perfil">
<div class="avatar"></div>
<div class="info">
<span class="nome" id="nomeUsuario">Carregando...</span>
<span class="cargo" id="cursoUsuario"></span>
</div>
</div>
<button id="btnLogout" title="Sair">Sair</button>
</div>
</div>
<div class="calendar-header">
<div class="mes-nav">
<button class="seta antGrande" aria-label="Anterior">&lsaquo;</button>
<span class="titulo-mes">Janeiro, 2025</span>
<button class="seta proxGrande" aria-label="Próximo">&rsaquo;</button>
</div>
<div class="view-switch">
<button type="button" data-view="dia">Dia</button>
<button type="button" data-view="semana">Semana</button>
<button type="button" data-view="mes" class="active">Mês</button>
</div>
<button id="btnNovoEvento">+ Novo Evento</button>
</div>
<div class="calendar-area month-view" id="calendarArea"></div>
</div>
<!-- MODAL DE EVENTO -->
<div class="modal-overlay" id="modalEvento">
<div class="modal">
<div class="modal-titulo" id="modalEventoTitulo">Novo Evento</div>
<div class="modal-campo">
<label for="evTitulo">Título *</label>
<input type="text" id="evTitulo" placeholder="Ex: Prova de Matemática">
</div>
<div class="modal-campo">
<label for="evDescricao">Descrição</label>
<textarea id="evDescricao" placeholder="Detalhes do evento..."></textarea>
</div>
<div class="modal-linha">
<div class="modal-campo">
<label for="evTipo">Tipo *</label>
<select id="evTipo">
<option value="AULA">Aula</option>
<option value="PROVA">Prova</option>
<option value="TRABALHO">Trabalho</option>
<option value="ESTUDO">Estudo</option>
<option value="EXAME">Exame</option>
<option value="OUTRO">Outro</option>
</select>
</div>
<div class="modal-campo">
<label for="evDisciplina">Disciplina</label>
<select id="evDisciplina">
<option value="">Nenhuma</option>
</select>
</div>
</div>
<div class="modal-linha">
<div class="modal-campo">
<label for="evData">Data e Hora *</label>
<input type="datetime-local" id="evData">
</div>
<div class="modal-campo">
<label for="evLocal">Local</label>
<input type="text" id="evLocal" placeholder="Ex: Sala 204">
</div>
</div>
<div class="modal-acoes">
<button class="btn-perigo" id="btnExcluirEvento" style="display:none">Excluir</button>
<button class="btn-secundario" id="btnCancelarEvento">Cancelar</button>
<button class="btn-primario" id="btnSalvarEvento">Salvar</button>
</div>
</div>
</div>
<!-- MODAL DE TAREFA -->
<div class="modal-overlay" id="modalTarefa">
<div class="modal">
<div class="modal-titulo" id="modalTarefaTitulo">Nova Tarefa</div>
<div class="modal-campo">
<label for="tfTitulo">Título *</label>
<input type="text" id="tfTitulo" placeholder="Ex: Estudar para a prova">
</div>
<div class="modal-campo">
<label for="tfDescricao">Descrição</label>
<textarea id="tfDescricao" placeholder="Detalhes da tarefa..."></textarea>
</div>
<div class="modal-linha">
<div class="modal-campo">
<label for="tfPrioridade">Prioridade *</label>
<select id="tfPrioridade">
<option value="BAIXA">Baixa</option>
<option value="MEDIA" selected>Média</option>
<option value="ALTA">Alta</option>
<option value="URGENTE">Urgente</option>
</select>
</div>
<div class="modal-campo">
<label for="tfStatus">Status</label>
<select id="tfStatus">
<option value="PENDENTE" selected>Pendente</option>
<option value="EM_ANDAMENTO">Em Andamento</option>
<option value="CONCLUIDA">Concluída</option>
</select>
</div>
</div>
<div class="modal-linha">
<div class="modal-campo">
<label for="tfData">Data de Entrega *</label>
<input type="date" id="tfData">
</div>
<div class="modal-campo">
<label for="tfDisciplina">Disciplina</label>
<select id="tfDisciplina">
<option value="">Nenhuma</option>
</select>
</div>
</div>
<div class="modal-acoes">
<button class="btn-perigo" id="btnExcluirTarefa" style="display:none">Excluir</button>
<button class="btn-secundario" id="btnCancelarTarefa">Cancelar</button>
<button class="btn-primario" id="btnSalvarTarefa">Salvar</button>
</div>
</div>
</div>
<!-- TOAST -->
<div id="toast"></div>
<script>
// =============================================
// AUTENTICAÇÃO & BOOTSTRAP
// =============================================
const token = localStorage.getItem('fa_token');
const userStr = localStorage.getItem('fa_user');
if (!token) {
window.location.href = 'login.html';
}
let usuario = null;
try { usuario = JSON.parse(userStr); } catch(e) {}
function logout() {
localStorage.removeItem('fa_token');
localStorage.removeItem('fa_user');
window.location.href = 'login.html';
}
document.getElementById('btnLogout').addEventListener('click', logout);
// =============================================
// API HELPERS
// =============================================
async function api(method, path, body) {
const opts = {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
}
};
if (body) opts.body = JSON.stringify(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; }
}
const json = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(json.message || 'Erro na requisição');
}
return json.data;
}
// =============================================
// TOAST
// =============================================
const toastEl = document.getElementById('toast');
let toastTimer;
function mostrarToast(msg, tipo = 'sucesso') {
clearTimeout(toastTimer);
toastEl.textContent = msg;
toastEl.className = 'visivel ' + tipo;
toastTimer = setTimeout(() => { toastEl.className = ''; }, 3200);
}
// =============================================
// ESTADO GLOBAL
// =============================================
let dataMini = new Date();
let dataGrande = new Date();
let modoAtual = 'mes';
let dataSelecionada = null;
let eventos = [];
let tarefas = [];
let disciplinas = [];
let eventoEditandoId = null;
let tarefaEditandoId = null;
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'];
const diasLongo = ['Domingo','Segunda','Terça','Quarta','Quinta','Sexta','Sábado'];
// =============================================
// CARREGA DADOS DO USUÁRIO
// =============================================
async function carregarUsuario() {
try {
const u = await api('GET', '/api/estudantes/me');
usuario = u;
localStorage.setItem('fa_user', JSON.stringify(u));
} catch(e) { /* usa cache */ }
if (usuario) {
document.getElementById('nomeUsuario').textContent = usuario.nome || 'Usuário';
document.getElementById('cursoUsuario').textContent =
usuario.curso ? (usuario.curso + (usuario.periodo ? ' ' + usuario.periodo + 'º período' : '')) : '';
}
}
// =============================================
// CARREGA DADOS DA API
// =============================================
async function carregarDados() {
if (!usuario) return;
const id = usuario.id;
try {
[eventos, tarefas, disciplinas] = await Promise.all([
api('GET', `/api/eventos/estudante/${id}`),
api('GET', `/api/tarefas/estudante/${id}`),
api('GET', `/api/disciplinas/estudante/${id}`)
]);
if (!eventos) eventos = [];
if (!tarefas) tarefas = [];
if (!disciplinas) disciplinas = [];
} catch(e) {
mostrarToast('Erro ao carregar dados: ' + e.message, 'erro');
}
}
function nomeDisciplina(id) {
if (!id) return null;
const d = disciplinas.find(d => d.id === id);
return d ? d.nome : null;
}
// Preenche selects de disciplina nos modais
function preencherSelectsDisciplina() {
['evDisciplina', 'tfDisciplina'].forEach(selId => {
const sel = document.getElementById(selId);
// Remove options exceto a primeira
while (sel.options.length > 1) sel.remove(1);
disciplinas.forEach(d => {
const opt = document.createElement('option');
opt.value = d.id;
opt.textContent = d.nome;
sel.appendChild(opt);
});
});
}
// =============================================
// NORMALIZAÇÃO DE DATAS
// =============================================
function normalizarData(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}
function formatarISO(d) {
const a = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2,'0');
const dia = String(d.getDate()).padStart(2,'0');
return `${a}-${m}-${dia}`;
}
function formatarCurta(d) {
return `${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}`;
}
function adicionarDias(d, n) {
const r = new Date(d);
r.setDate(r.getDate() + n);
return r;
}
function inicioSemana(d) {
const r = normalizarData(d);
r.setDate(r.getDate() - r.getDay());
return r;
}
// Pega a data ISO de um evento (pode ser dataHora ou dataEntrega)
function dataDoEvento(ev) {
const raw = ev.dataHora || ev.dataEntrega;
if (!raw) return null;
return raw.substring(0, 10); // 'YYYY-MM-DD'
}
function eventosDaData(iso) {
const evs = eventos.filter(e => dataDoEvento(e) === iso);
const tfs = tarefas.filter(t => t.dataEntrega === iso);
return { evs, tfs };
}
// Cor baseada no tipo do evento
const corPorTipo = { PROVA: 'amarelo', AULA: 'azul', TRABALHO: 'verde', EXAME: 'amarelo', OUTRO: '' };
// =============================================
// MINI CALENDÁRIO
// =============================================
function renderMini() {
const mesEl = document.getElementById('mes');
const diasEl = document.getElementById('dias');
const ano = dataMini.getFullYear();
const mes = dataMini.getMonth();
mesEl.textContent = `${meses[mes]} ${ano}`;
diasEl.replaceChildren();
const primeiroDia = new Date(ano, mes, 1).getDay();
const ultimoDia = new Date(ano, mes + 1, 0).getDate();
const ultimoAnterior = new Date(ano, mes, 0).getDate();
const totalCelulas = Math.ceil((primeiroDia + ultimoDia) / 7) * 7;
let diaAtual = 1, diaPros = 1;
for (let s = 0; s < totalCelulas / 7; s++) {
const linha = document.createElement('tr');
for (let ds = 0; ds < 7; ds++) {
const pos = s * 7 + ds;
const cel = document.createElement('td');
if (pos < primeiroDia) {
cel.textContent = ultimoAnterior - (primeiroDia - pos - 1);
cel.className = 'outromes';
} else if (diaAtual <= ultimoDia) {
cel.textContent = diaAtual;
const hoje = new Date();
if (diaAtual === hoje.getDate() && mes === hoje.getMonth() && ano === hoje.getFullYear()) {
cel.className = 'today';
}
const d = diaAtual;
cel.addEventListener('click', () => {
dataSelecionada = new Date(ano, mes, d);
dataGrande = new Date(dataSelecionada);
renderCalendarioGrande();
renderMini();
});
diaAtual++;
} else {
cel.textContent = diaPros++;
cel.className = 'outromes';
}
linha.appendChild(cel);
}
diasEl.appendChild(linha);
}
}
// =============================================
// AGENDA DO DIA (barra esquerda)
// =============================================
function renderAgenda() {
const container = document.getElementById('agenda');
container.replaceChildren();
const header = document.createElement('div');
header.className = 'agenda-header';
header.innerHTML = '<strong>HOJE</strong> ' +
new Intl.DateTimeFormat('pt-BR').format(new Date());
container.appendChild(header);
const hoje = formatarISO(normalizarData(new Date()));
const { evs, tfs } = eventosDaData(hoje);
const tudo = [
...evs.map(e => ({ titulo: e.titulo, hora: e.dataHora ? e.dataHora.substring(11,16) : '', tipo: 'evento' })),
...tfs.map(t => ({ titulo: t.titulo, hora: '', tipo: 'tarefa' }))
];
if (tudo.length === 0) {
const vazio = document.createElement('div');
vazio.className = 'agenda-empty';
vazio.textContent = 'Sem eventos para hoje.';
container.appendChild(vazio);
return;
}
tudo.forEach(item => {
const ev = document.createElement('div');
ev.className = 'evento';
ev.innerHTML = `<div class="hora">${item.hora || (item.tipo === 'tarefa' ? '📋 Tarefa' : '')}</div>
<div class="titulo">${item.titulo}</div>`;
container.appendChild(ev);
});
}
// =============================================
// FERIADOS (fixos Brasil)
// =============================================
function renderFeriados() {
const container = document.getElementById('feriados');
container.replaceChildren();
const header = document.createElement('div');
header.className = 'feriados-header';
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());
if (feriadosMes.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>';
container.appendChild(item);
return;
}
feriadosMes.forEach(f => {
const item = document.createElement('div');
item.className = 'feriado';
item.innerHTML = `<span class="dot"></span><span>${f.texto}</span>`;
container.appendChild(item);
});
}
// =============================================
// CALENDÁRIO GRANDE
// =============================================
function atualizarTituloGrande() {
const titulo = document.querySelector('.titulo-mes');
if (modoAtual === 'mes') {
titulo.textContent = `${meses[dataGrande.getMonth()]}, ${dataGrande.getFullYear()}`;
} else if (modoAtual === 'semana') {
const ini = inicioSemana(dataGrande);
const fim = adicionarDias(ini, 6);
titulo.textContent = `Semana ${formatarCurta(ini)} ${formatarCurta(fim)}/${fim.getFullYear()}`;
} else {
titulo.textContent = `${diasLongo[dataGrande.getDay()]}, ${formatarCurta(dataGrande)}/${dataGrande.getFullYear()}`;
}
}
function configurarArea(classe) {
const area = document.getElementById('calendarArea');
area.className = 'calendar-area ' + classe;
area.replaceChildren();
return area;
}
function criarCardEvento(ev, isTarefa) {
const card = document.createElement('div');
const cor = isTarefa ? 'verde' : (corPorTipo[ev.tipo] || '');
card.className = `calendar-event ${cor}`;
card.innerHTML = `
<div class="calendar-event-hora">${isTarefa ? '📋 Tarefa' : (ev.dataHora ? ev.dataHora.substring(11,16) : '')}</div>
<div class="calendar-event-titulo">${ev.titulo}</div>`;
card.addEventListener('click', (e) => {
e.stopPropagation();
if (isTarefa) abrirModalTarefa(ev);
else abrirModalEvento(ev);
});
return card;
}
/* MÊS */
function renderMes() {
const area = configurarArea('month-view');
atualizarTituloGrande();
diasCurto.forEach(d => {
const el = document.createElement('div');
el.className = 'dia-semana';
el.textContent = d;
area.appendChild(el);
});
const ano = dataGrande.getFullYear(), mes = dataGrande.getMonth();
const primeiroDia = new Date(ano, mes, 1).getDay();
const ultimoDia = new Date(ano, mes + 1, 0).getDate();
const anteriorUltimo = new Date(ano, mes, 0).getDate();
const hoje = normalizarData(new Date());
for (let i = 0; i < 42; i++) {
const box = document.createElement('div');
box.className = 'dia-box';
let dNum, dData, outroMes = false;
if (i < primeiroDia) {
dNum = anteriorUltimo - (primeiroDia - i - 1);
dData = new Date(ano, mes - 1, dNum);
outroMes = true;
} else if (i < primeiroDia + ultimoDia) {
dNum = i - primeiroDia + 1;
dData = new Date(ano, mes, dNum);
} else {
dNum = i - primeiroDia - ultimoDia + 1;
dData = new Date(ano, mes + 1, dNum);
outroMes = true;
}
if (outroMes) box.classList.add('outro-mes');
const iso = formatarISO(dData);
const isHoje = !outroMes && normalizarData(dData).getTime() === hoje.getTime();
if (isHoje) box.classList.add('today');
if (dataSelecionada && iso === formatarISO(normalizarData(dataSelecionada))) {
box.classList.add('selecionado');
}
const numEl = document.createElement('div');
numEl.className = 'num-dia';
numEl.textContent = dNum;
box.appendChild(numEl);
// Mostra até 3 eventos
const { evs, tfs } = eventosDaData(iso);
const todos = [...evs.map(e => ({ ...e, _tipo: 'evento' })), ...tfs.map(t => ({ ...t, _tipo: 'tarefa' }))];
const limite = 2;
todos.slice(0, limite).forEach(item => {
const mini = document.createElement('div');
const isTarefa = item._tipo === 'tarefa';
mini.className = 'evento-mini ' + (isTarefa ? 'verde' : (corPorTipo[item.tipo] || ''));
mini.textContent = item.titulo;
box.appendChild(mini);
});
if (todos.length > limite) {
const mais = document.createElement('div');
mais.className = 'mais-eventos';
mais.textContent = `+${todos.length - limite} mais`;
box.appendChild(mais);
}
box.addEventListener('click', () => {
dataSelecionada = dData;
dataGrande = new Date(dData);
renderCalendarioGrande();
renderMini();
});
area.appendChild(box);
}
}
/* SEMANA */
function renderSemana() {
const area = configurarArea('week-view');
atualizarTituloGrande();
const ini = inicioSemana(dataGrande);
const hoje = normalizarData(new Date());
for (let i = 0; i < 7; i++) {
const d = adicionarDias(ini, i);
const iso = formatarISO(d);
const { evs, tfs } = eventosDaData(iso);
const col = document.createElement('div');
col.className = 'week-col';
if (normalizarData(d).getTime() === hoje.getTime()) col.classList.add('today');
const head = document.createElement('div');
head.className = 'week-col-head';
head.textContent = diasCurto[d.getDay()];
const dt = document.createElement('span');
dt.className = 'week-col-date';
dt.textContent = `${formatarCurta(d)}/${d.getFullYear()}`;
head.appendChild(dt);
const lista = document.createElement('div');
lista.className = 'week-events';
const todos = [...evs.map(e => ({ ...e, _tipo: 'evento' })), ...tfs.map(t => ({ ...t, _tipo: 'tarefa' }))];
if (todos.length === 0) {
const vazio = document.createElement('div');
vazio.className = 'week-empty';
vazio.textContent = 'Sem eventos';
lista.appendChild(vazio);
} else {
todos.forEach(item => lista.appendChild(criarCardEvento(item, item._tipo === 'tarefa')));
}
col.appendChild(head);
col.appendChild(lista);
area.appendChild(col);
}
}
/* DIA */
function renderDia() {
const area = configurarArea('day-view');
atualizarTituloGrande();
const iso = formatarISO(normalizarData(dataGrande));
const { evs, tfs } = eventosDaData(iso);
const painel = document.createElement('div');
painel.className = 'day-panel';
const head = document.createElement('div');
head.className = 'day-panel-header';
head.textContent = `Agenda de ${formatarCurta(dataGrande)}/${dataGrande.getFullYear()}`;
painel.appendChild(head);
const lista = document.createElement('div');
lista.className = 'day-events';
const todos = [...evs.map(e => ({ ...e, _tipo: 'evento' })), ...tfs.map(t => ({ ...t, _tipo: 'tarefa' }))];
if (todos.length === 0) {
const vazio = document.createElement('div');
vazio.className = 'day-empty';
vazio.textContent = 'Nenhum evento para este dia.';
lista.appendChild(vazio);
} else {
todos.forEach(item => lista.appendChild(criarCardEvento(item, item._tipo === 'tarefa')));
}
painel.appendChild(lista);
area.appendChild(painel);
}
function renderCalendarioGrande() {
if (modoAtual === 'dia') renderDia();
else if (modoAtual === 'semana') renderSemana();
else renderMes();
}
function moverPeriodo(dir) {
if (modoAtual === 'dia') dataGrande.setDate(dataGrande.getDate() + dir);
else if (modoAtual === 'semana') dataGrande.setDate(dataGrande.getDate() + dir * 7);
else dataGrande.setMonth(dataGrande.getMonth() + dir);
renderCalendarioGrande();
atualizarTituloGrande();
}
// =============================================
// MODAL DE EVENTO
// =============================================
function abrirModalEvento(ev) {
eventoEditandoId = ev ? ev.id : null;
const titulo = document.getElementById('modalEventoTitulo');
const btnExcluir = document.getElementById('btnExcluirEvento');
titulo.textContent = ev ? 'Editar Evento' : 'Novo Evento';
btnExcluir.style.display = ev ? 'inline-block' : 'none';
// Preenche ou limpa campos
document.getElementById('evTitulo').value = ev ? ev.titulo : '';
document.getElementById('evDescricao').value = ev ? (ev.descricao || '') : '';
document.getElementById('evTipo').value = ev ? (ev.tipo || 'OUTRO') : 'OUTRO';
document.getElementById('evLocal').value = ev ? (ev.local || '') : '';
if (ev && ev.dataHora) {
// dataHora vem como "2025-04-01T14:00:00" datetime-local precisa de "2025-04-01T14:00"
document.getElementById('evData').value = ev.dataHora.substring(0, 16);
} else if (dataSelecionada) {
const iso = formatarISO(normalizarData(dataSelecionada));
document.getElementById('evData').value = iso + 'T08:00';
} else {
document.getElementById('evData').value = '';
}
document.getElementById('evDisciplina').value = ev ? (ev.disciplinaId || '') : '';
document.getElementById('modalEvento').classList.add('aberto');
document.getElementById('evTitulo').focus();
}
document.getElementById('btnNovoEvento').addEventListener('click', () => abrirModalEvento(null));
document.getElementById('btnCancelarEvento').addEventListener('click', () => {
document.getElementById('modalEvento').classList.remove('aberto');
});
document.getElementById('btnSalvarEvento').addEventListener('click', async () => {
const titulo = document.getElementById('evTitulo').value.trim();
const dataHoraRaw = document.getElementById('evData').value;
if (!titulo) { mostrarToast('Título é obrigatório.', 'erro'); return; }
if (!dataHoraRaw) { mostrarToast('Data e hora são obrigatórias.', 'erro'); return; }
const btn = document.getElementById('btnSalvarEvento');
btn.disabled = true;
const payload = {
titulo,
descricao: document.getElementById('evDescricao').value.trim() || null,
tipo: document.getElementById('evTipo').value,
local: document.getElementById('evLocal').value.trim() || null,
disciplinaId: document.getElementById('evDisciplina').value || null,
dataHora: dataHoraRaw + ':00', // adiciona segundos
estudanteId: usuario.id
};
try {
if (eventoEditandoId) {
const updated = await api('PUT', `/api/eventos/${eventoEditandoId}`, payload);
const idx = eventos.findIndex(e => e.id === eventoEditandoId);
if (idx !== -1) eventos[idx] = updated;
mostrarToast('Evento atualizado!');
} else {
const novo = await api('POST', '/api/eventos', payload);
eventos.push(novo);
mostrarToast('Evento criado!');
}
document.getElementById('modalEvento').classList.remove('aberto');
renderCalendarioGrande();
renderAgenda();
} catch(e) {
mostrarToast('Erro: ' + e.message, 'erro');
} finally {
btn.disabled = false;
}
});
document.getElementById('btnExcluirEvento').addEventListener('click', async () => {
if (!confirm('Excluir este evento?')) return;
try {
await api('DELETE', `/api/eventos/${eventoEditandoId}?estudanteId=${usuario.id}`);
eventos = eventos.filter(e => e.id !== eventoEditandoId);
document.getElementById('modalEvento').classList.remove('aberto');
mostrarToast('Evento excluído.', 'sucesso');
renderCalendarioGrande();
renderAgenda();
} catch(e) {
mostrarToast('Erro: ' + e.message, 'erro');
}
});
// =============================================
// MODAL DE TAREFA
// =============================================
function abrirModalTarefa(tf) {
tarefaEditandoId = tf ? tf.id : null;
const titulo = document.getElementById('modalTarefaTitulo');
const btnExcluir = document.getElementById('btnExcluirTarefa');
titulo.textContent = tf ? 'Editar Tarefa' : 'Nova Tarefa';
btnExcluir.style.display = tf ? 'inline-block' : 'none';
document.getElementById('tfTitulo').value = tf ? tf.titulo : '';
document.getElementById('tfDescricao').value = tf ? (tf.descricao || '') : '';
document.getElementById('tfPrioridade').value = tf ? (tf.prioridade || 'MEDIA') : 'MEDIA';
document.getElementById('tfStatus').value = tf ? (tf.status || 'PENDENTE') : 'PENDENTE';
document.getElementById('tfDisciplina').value = tf ? (tf.disciplinaId || '') : '';
if (tf && tf.dataEntrega) {
document.getElementById('tfData').value = tf.dataEntrega;
} else if (dataSelecionada) {
document.getElementById('tfData').value = formatarISO(normalizarData(dataSelecionada));
} else {
document.getElementById('tfData').value = '';
}
document.getElementById('modalTarefa').classList.add('aberto');
document.getElementById('tfTitulo').focus();
}
document.getElementById('btnCancelarTarefa').addEventListener('click', () => {
document.getElementById('modalTarefa').classList.remove('aberto');
});
document.getElementById('btnSalvarTarefa').addEventListener('click', async () => {
const titulo = document.getElementById('tfTitulo').value.trim();
const dataEntrega = document.getElementById('tfData').value;
if (!titulo) { mostrarToast('Título é obrigatório.', 'erro'); return; }
if (!dataEntrega) { mostrarToast('Data de entrega é obrigatória.', 'erro'); return; }
const btn = document.getElementById('btnSalvarTarefa');
btn.disabled = true;
const payload = {
titulo,
descricao: document.getElementById('tfDescricao').value.trim() || null,
prioridade: document.getElementById('tfPrioridade').value,
status: document.getElementById('tfStatus').value,
dataEntrega,
disciplinaId: document.getElementById('tfDisciplina').value || null,
estudanteId: usuario.id
};
try {
if (tarefaEditandoId) {
const updated = await api('PUT', `/api/tarefas/${tarefaEditandoId}`, payload);
const idx = tarefas.findIndex(t => t.id === tarefaEditandoId);
if (idx !== -1) tarefas[idx] = updated;
mostrarToast('Tarefa atualizada!');
} else {
const nova = await api('POST', '/api/tarefas', payload);
tarefas.push(nova);
mostrarToast('Tarefa criada!');
}
document.getElementById('modalTarefa').classList.remove('aberto');
renderCalendarioGrande();
renderAgenda();
} catch(e) {
mostrarToast('Erro: ' + e.message, 'erro');
} finally {
btn.disabled = false;
}
});
document.getElementById('btnExcluirTarefa').addEventListener('click', async () => {
if (!confirm('Excluir esta tarefa?')) return;
try {
await api('DELETE', `/api/tarefas/${tarefaEditandoId}`);
tarefas = tarefas.filter(t => t.id !== tarefaEditandoId);
document.getElementById('modalTarefa').classList.remove('aberto');
mostrarToast('Tarefa excluída.', 'sucesso');
renderCalendarioGrande();
renderAgenda();
} catch(e) {
mostrarToast('Erro: ' + e.message, 'erro');
}
});
// Fecha modais clicando no overlay
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.classList.remove('aberto');
});
});
// Fecha modais com ESC
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
document.querySelectorAll('.modal-overlay.aberto').forEach(m => m.classList.remove('aberto'));
}
});
// =============================================
// NAVEGAÇÃO E VIEW SWITCH
// =============================================
document.querySelector('.prox').onclick = () => { dataMini.setMonth(dataMini.getMonth() + 1); renderMini(); renderFeriados(); };
document.querySelector('.ant').onclick = () => { dataMini.setMonth(dataMini.getMonth() - 1); renderMini(); renderFeriados(); };
document.querySelector('.antGrande').onclick = () => moverPeriodo(-1);
document.querySelector('.proxGrande').onclick = () => moverPeriodo(1);
document.querySelectorAll('.view-switch button').forEach(btn => {
btn.addEventListener('click', () => {
modoAtual = btn.dataset.view;
document.querySelectorAll('.view-switch button').forEach(b => b.classList.toggle('active', b === btn));
renderCalendarioGrande();
});
});
// Duplo clique em dia abre modal de novo evento
document.getElementById('calendarArea').addEventListener('dblclick', (e) => {
abrirModalEvento(null);
});
// =============================================
// INICIALIZAÇÃO
// =============================================
async function init() {
// Mostra spinner enquanto carrega
document.getElementById('calendarArea').innerHTML =
'<div class="loading"><div class="spinner"></div>Carregando...</div>';
await carregarUsuario();
await carregarDados();
preencherSelectsDisciplina();
renderMini();
renderCalendarioGrande();
renderAgenda();
renderFeriados();
}
init();
</script>
</body>
</html>