Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fde3f44f4e |
@@ -1,37 +0,0 @@
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
# Eclipse
|
||||
.settings/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
*.factorypath
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -1,28 +0,0 @@
|
||||
FROM maven:3.9-eclipse-temurin-17 AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pom.xml .
|
||||
RUN mvn dependency:go-offline -B
|
||||
|
||||
COPY src ./src
|
||||
RUN mvn clean package -DskipTests
|
||||
|
||||
FROM eclipse-temurin:17-jre-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
||||
|
||||
COPY --from=build /app/target/*.jar app.jar
|
||||
|
||||
RUN chown appuser:appgroup app.jar
|
||||
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
ENV SERVER_PORT=8080
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@@ -1,133 +0,0 @@
|
||||
# Focus Agenda - Agenda Digital para Estudantes
|
||||
|
||||
CENTRO ESTADUAL DE EDUCACAO TECNOLOGICA "PAULA SOUZA"
|
||||
ETEC PEDRO D'ARCADIA NETO
|
||||
Tecnico em Desenvolvimento de Sistemas
|
||||
|
||||
## Autores
|
||||
|
||||
- BORGES, Gabriel H. M.
|
||||
- CRUZ, Fernando M. B. da
|
||||
- ARAUJO, Gustavo Ferreira
|
||||
- OLIVEIRA, Henry E. de
|
||||
- HABU, Nadia Sakae
|
||||
|
||||
##Descricao
|
||||
|
||||
Plataforma digital para organizacao de estudos destinada a alunos do ensino medio e tecnico. A ferramenta auxilia na gestao de rotinas academicas, enviando notificacoes sobre atividades diarias, horarios de estudo, datas de provas e outros compromissos academicos.
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
- Cadastro e autenticacao de usuarios
|
||||
- Calendario mensal, semanal e diario
|
||||
- Criacao e gerenciamentos de eventos
|
||||
- Criacao e gerenciamento de tarefas com prioridades
|
||||
- Gerenciamento de disciplinas
|
||||
- Sistema de notificacoes
|
||||
- Tema claro e escuro
|
||||
- Painel informativo com feriados nacionais
|
||||
|
||||
## Tecnologias
|
||||
|
||||
### Frontend
|
||||
- HTML5
|
||||
- CSS3
|
||||
- JavaScript
|
||||
|
||||
### Backend
|
||||
- Java 17
|
||||
- Spring Boot 3.2.0
|
||||
- Spring Security
|
||||
- JWT (JSON Web Token)
|
||||
- MongoDB
|
||||
|
||||
## Requisitos
|
||||
|
||||
- Java 17 ou superior
|
||||
- Maven 3.8+
|
||||
- MongoDB
|
||||
|
||||
## Execucao
|
||||
|
||||
### Build do projeto
|
||||
|
||||
```bash
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
### Execucao com Maven
|
||||
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### Execucao com JAR
|
||||
|
||||
```bash
|
||||
java -jar target/agenda-digital-estudantes-1.0.0.jar
|
||||
```
|
||||
|
||||
### Variaveis de ambiente
|
||||
|
||||
| Variavel | Descricao | Valor padrao |
|
||||
|---|---|---|
|
||||
| APP_NAME | Nome da aplicacao | Focus Agenda |
|
||||
| SERVER_PORT | Porta do servidor | 8080 |
|
||||
| SPRING_PROFILES_ACTIVE | Perfil ativo | dev |
|
||||
| MONGO_URI | URI de conexao com MongoDB | mongodb://localhost:27017/agenda_estudantil |
|
||||
| CORS_ORIGINS | Origens permitidas para CORS | http://localhost:8080,http://localhost:3000 |
|
||||
| JWT_SECRET | Chave secreta para JWT | (chave padrao) |
|
||||
| JWT_EXPIRATION | Expiracao do token em milissegundos | 86400000 |
|
||||
|
||||
## Docker
|
||||
|
||||
### Build da imagem
|
||||
|
||||
```bash
|
||||
docker build -t focus-agenda .
|
||||
```
|
||||
|
||||
### Execucao do container
|
||||
|
||||
```bash
|
||||
docker run -d -p 8080:8080 --name focus-agenda focus-agenda
|
||||
```
|
||||
|
||||
### Execucao com MongoDB
|
||||
|
||||
```bash
|
||||
docker run -d -p 8080:8080 -e MONGO_URI=mongodb://host.docker.internal:27017/agenda_estudantil --name focus-agenda focus-agenda
|
||||
```
|
||||
|
||||
## Acesso
|
||||
|
||||
Apos iniciar a aplicacao, acesse:
|
||||
|
||||
- Aplicacao: http://localhost:8080
|
||||
- Swagger UI: http://localhost:8080/swagger-ui.html
|
||||
- API Docs: http://localhost:8080/v3/api-docs
|
||||
|
||||
## Estrutura do Projeto
|
||||
|
||||
```
|
||||
src/
|
||||
├── main/
|
||||
│ ├── java/com/agendaestudantil/
|
||||
│ │ ├── configuracao/ # Configuracoes de seguranca e MongoDB
|
||||
│ │ ├── controlador/ # Controladores REST
|
||||
│ │ ├── dto/ # Objetos de transferencia de dados
|
||||
│ │ ├── entidade/ # Entidades do dominio
|
||||
│ │ ├── excecao/ # Excecoes personalizadas e manipulador global
|
||||
│ │ ├── filtro/ # Filtro JWT
|
||||
│ │ ├── repositorio/ # Interfaces de repositorio
|
||||
│ │ ├── seguranca/ # Autenticacao e detalhes do usuario
|
||||
│ │ ├── servico/ # Regras de negocio
|
||||
│ │ └── utilitario/ # Utilitarios (JWT)
|
||||
│ └── resources/
|
||||
│ ├── static/ # Frontend (HTML, CSS, JS)
|
||||
│ └── application*.properties
|
||||
```
|
||||
|
||||
## Licenca
|
||||
|
||||
Projeto academico desenvolvido para o Curso Tecnico em Desenvolvimento de Sistemas da ETEC Pedro D'Arcadia Neto.
|
||||
@@ -1,103 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#textotop {
|
||||
padding-left: 20px;
|
||||
font-size: clamp(22px, 5vw, 38px);
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#log {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mens {
|
||||
text-align: center;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.campo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
#emailid,
|
||||
#senhaid,
|
||||
#nomeid,
|
||||
#csenhaid {
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #c7c7c7;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
#logbtn {
|
||||
align-self: center;
|
||||
width: 50%;
|
||||
padding: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background-color: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#logbtn:hover {
|
||||
background-color: #a03224;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #111;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -1,62 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cadastro</title>
|
||||
<link rel="stylesheet" href="cadastro.css">
|
||||
<title>Tcc parte2</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" href="imagens/icone.png">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Barrinha que fica no topo -->
|
||||
|
||||
|
||||
<!--Barrinha que fica no topo-->
|
||||
<div id="topo">
|
||||
<h1 id="textotop">Focus Agenda</h1>
|
||||
</div>
|
||||
</div>
|
||||
<!--cabo bar-->
|
||||
|
||||
<!-- Cadastro -->
|
||||
<!--Login e outras coisas-->
|
||||
<div class="card">
|
||||
<div id="log">
|
||||
<h1 class="mens">Crie Sua Conta</h1>
|
||||
|
||||
<form id="cadastroForm" method="GET" action="login.html">
|
||||
<form method="POST" action="cadastro.html">
|
||||
<div class="campo">
|
||||
<label for="emailid">Email</label>
|
||||
<input type="email" placeholder="Digite seu email" name="email" id="emailid" autocomplete="email" required>
|
||||
<input type="email" placeholder="Digite seu email" name="email" id="emailid" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="nomeid">Nome</label>
|
||||
<input type="text" placeholder="Nome de usuario" name="user" id="nomeid" autocomplete="name" required>
|
||||
<input type="name" placeholder="Nome de usuario" name="user" id="nomeid" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="senhaid">Senha</label>
|
||||
<input type="password" placeholder="Digite sua senha" name="senha" id="senhaid" autocomplete="new-password" required>
|
||||
<input type="password" placeholder="Digite sua senha" name="senha" id="senhaid" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="csenhaid">Confirmar Senha</label>
|
||||
<input type="password" placeholder="Confirme sua senha" name="csenha" id="csenhaid" autocomplete="new-password" required>
|
||||
<input type="password" placeholder="Confirme sua senha" name="csenha" id="csenhaid" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="logbtn">Cadastrar</button>
|
||||
</form>
|
||||
|
||||
<p class="mens"><a href="login.html" id="linklog">Ja tem uma conta?</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<!--cabo log-->
|
||||
|
||||
<script>
|
||||
const cadastroForm = document.getElementById("cadastroForm");
|
||||
const senhaInput = document.getElementById("senhaid");
|
||||
const confirmarSenhaInput = document.getElementById("csenhaid");
|
||||
|
||||
cadastroForm.addEventListener("submit", (event) => {
|
||||
if (senhaInput.value !== confirmarSenhaInput.value) {
|
||||
event.preventDefault();
|
||||
alert("As senhas nao conferem.");
|
||||
confirmarSenhaInput.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,587 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
#header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #c0392b, #114455);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#title {
|
||||
color: #fff;
|
||||
padding-left: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
/* BARRA ESQUERDA */
|
||||
#barraesquerda {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
width: 320px;
|
||||
height: calc(100vh - 50px);
|
||||
background: #c0392b;
|
||||
padding: 15px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: 'Inter', sans-serif;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#calendario {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.calendariotop {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#mes {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#calendarseta {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
#calendarseta button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
#calendarseta button:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.calendariodia {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.calendariodia th {
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.calendariodia td {
|
||||
text-align: center;
|
||||
padding: 6px;
|
||||
font-size: 12px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.calendariodia td::after {
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.calendariodia td.today {
|
||||
background: #114455;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.outromes {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
/* AGENDA */
|
||||
.agenda-header {
|
||||
margin-top: 15px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.agenda-empty {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.evento {
|
||||
margin-top: 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 3px solid #fff;
|
||||
}
|
||||
|
||||
.evento.azul,
|
||||
.calendar-event.azul {
|
||||
border-color: #00c2ff;
|
||||
}
|
||||
|
||||
.evento.verde,
|
||||
.calendar-event.verde {
|
||||
border-color: #2ecc71;
|
||||
}
|
||||
|
||||
.evento.vermelho,
|
||||
.calendar-event.vermelho {
|
||||
border-color: #e74c3c;
|
||||
}
|
||||
|
||||
.evento.roxo,
|
||||
.calendar-event.roxo {
|
||||
border-color: #9b59b6;
|
||||
}
|
||||
|
||||
.evento.laranja,
|
||||
.calendar-event.laranja {
|
||||
border-color: #e67e22;
|
||||
}
|
||||
|
||||
.evento.amarelo,
|
||||
.calendar-event.amarelo {
|
||||
border-color: #f1c40f;
|
||||
}
|
||||
|
||||
.evento.rosa,
|
||||
.calendar-event.rosa {
|
||||
border-color: #ff6b81;
|
||||
}
|
||||
|
||||
.evento.ciano,
|
||||
.calendar-event.ciano {
|
||||
border-color: #1abc9c;
|
||||
}
|
||||
|
||||
.evento.marinho,
|
||||
.calendar-event.marinho {
|
||||
border-color: #2c3e50;
|
||||
}
|
||||
|
||||
.evento.lilas,
|
||||
.calendar-event.lilas {
|
||||
border-color: #a29bfe;
|
||||
}
|
||||
|
||||
.evento.salmao,
|
||||
.calendar-event.salmao {
|
||||
border-color: #fa8072;
|
||||
}
|
||||
|
||||
.evento.menta,
|
||||
.calendar-event.menta {
|
||||
border-color: #55efc4;
|
||||
}
|
||||
|
||||
.hora {
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.titulo {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.link {
|
||||
font-size: 11px;
|
||||
color: #ffdede;
|
||||
}
|
||||
|
||||
/* FERIADOS */
|
||||
.feriados-header {
|
||||
margin-top: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feriado {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #d4ff6a;
|
||||
border-radius: 50%;
|
||||
margin-top: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* MAIN */
|
||||
.main {
|
||||
margin-left: 320px;
|
||||
margin-top: 50px;
|
||||
min-height: calc(100vh - 50px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* TOPBAR */
|
||||
.topbar {
|
||||
min-height: 70px;
|
||||
background: #eaeaea;
|
||||
border-bottom: 3px solid #c0392b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 25px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.topbar h1 {
|
||||
font-size: 24px;
|
||||
color: #1f3b57;
|
||||
}
|
||||
|
||||
.user-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.perfil {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: url('https://i.pravatar.cc/100') center/cover;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nome {
|
||||
font-weight: 600;
|
||||
color: #1f3b57;
|
||||
}
|
||||
|
||||
.cargo {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.icone-img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
object-fit: contain;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.icone-img:hover {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
/* HEADER CALENDARIO */
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 20px;
|
||||
background: #eee;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.mes-nav {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: min(360px, 100%);
|
||||
padding: 0 42px;
|
||||
}
|
||||
|
||||
.titulo-mes {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f3b57;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.seta {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
.antGrande {
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.proxGrande {
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.view-switch {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.view-switch button {
|
||||
border: none;
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: #1f3b57;
|
||||
}
|
||||
|
||||
.view-switch .active {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* CALENDARIO GRANDE */
|
||||
.calendar-area {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.calendar-area.month-view {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: 40px repeat(6, 120px);
|
||||
}
|
||||
|
||||
.dia-semana {
|
||||
background: #f0f0f0;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
padding: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.dia-box {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.dia-box:hover {
|
||||
background: #eaeaff;
|
||||
}
|
||||
|
||||
.dia-box.today {
|
||||
background: #fff5f2;
|
||||
border-color: #c0392b;
|
||||
font-weight: 700;
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
.outro-mes {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.calendar-area.week-view {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(150px, 1fr));
|
||||
gap: 0;
|
||||
overflow-x: auto;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.week-col {
|
||||
min-height: 420px;
|
||||
min-width: 150px;
|
||||
border-right: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.week-col.today {
|
||||
background: #fffaf8;
|
||||
}
|
||||
|
||||
.week-col-head {
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #1f3b57;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.week-col-date {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.week-events {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.week-empty,
|
||||
.day-empty {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.calendar-area.day-view {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.day-panel {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.day-panel-header {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1f3b57;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.day-events {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.calendar-event {
|
||||
border-left: 3px solid #c0392b;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.calendar-event-hora {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.calendar-event-titulo {
|
||||
font-size: 14px;
|
||||
color: #1f2937;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* RESPONSIVO */
|
||||
@media (max-width: 1024px) {
|
||||
body {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
#barraesquerda {
|
||||
position: static;
|
||||
top: auto;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.calendar-area.month-view {
|
||||
grid-template-rows: 40px repeat(6, 90px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
#title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
padding: 12px 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.topbar h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.mes-nav {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.view-switch {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.calendar-area.month-view {
|
||||
grid-template-rows: 36px repeat(6, 72px);
|
||||
}
|
||||
|
||||
.dia-box {
|
||||
padding: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.week-col {
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.calendar-area.day-view {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
<!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">
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER -->
|
||||
<div id="header">
|
||||
<h1 id="title">Focus Agenda</h1>
|
||||
</div>
|
||||
|
||||
<!-- BARRA ESQUERDA -->
|
||||
<div id="barraesquerda">
|
||||
<!-- MINI CALENDARIO -->
|
||||
<div id="calendario">
|
||||
<div class="calendariotop">
|
||||
<div id="mes"></div>
|
||||
<div id="calendarseta">
|
||||
<button class="ant" aria-label="Mes anterior">‹</button>
|
||||
<button class="prox" aria-label="Proximo mes">›</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>
|
||||
|
||||
<!-- AGENDA -->
|
||||
<div id="agenda"></div>
|
||||
|
||||
<!-- FERIADOS -->
|
||||
<div id="feriados"></div>
|
||||
</div>
|
||||
|
||||
<!-- CONTEUDO PRINCIPAL -->
|
||||
<div class="main">
|
||||
<div class="topbar">
|
||||
<h1>Calendario</h1>
|
||||
|
||||
<div class="user-area">
|
||||
<img src="imagens/sino.png" class="icone-img" alt="Notificacoes">
|
||||
|
||||
<div class="perfil">
|
||||
<div class="avatar"></div>
|
||||
<div class="info">
|
||||
<span class="nome">Usuario</span>
|
||||
<span class="cargo">Admin</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img src="imagens/engrenagem.png" class="icone-img" alt="Configuracoes">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calendar-header">
|
||||
<div class="mes-nav">
|
||||
<button class="seta antGrande" aria-label="Anterior">‹</button>
|
||||
<span class="titulo-mes">Janeiro, 2025</span>
|
||||
<button class="seta proxGrande" aria-label="Proximo">›</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">Mes</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CALENDARIO GRANDE -->
|
||||
<div class="calendar-area month-view" id="calendarArea"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let dataMini = new Date();
|
||||
let dataGrande = new Date();
|
||||
let modoAtual = "mes";
|
||||
|
||||
const meses = [
|
||||
"Janeiro", "Fevereiro", "Marco", "Abril",
|
||||
"Maio", "Junho", "Julho", "Agosto",
|
||||
"Setembro", "Outubro", "Novembro", "Dezembro"
|
||||
];
|
||||
|
||||
const nomesDiaLongo = [
|
||||
"Domingo", "Segunda", "Terca", "Quarta", "Quinta", "Sexta", "Sabado"
|
||||
];
|
||||
|
||||
const nomesDiaCurto = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"];
|
||||
|
||||
function normalizarData(data) {
|
||||
return new Date(data.getFullYear(), data.getMonth(), data.getDate());
|
||||
}
|
||||
|
||||
function adicionarDias(data, quantidade) {
|
||||
const novaData = new Date(data);
|
||||
novaData.setDate(novaData.getDate() + quantidade);
|
||||
return novaData;
|
||||
}
|
||||
|
||||
function formatarISO(data) {
|
||||
const ano = data.getFullYear();
|
||||
const mes = String(data.getMonth() + 1).padStart(2, "0");
|
||||
const dia = String(data.getDate()).padStart(2, "0");
|
||||
return `${ano}-${mes}-${dia}`;
|
||||
}
|
||||
|
||||
function formatarDataCurta(data) {
|
||||
const dia = String(data.getDate()).padStart(2, "0");
|
||||
const mes = String(data.getMonth() + 1).padStart(2, "0");
|
||||
return `${dia}/${mes}`;
|
||||
}
|
||||
|
||||
function obterInicioSemana(data) {
|
||||
const inicio = normalizarData(data);
|
||||
inicio.setDate(inicio.getDate() - inicio.getDay());
|
||||
return inicio;
|
||||
}
|
||||
|
||||
function extrairMinutos(horaIntervalo) {
|
||||
const inicio = horaIntervalo.split("-")[0].trim();
|
||||
const [hora, minuto] = inicio.split(":").map(Number);
|
||||
return hora * 60 + minuto;
|
||||
}
|
||||
|
||||
const hojeBase = normalizarData(new Date());
|
||||
|
||||
const agendaData = [
|
||||
{ data: formatarISO(hojeBase), hora: "08:15 - 09:15", titulo: "Planejamento do dia", cor: "vermelho" },
|
||||
{ data: formatarISO(hojeBase), hora: "10:00 - 11:00", titulo: "Reuniao rapida", cor: "rosa" },
|
||||
{ data: formatarISO(adicionarDias(hojeBase, 1)), hora: "14:00 - 15:00", titulo: "Revisar tarefas", cor: "azul" },
|
||||
{ data: formatarISO(adicionarDias(hojeBase, 3)), hora: "16:00 - 17:00", titulo: "Entrega parcial", cor: "verde" }
|
||||
];
|
||||
|
||||
const feriadosData = [
|
||||
{ texto: "01 - Ano Novo" }
|
||||
];
|
||||
|
||||
function obterEventosDaData(data) {
|
||||
const chave = formatarISO(normalizarData(data));
|
||||
return agendaData
|
||||
.filter((evento) => evento.data === chave)
|
||||
.sort((a, b) => extrairMinutos(a.hora) - extrairMinutos(b.hora));
|
||||
}
|
||||
|
||||
function atualizarTituloGrande() {
|
||||
const titulo = document.querySelector(".titulo-mes");
|
||||
|
||||
if (modoAtual === "mes") {
|
||||
titulo.textContent = `${meses[dataGrande.getMonth()]}, ${dataGrande.getFullYear()}`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (modoAtual === "semana") {
|
||||
const inicio = obterInicioSemana(dataGrande);
|
||||
const fim = adicionarDias(inicio, 6);
|
||||
titulo.textContent = `Semana ${formatarDataCurta(inicio)} - ${formatarDataCurta(fim)}/${fim.getFullYear()}`;
|
||||
return;
|
||||
}
|
||||
|
||||
titulo.textContent = `${nomesDiaLongo[dataGrande.getDay()]}, ${formatarDataCurta(dataGrande)}/${dataGrande.getFullYear()}`;
|
||||
}
|
||||
|
||||
function configurarArea(classeModo) {
|
||||
const area = document.getElementById("calendarArea");
|
||||
area.className = `calendar-area ${classeModo}`;
|
||||
area.replaceChildren();
|
||||
return area;
|
||||
}
|
||||
|
||||
function criarCardEvento(evento) {
|
||||
const card = document.createElement("div");
|
||||
card.className = `calendar-event ${evento.cor}`;
|
||||
|
||||
const hora = document.createElement("div");
|
||||
hora.className = "calendar-event-hora";
|
||||
hora.textContent = evento.hora;
|
||||
|
||||
const titulo = document.createElement("div");
|
||||
titulo.className = "calendar-event-titulo";
|
||||
titulo.textContent = evento.titulo;
|
||||
|
||||
card.appendChild(hora);
|
||||
card.appendChild(titulo);
|
||||
return card;
|
||||
}
|
||||
|
||||
/* MINI CALENDARIO */
|
||||
function renderCalendarioMini() {
|
||||
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 ultimoDiaMesAnterior = new Date(ano, mes, 0).getDate();
|
||||
|
||||
let diaAtualNum = 1;
|
||||
let diaSeguinte = 1;
|
||||
|
||||
const totalCelulas = primeiroDia + ultimoDia;
|
||||
let totalLinhas = Math.ceil(totalCelulas / 7);
|
||||
if (totalLinhas < 4) totalLinhas = 4;
|
||||
|
||||
for (let semana = 0; semana < totalLinhas; semana++) {
|
||||
const linha = document.createElement("tr");
|
||||
|
||||
for (let diaSemana = 0; diaSemana < 7; diaSemana++) {
|
||||
const pos = semana * 7 + diaSemana;
|
||||
const celula = document.createElement("td");
|
||||
|
||||
if (pos < primeiroDia) {
|
||||
const dia = ultimoDiaMesAnterior - (primeiroDia - pos - 1);
|
||||
celula.className = "outromes";
|
||||
celula.textContent = String(dia);
|
||||
} else if (diaAtualNum <= ultimoDia) {
|
||||
const hoje = new Date();
|
||||
const isHoje =
|
||||
diaAtualNum === hoje.getDate() &&
|
||||
mes === hoje.getMonth() &&
|
||||
ano === hoje.getFullYear();
|
||||
|
||||
if (isHoje) {
|
||||
celula.className = "today";
|
||||
}
|
||||
|
||||
celula.textContent = String(diaAtualNum);
|
||||
diaAtualNum++;
|
||||
} else {
|
||||
celula.className = "outromes";
|
||||
celula.textContent = String(diaSeguinte);
|
||||
diaSeguinte++;
|
||||
}
|
||||
|
||||
linha.appendChild(celula);
|
||||
}
|
||||
|
||||
diasEl.appendChild(linha);
|
||||
}
|
||||
}
|
||||
|
||||
/* CALENDARIO GRANDE - MES */
|
||||
function renderCalendarioMes() {
|
||||
const area = configurarArea("month-view");
|
||||
atualizarTituloGrande();
|
||||
|
||||
nomesDiaCurto.forEach((dia) => {
|
||||
const el = document.createElement("div");
|
||||
el.className = "dia-semana";
|
||||
el.textContent = dia;
|
||||
area.appendChild(el);
|
||||
});
|
||||
|
||||
const ano = dataGrande.getFullYear();
|
||||
const mes = dataGrande.getMonth();
|
||||
const primeiroDia = new Date(ano, mes, 1).getDay();
|
||||
const ultimoDia = new Date(ano, mes + 1, 0).getDate();
|
||||
const ultimoMesAnterior = new Date(ano, mes, 0).getDate();
|
||||
|
||||
const totalCelulasMes = 42;
|
||||
let proximoDia = 1;
|
||||
|
||||
for (let i = 0; i < totalCelulasMes; i++) {
|
||||
const box = document.createElement("div");
|
||||
box.className = "dia-box";
|
||||
|
||||
if (i < primeiroDia) {
|
||||
box.classList.add("outro-mes");
|
||||
box.textContent = String(ultimoMesAnterior - (primeiroDia - i - 1));
|
||||
} else if (i < primeiroDia + ultimoDia) {
|
||||
const diaAtual = i - primeiroDia + 1;
|
||||
box.textContent = String(diaAtual);
|
||||
|
||||
const hoje = new Date();
|
||||
const isHoje =
|
||||
diaAtual === hoje.getDate() &&
|
||||
mes === hoje.getMonth() &&
|
||||
ano === hoje.getFullYear();
|
||||
|
||||
if (isHoje) {
|
||||
box.classList.add("today");
|
||||
}
|
||||
} else {
|
||||
box.classList.add("outro-mes");
|
||||
box.textContent = String(proximoDia++);
|
||||
}
|
||||
|
||||
area.appendChild(box);
|
||||
}
|
||||
}
|
||||
|
||||
/* CALENDARIO GRANDE - SEMANA */
|
||||
function renderCalendarioSemana() {
|
||||
const area = configurarArea("week-view");
|
||||
atualizarTituloGrande();
|
||||
|
||||
const inicioSemana = obterInicioSemana(dataGrande);
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const dataColuna = adicionarDias(inicioSemana, i);
|
||||
const eventos = obterEventosDaData(dataColuna);
|
||||
|
||||
const coluna = document.createElement("div");
|
||||
coluna.className = "week-col";
|
||||
|
||||
const cabecalho = document.createElement("div");
|
||||
cabecalho.className = "week-col-head";
|
||||
cabecalho.textContent = nomesDiaCurto[dataColuna.getDay()];
|
||||
|
||||
const numero = document.createElement("span");
|
||||
numero.className = "week-col-date";
|
||||
numero.textContent = `${formatarDataCurta(dataColuna)}/${dataColuna.getFullYear()}`;
|
||||
cabecalho.appendChild(numero);
|
||||
|
||||
if (
|
||||
dataColuna.getDate() === new Date().getDate() &&
|
||||
dataColuna.getMonth() === new Date().getMonth() &&
|
||||
dataColuna.getFullYear() === new Date().getFullYear()
|
||||
) {
|
||||
coluna.classList.add("today");
|
||||
}
|
||||
|
||||
const lista = document.createElement("div");
|
||||
lista.className = "week-events";
|
||||
|
||||
if (eventos.length === 0) {
|
||||
const vazio = document.createElement("div");
|
||||
vazio.className = "week-empty";
|
||||
vazio.textContent = "Sem eventos";
|
||||
lista.appendChild(vazio);
|
||||
} else {
|
||||
eventos.forEach((evento) => {
|
||||
lista.appendChild(criarCardEvento(evento));
|
||||
});
|
||||
}
|
||||
|
||||
coluna.appendChild(cabecalho);
|
||||
coluna.appendChild(lista);
|
||||
area.appendChild(coluna);
|
||||
}
|
||||
}
|
||||
|
||||
/* CALENDARIO GRANDE - DIA */
|
||||
function renderCalendarioDia() {
|
||||
const area = configurarArea("day-view");
|
||||
atualizarTituloGrande();
|
||||
|
||||
const eventos = obterEventosDaData(dataGrande);
|
||||
|
||||
const painel = document.createElement("div");
|
||||
painel.className = "day-panel";
|
||||
|
||||
const cabecalho = document.createElement("div");
|
||||
cabecalho.className = "day-panel-header";
|
||||
cabecalho.textContent = `Agenda de ${formatarDataCurta(dataGrande)}/${dataGrande.getFullYear()}`;
|
||||
|
||||
const lista = document.createElement("div");
|
||||
lista.className = "day-events";
|
||||
|
||||
if (eventos.length === 0) {
|
||||
const vazio = document.createElement("div");
|
||||
vazio.className = "day-empty";
|
||||
vazio.textContent = "Nenhum evento para este dia.";
|
||||
lista.appendChild(vazio);
|
||||
} else {
|
||||
eventos.forEach((evento) => {
|
||||
lista.appendChild(criarCardEvento(evento));
|
||||
});
|
||||
}
|
||||
|
||||
painel.appendChild(cabecalho);
|
||||
painel.appendChild(lista);
|
||||
area.appendChild(painel);
|
||||
}
|
||||
|
||||
function renderCalendarioGrande() {
|
||||
if (modoAtual === "dia") {
|
||||
renderCalendarioDia();
|
||||
return;
|
||||
}
|
||||
|
||||
if (modoAtual === "semana") {
|
||||
renderCalendarioSemana();
|
||||
return;
|
||||
}
|
||||
|
||||
renderCalendarioMes();
|
||||
}
|
||||
|
||||
function renderAgenda() {
|
||||
const container = document.getElementById("agenda");
|
||||
container.replaceChildren();
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "agenda-header";
|
||||
|
||||
const strong = document.createElement("strong");
|
||||
strong.textContent = "HOJE ";
|
||||
header.appendChild(strong);
|
||||
|
||||
const hoje = new Date();
|
||||
header.appendChild(document.createTextNode(new Intl.DateTimeFormat("pt-BR").format(hoje)));
|
||||
container.appendChild(header);
|
||||
|
||||
const eventosHoje = obterEventosDaData(hoje);
|
||||
|
||||
if (eventosHoje.length === 0) {
|
||||
const vazio = document.createElement("div");
|
||||
vazio.className = "agenda-empty";
|
||||
vazio.textContent = "Sem eventos para hoje.";
|
||||
container.appendChild(vazio);
|
||||
return;
|
||||
}
|
||||
|
||||
eventosHoje.forEach((ev) => {
|
||||
const evento = document.createElement("div");
|
||||
evento.className = `evento ${ev.cor}`;
|
||||
|
||||
const hora = document.createElement("div");
|
||||
hora.className = "hora";
|
||||
hora.textContent = ev.hora;
|
||||
|
||||
const titulo = document.createElement("div");
|
||||
titulo.className = "titulo";
|
||||
titulo.textContent = ev.titulo;
|
||||
|
||||
evento.appendChild(hora);
|
||||
evento.appendChild(titulo);
|
||||
container.appendChild(evento);
|
||||
});
|
||||
}
|
||||
|
||||
function renderFeriados() {
|
||||
const container = document.getElementById("feriados");
|
||||
container.replaceChildren();
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "feriados-header";
|
||||
header.textContent = "FERIADOS";
|
||||
container.appendChild(header);
|
||||
|
||||
feriadosData.forEach((f) => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "feriado";
|
||||
|
||||
const dot = document.createElement("span");
|
||||
dot.className = "dot";
|
||||
|
||||
const texto = document.createElement("span");
|
||||
texto.textContent = f.texto;
|
||||
|
||||
item.appendChild(dot);
|
||||
item.appendChild(texto);
|
||||
container.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function moverPeriodo(direcao) {
|
||||
if (modoAtual === "dia") {
|
||||
dataGrande.setDate(dataGrande.getDate() + direcao);
|
||||
} else if (modoAtual === "semana") {
|
||||
dataGrande.setDate(dataGrande.getDate() + (direcao * 7));
|
||||
} else {
|
||||
dataGrande.setMonth(dataGrande.getMonth() + direcao);
|
||||
}
|
||||
|
||||
renderCalendarioGrande();
|
||||
}
|
||||
|
||||
function ativarBotaoModo(modo) {
|
||||
const botoes = document.querySelectorAll(".view-switch button");
|
||||
botoes.forEach((botao) => {
|
||||
botao.classList.toggle("active", botao.dataset.view === modo);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelector(".prox").onclick = () => {
|
||||
dataMini.setMonth(dataMini.getMonth() + 1);
|
||||
renderCalendarioMini();
|
||||
};
|
||||
|
||||
document.querySelector(".ant").onclick = () => {
|
||||
dataMini.setMonth(dataMini.getMonth() - 1);
|
||||
renderCalendarioMini();
|
||||
};
|
||||
|
||||
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;
|
||||
ativarBotaoModo(modoAtual);
|
||||
renderCalendarioGrande();
|
||||
});
|
||||
});
|
||||
|
||||
renderCalendarioMini();
|
||||
renderCalendarioGrande();
|
||||
renderAgenda();
|
||||
renderFeriados();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,22 +0,0 @@
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:7
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- mongo_data:/data/db
|
||||
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- mongodb
|
||||
environment:
|
||||
MONGO_URI: mongodb://mongodb:27017/agenda_estudantil
|
||||
JWT_SECRET: 4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b
|
||||
CORS_ORIGINS: http://localhost:8080
|
||||
SPRING_PROFILES_ACTIVE: prod
|
||||
|
||||
volumes:
|
||||
mongo_data:
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
@@ -1,100 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#textotop {
|
||||
padding-left: 20px;
|
||||
font-size: clamp(22px, 5vw, 38px);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#log {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mens {
|
||||
text-align: center;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.campo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
#emailid,
|
||||
#senhaid {
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #c7c7c7;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
#logbtn {
|
||||
align-self: center;
|
||||
width: 50%;
|
||||
padding: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background-color: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#logbtn:hover {
|
||||
background-color: #a03224;
|
||||
}
|
||||
|
||||
#linkcada {
|
||||
color: #111;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#linkcada:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="imagens/icone.png">
|
||||
<link rel="stylesheet" href="login.css">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Barrinha que fica no topo -->
|
||||
<div id="topo">
|
||||
<h1 id="textotop">Focus Agenda</h1>
|
||||
</div>
|
||||
|
||||
<!-- Login -->
|
||||
<div id="log">
|
||||
<h1 class="mens">Bem-vindo!</h1>
|
||||
<h3 class="mens">Faça seu login</h3>
|
||||
|
||||
<form method="GET" action="calendario.html">
|
||||
<div class="campo">
|
||||
<label for="emailid">Email</label>
|
||||
<input type="email" placeholder="Digite seu email" name="email" id="emailid" autocomplete="email" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="senhaid">Senha</label>
|
||||
<input type="password" placeholder="Digite sua senha" name="senha" id="senhaid" autocomplete="current-password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="logbtn">Logar</button>
|
||||
</form>
|
||||
|
||||
<p class="mens"><a href="cadastro.html" id="linkcada">Cadastrar-se</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,141 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.agendaestudantil</groupId>
|
||||
<artifactId>agenda-digital-estudantes</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>Agenda Digital para Estudantes</name>
|
||||
<description>Backend para agenda digital destinado a estudantes com dificuldade de organização</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.12.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.12.6</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.12.6</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.cdimascio</groupId>
|
||||
<artifactId>dotenv-java</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.agendaestudantil;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
public class AgendaDigitalEstudantesApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AgendaDigitalEstudantesApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.agendaestudantil.configuracao;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.config.EnableMongoAuditing;
|
||||
|
||||
@Configuration
|
||||
@EnableMongoAuditing
|
||||
public class ConfiguracaoMongo {
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package com.agendaestudantil.configuracao;
|
||||
|
||||
import com.agendaestudantil.dto.RespostaApi;
|
||||
import com.agendaestudantil.filtro.FiltroJwt;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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.http.MediaType;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
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.configuration.EnableWebSecurity;
|
||||
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.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class ConfiguracaoSeguranca {
|
||||
|
||||
private final FiltroJwt filtroJwt;
|
||||
private final String corsAllowedOrigins;
|
||||
private final String perfilAtivo;
|
||||
|
||||
public ConfiguracaoSeguranca(FiltroJwt filtroJwt,
|
||||
@Value("${cors.allowed.origins}") String corsAllowedOrigins,
|
||||
@Value("${spring.profiles.active:dev}") String perfilAtivo) {
|
||||
this.filtroJwt = filtroJwt;
|
||||
this.corsAllowedOrigins = corsAllowedOrigins;
|
||||
this.perfilAtivo = perfilAtivo;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
List<String> pathsPublicos = new ArrayList<>(List.of(
|
||||
"/", "/index.html", "/login.html", "/cadastro.html", "/calendario.html", "/configuracoes.html",
|
||||
"/politica-privacidade.html",
|
||||
"/favicon.ico", "/imagens/**",
|
||||
"/*.css", "/*.js", "/*.ico", "/*.png",
|
||||
"/api/estudantes/cadastro", "/api/estudantes/login"));
|
||||
|
||||
if ("dev".equals(perfilAtivo)) {
|
||||
pathsPublicos.add("/swagger-ui/**");
|
||||
pathsPublicos.add("/v3/api-docs/**");
|
||||
}
|
||||
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(pathsPublicos.toArray(new String[0]))
|
||||
.permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint((request, response, authException) -> {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
RespostaApi<Void> body = new RespostaApi<>(null, "Acesso nao autorizado", LocalDateTime.now());
|
||||
new ObjectMapper().writeValue(response.getOutputStream(), body);
|
||||
}))
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.addFilterBefore(filtroJwt, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(List.of(corsAllowedOrigins.split(",")));
|
||||
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setExposedHeaders(List.of("Authorization"));
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setMaxAge(3600L);
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.agendaestudantil.configuracao;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class ResourceController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
return "forward:/login.html";
|
||||
}
|
||||
|
||||
@GetMapping("/app")
|
||||
public String app() {
|
||||
return "forward:/calendario.html";
|
||||
}
|
||||
|
||||
@GetMapping("/config")
|
||||
public String config() {
|
||||
return "forward:/configuracoes.html";
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.agendaestudantil.controlador;
|
||||
|
||||
import com.agendaestudantil.dto.RequisicaoDisciplinaDTO;
|
||||
import com.agendaestudantil.dto.RespostaApi;
|
||||
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
|
||||
import com.agendaestudantil.entidade.Disciplina;
|
||||
import com.agendaestudantil.servico.DisciplinaServico;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/disciplinas")
|
||||
public class DisciplinaControlador {
|
||||
|
||||
private final DisciplinaServico disciplinaServico;
|
||||
|
||||
public DisciplinaControlador(DisciplinaServico disciplinaServico) {
|
||||
this.disciplinaServico = disciplinaServico;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> criarDisciplina(
|
||||
@Valid @RequestBody RequisicaoDisciplinaDTO dto,
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
Disciplina disciplina = new Disciplina();
|
||||
disciplina.setNome(dto.nome());
|
||||
disciplina.setProfessor(dto.professor());
|
||||
disciplina.setSala(dto.sala());
|
||||
disciplina.setCor(dto.cor());
|
||||
|
||||
RespostaDisciplinaDTO resposta = disciplinaServico.criarDisciplina(disciplina, estudanteId);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<RespostaApi<List<RespostaDisciplinaDTO>>> listarPorEstudante(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaDisciplinaDTO> disciplinas = disciplinaServico.listarPorEstudante(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(disciplinas));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> buscarPorId(
|
||||
@PathVariable String id,
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
RespostaDisciplinaDTO disciplina = disciplinaServico.buscarPorId(id, estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(disciplina));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<RespostaDisciplinaDTO>> atualizarDisciplina(
|
||||
@PathVariable String id,
|
||||
@Valid @RequestBody RequisicaoDisciplinaDTO dto,
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
Disciplina disciplina = new Disciplina();
|
||||
disciplina.setNome(dto.nome());
|
||||
disciplina.setProfessor(dto.professor());
|
||||
disciplina.setSala(dto.sala());
|
||||
disciplina.setCor(dto.cor());
|
||||
|
||||
RespostaDisciplinaDTO resposta = disciplinaServico.atualizarDisciplina(id, disciplina, estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<Void>> excluirDisciplina(
|
||||
@PathVariable String id,
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
disciplinaServico.excluirDisciplina(id, estudanteId);
|
||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package com.agendaestudantil.controlador;
|
||||
|
||||
import com.agendaestudantil.dto.RespostaApi;
|
||||
import com.agendaestudantil.dto.RequisicaoAtualizacaoEstudanteDTO;
|
||||
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||
import com.agendaestudantil.dto.RespostaDadosCompletoDTO;
|
||||
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||
import com.agendaestudantil.dto.RequisicaoTrocaSenhaDTO;
|
||||
import com.agendaestudantil.servico.EstudanteServico;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/estudantes")
|
||||
public class EstudanteControlador {
|
||||
|
||||
private final EstudanteServico estudanteServico;
|
||||
|
||||
public EstudanteControlador(EstudanteServico estudanteServico) {
|
||||
this.estudanteServico = estudanteServico;
|
||||
}
|
||||
|
||||
@PostMapping("/cadastro")
|
||||
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> cadastrar(@Valid @RequestBody RequisicaoCadastroDTO dto) {
|
||||
RespostaEstudanteDTO resposta = estudanteServico.cadastrar(dto);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<RespostaApi<RespostaLoginDTO>> login(@Valid @RequestBody RequisicaoLoginDTO dto) {
|
||||
RespostaLoginDTO resposta = estudanteServico.login(dto);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> me(@AuthenticationPrincipal UserDetails userDetails) {
|
||||
RespostaEstudanteDTO resposta = estudanteServico.buscarPorId(userDetails.getUsername());
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@GetMapping("/me/dados")
|
||||
public ResponseEntity<RespostaApi<RespostaDadosCompletoDTO>> exportarDados(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
RespostaDadosCompletoDTO dados = estudanteServico.exportarDados(userDetails.getUsername());
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(dados));
|
||||
}
|
||||
|
||||
@PutMapping("/me")
|
||||
public ResponseEntity<RespostaApi<RespostaEstudanteDTO>> atualizar(
|
||||
@AuthenticationPrincipal UserDetails userDetails,
|
||||
@Valid @RequestBody RequisicaoAtualizacaoEstudanteDTO dto) {
|
||||
RespostaEstudanteDTO resposta = estudanteServico.atualizar(userDetails.getUsername(), dto);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@PutMapping("/senha")
|
||||
public ResponseEntity<RespostaApi<Void>> trocarSenha(
|
||||
@AuthenticationPrincipal UserDetails userDetails,
|
||||
@Valid @RequestBody RequisicaoTrocaSenhaDTO dto) {
|
||||
estudanteServico.trocarSenha(userDetails.getUsername(), dto);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(null));
|
||||
}
|
||||
|
||||
@DeleteMapping("/me")
|
||||
public ResponseEntity<RespostaApi<Void>> excluirConta(@AuthenticationPrincipal UserDetails userDetails) {
|
||||
estudanteServico.excluirConta(userDetails.getUsername());
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(null));
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package com.agendaestudantil.controlador;
|
||||
|
||||
import com.agendaestudantil.dto.RequisicaoEventoDTO;
|
||||
import com.agendaestudantil.dto.RespostaApi;
|
||||
import com.agendaestudantil.dto.RespostaEventoDTO;
|
||||
import com.agendaestudantil.entidade.Evento;
|
||||
import com.agendaestudantil.servico.EventoServico;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/eventos")
|
||||
public class EventoControlador {
|
||||
|
||||
private final EventoServico eventoServico;
|
||||
|
||||
public EventoControlador(EventoServico eventoServico) {
|
||||
this.eventoServico = eventoServico;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<RespostaApi<RespostaEventoDTO>> criarEvento(
|
||||
@Valid @RequestBody RequisicaoEventoDTO dto,
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
Evento evento = new Evento();
|
||||
evento.setTitulo(dto.titulo());
|
||||
evento.setDescricao(dto.descricao());
|
||||
evento.setTipo(dto.tipo());
|
||||
evento.setLocal(dto.local());
|
||||
evento.setDataHora(dto.dataHora());
|
||||
evento.setDisciplinaId(dto.disciplinaId());
|
||||
|
||||
RespostaEventoDTO resposta = eventoServico.criarEvento(evento, estudanteId);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorEstudante(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaEventoDTO> eventos = eventoServico.listarPorEstudante(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||
}
|
||||
|
||||
@GetMapping("/me/periodo")
|
||||
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarPorPeriodo(
|
||||
@AuthenticationPrincipal UserDetails userDetails,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime inicio,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime fim) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaEventoDTO> eventos = eventoServico.listarPorPeriodo(estudanteId, inicio, fim);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||
}
|
||||
|
||||
@GetMapping("/me/proximos")
|
||||
public ResponseEntity<RespostaApi<List<RespostaEventoDTO>>> listarProximosEventos(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaEventoDTO> eventos = eventoServico.listarProximosEventos(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(eventos));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<RespostaEventoDTO>> buscarPorId(@PathVariable String id) {
|
||||
RespostaEventoDTO evento = eventoServico.buscarPorId(id);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(evento));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<RespostaEventoDTO>> atualizarEvento(
|
||||
@PathVariable String id,
|
||||
@Valid @RequestBody RequisicaoEventoDTO dto) {
|
||||
RespostaEventoDTO resposta = eventoServico.atualizarEvento(id, dto);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(resposta));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<Void>> excluirEvento(@PathVariable String id) {
|
||||
eventoServico.excluirEvento(id);
|
||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/cancelar")
|
||||
public ResponseEntity<RespostaApi<RespostaEventoDTO>> cancelarEvento(@PathVariable String id) {
|
||||
RespostaEventoDTO evento = eventoServico.cancelarEvento(id);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(evento));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.agendaestudantil.controlador;
|
||||
|
||||
import com.agendaestudantil.dto.RespostaApi;
|
||||
import com.agendaestudantil.dto.RespostaNotificacaoDTO;
|
||||
import com.agendaestudantil.servico.NotificacaoServico;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/notificacoes")
|
||||
public class NotificacaoControlador {
|
||||
|
||||
private final NotificacaoServico notificacaoServico;
|
||||
|
||||
public NotificacaoControlador(NotificacaoServico notificacaoServico) {
|
||||
this.notificacaoServico = notificacaoServico;
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<RespostaApi<List<RespostaNotificacaoDTO>>> listarTodas(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaNotificacaoDTO> notificacoes = notificacaoServico.listarTodas(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(notificacoes));
|
||||
}
|
||||
|
||||
@GetMapping("/me/nao-lidas")
|
||||
public ResponseEntity<RespostaApi<List<RespostaNotificacaoDTO>>> listarNaoLidas(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaNotificacaoDTO> notificacoes = notificacaoServico.listarNaoLidas(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(notificacoes));
|
||||
}
|
||||
|
||||
@GetMapping("/me/contagem")
|
||||
public ResponseEntity<RespostaApi<Map<String, Long>>> contarNaoLidas(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
long total = notificacaoServico.contarNaoLidas(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(Map.of("total", total)));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/ler")
|
||||
public ResponseEntity<RespostaApi<RespostaNotificacaoDTO>> marcarComoLida(@PathVariable String id) {
|
||||
RespostaNotificacaoDTO notificacao = notificacaoServico.marcarComoLida(id);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(notificacao));
|
||||
}
|
||||
|
||||
@PatchMapping("/me/ler-todas")
|
||||
public ResponseEntity<RespostaApi<Void>> marcarTodasComoLidas(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
notificacaoServico.marcarTodasComoLidas(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(null));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<Void>> excluirNotificacao(@PathVariable String id) {
|
||||
notificacaoServico.excluirNotificacao(id);
|
||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.agendaestudantil.controlador;
|
||||
|
||||
import com.agendaestudantil.dto.RespostaApi;
|
||||
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||
import com.agendaestudantil.servico.TarefaServico;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/tarefas")
|
||||
public class TarefaControlador {
|
||||
|
||||
private final TarefaServico tarefaServico;
|
||||
|
||||
public TarefaControlador(TarefaServico tarefaServico) {
|
||||
this.tarefaServico = tarefaServico;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> criarTarefa(@Valid @RequestBody RequisicaoTarefaDTO dto) {
|
||||
RespostaTarefaDTO tarefa = tarefaServico.criarTarefa(dto);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(RespostaApi.sucesso(tarefa));
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorEstudante(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||
}
|
||||
|
||||
@GetMapping("/me/pendentes")
|
||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPendentes(
|
||||
@AuthenticationPrincipal UserDetails userDetails) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPendentes(estudanteId);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||
}
|
||||
|
||||
@GetMapping("/me/data")
|
||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorData(
|
||||
@AuthenticationPrincipal UserDetails userDetails,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate data) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorData(estudanteId, data);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||
}
|
||||
|
||||
@GetMapping("/me/periodo")
|
||||
public ResponseEntity<RespostaApi<List<RespostaTarefaDTO>>> listarTarefasPorPeriodo(
|
||||
@AuthenticationPrincipal UserDetails userDetails,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate inicio,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate fim) {
|
||||
String estudanteId = userDetails.getUsername();
|
||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorPeriodo(estudanteId, inicio, fim);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefas));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> buscarTarefaPorId(@PathVariable String id) {
|
||||
RespostaTarefaDTO dto = tarefaServico.buscarTarefaPorId(id);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(dto));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> atualizarTarefa(@PathVariable String id,
|
||||
@Valid @RequestBody RequisicaoTarefaDTO dto) {
|
||||
RespostaTarefaDTO tarefa = tarefaServico.atualizarTarefa(id, dto);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefa));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<RespostaApi<Void>> excluirTarefa(@PathVariable String id) {
|
||||
tarefaServico.excluirTarefa(id);
|
||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(RespostaApi.sucesso(null));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/concluir")
|
||||
public ResponseEntity<RespostaApi<RespostaTarefaDTO>> marcarConcluida(@PathVariable String id) {
|
||||
RespostaTarefaDTO tarefa = tarefaServico.marcarConcluida(id);
|
||||
return ResponseEntity.ok(RespostaApi.sucesso(tarefa));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
public record RequisicaoAtualizacaoEstudanteDTO(
|
||||
@NotBlank String nome,
|
||||
@NotBlank String curso,
|
||||
@NotNull @Min(1) Integer periodo
|
||||
) {
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record RequisicaoCadastroDTO(
|
||||
@NotBlank String nome,
|
||||
@Email @NotBlank String email,
|
||||
@NotBlank @Size(min = 6) String senha,
|
||||
@NotBlank String curso,
|
||||
@NotNull @Min(1) Integer periodo,
|
||||
Boolean consentimentoLgpd
|
||||
) {
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record RequisicaoDisciplinaDTO(
|
||||
@NotBlank String nome,
|
||||
String professor,
|
||||
String sala,
|
||||
String cor
|
||||
) {}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import com.agendaestudantil.entidade.Evento;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record RequisicaoEventoDTO(
|
||||
@NotBlank String titulo,
|
||||
String descricao,
|
||||
@NotNull Evento.TipoEvento tipo,
|
||||
String local,
|
||||
String disciplinaId,
|
||||
@NotNull LocalDateTime dataHora
|
||||
) {}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record RequisicaoLoginDTO(
|
||||
@Email @NotBlank String email,
|
||||
@NotBlank String senha
|
||||
) {
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import com.agendaestudantil.entidade.Tarefa;
|
||||
import jakarta.validation.constraints.FutureOrPresent;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record RequisicaoTarefaDTO(
|
||||
@NotBlank String titulo,
|
||||
String descricao,
|
||||
@NotNull(message = "Prioridade é obrigatória") Tarefa.Prioridade prioridade,
|
||||
Tarefa.StatusTarefa status,
|
||||
@NotNull @FutureOrPresent LocalDate dataEntrega,
|
||||
String disciplinaId
|
||||
) {
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record RequisicaoTrocaSenhaDTO(
|
||||
@NotBlank String senhaAtual,
|
||||
@NotBlank @Size(min = 6) String novaSenha
|
||||
) {
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record RespostaApi<T>(T data, String message, LocalDateTime timestamp) {
|
||||
public static <T> RespostaApi<T> sucesso(T data) {
|
||||
return new RespostaApi<>(data, "Sucesso", LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public record RespostaDadosCompletoDTO(
|
||||
RespostaEstudanteDTO estudante,
|
||||
List<RespostaTarefaDTO> tarefas,
|
||||
List<RespostaEventoDTO> eventos,
|
||||
List<RespostaDisciplinaDTO> disciplinas,
|
||||
LocalDateTime geradoEm
|
||||
) {}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
public record RespostaDisciplinaDTO(
|
||||
String id,
|
||||
String estudanteId,
|
||||
String nome,
|
||||
String professor,
|
||||
String sala,
|
||||
String cor
|
||||
) {}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
public record RespostaEstudanteDTO(
|
||||
String id,
|
||||
String nome,
|
||||
String email,
|
||||
String curso,
|
||||
Integer periodo
|
||||
) {
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record RespostaEventoDTO(
|
||||
String id,
|
||||
String estudanteId,
|
||||
String titulo,
|
||||
String descricao,
|
||||
String tipo,
|
||||
String local,
|
||||
String disciplinaId,
|
||||
LocalDateTime dataHora,
|
||||
String status,
|
||||
String nomeDisciplina
|
||||
) {}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
public record RespostaLoginDTO(String token, RespostaEstudanteDTO estudante) {
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record RespostaNotificacaoDTO(
|
||||
String id,
|
||||
String titulo,
|
||||
String mensagem,
|
||||
String tipo,
|
||||
String referenciaId,
|
||||
String tipoReferencia,
|
||||
boolean lida,
|
||||
LocalDateTime dataGeracao
|
||||
) {}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.agendaestudantil.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record RespostaTarefaDTO(
|
||||
String id,
|
||||
String titulo,
|
||||
String descricao,
|
||||
String prioridade,
|
||||
String status,
|
||||
LocalDate dataEntrega,
|
||||
String disciplinaId,
|
||||
String estudanteId
|
||||
) {}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.agendaestudantil.entidade;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Document(collection = "disciplinas")
|
||||
@CompoundIndex(name = "estudante_nome_idx", def = "{estudanteId: 1, nome: 1}")
|
||||
public class Disciplina extends EntidadeAuditoria {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
@Indexed
|
||||
private String estudanteId;
|
||||
private String nome;
|
||||
private String professor;
|
||||
private String sala;
|
||||
private String cor;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.agendaestudantil.entidade;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public abstract class EntidadeAuditoria {
|
||||
|
||||
@CreatedDate
|
||||
private LocalDateTime dataCriacao;
|
||||
|
||||
@LastModifiedDate
|
||||
private LocalDateTime dataAtualizacao;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.agendaestudantil.entidade;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Document(collection = "estudantes")
|
||||
public class Estudante extends EntidadeAuditoria {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Indexed(unique = true)
|
||||
private String email;
|
||||
|
||||
private String nome;
|
||||
|
||||
private String senha;
|
||||
|
||||
private String curso;
|
||||
|
||||
private Integer periodo;
|
||||
|
||||
private Boolean consentimentoLgpd;
|
||||
|
||||
private LocalDateTime dataConsentimento;
|
||||
|
||||
private String versaoTermos;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.agendaestudantil.entidade;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Document(collection = "eventos")
|
||||
@CompoundIndex(name = "estudante_data_hora_idx", def = "{estudanteId: 1, dataHora: 1}")
|
||||
public class Evento extends EntidadeAuditoria {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
@Indexed
|
||||
private String estudanteId;
|
||||
private String titulo;
|
||||
private String descricao;
|
||||
private TipoEvento tipo;
|
||||
private String local;
|
||||
private String disciplinaId;
|
||||
private LocalDateTime dataHora;
|
||||
private StatusEvento status;
|
||||
|
||||
public enum TipoEvento {
|
||||
AULA, PROVA, TRABALHO, ESTUDO, EXAME, OUTRO
|
||||
}
|
||||
|
||||
public enum StatusEvento {
|
||||
ATIVO, CANCELADO
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.agendaestudantil.entidade;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Document(collection = "notificacoes")
|
||||
@CompoundIndex(name = "estudante_lida_idx", def = "{estudanteId: 1, lida: 1}")
|
||||
public class Notificacao extends EntidadeAuditoria {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
@Indexed
|
||||
private String estudanteId;
|
||||
private String titulo;
|
||||
private String mensagem;
|
||||
private TipoNotificacao tipo;
|
||||
private String referenciaId;
|
||||
private TipoReferencia tipoReferencia;
|
||||
private boolean lida;
|
||||
private LocalDateTime dataGeracao;
|
||||
|
||||
public enum TipoNotificacao {
|
||||
PRAZO_PROXIMO, TAREFA_ATRASADA, EVENTO_PROXIMO
|
||||
}
|
||||
|
||||
public enum TipoReferencia {
|
||||
TAREFA, EVENTO
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.agendaestudantil.entidade;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Document(collection = "tarefas")
|
||||
@CompoundIndexes({
|
||||
@CompoundIndex(name = "estudante_data_entrega_idx", def = "{estudanteId: 1, dataEntrega: 1}"),
|
||||
@CompoundIndex(name = "estudante_status_idx", def = "{estudanteId: 1, status: 1}")
|
||||
})
|
||||
public class Tarefa extends EntidadeAuditoria {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
private String titulo;
|
||||
private String descricao;
|
||||
private Prioridade prioridade;
|
||||
private StatusTarefa status;
|
||||
private LocalDate dataEntrega;
|
||||
private String disciplinaId;
|
||||
@Indexed
|
||||
private String estudanteId;
|
||||
|
||||
public enum Prioridade {
|
||||
BAIXA, MEDIA, ALTA, URGENTE
|
||||
}
|
||||
|
||||
public enum StatusTarefa {
|
||||
PENDENTE, EM_ANDAMENTO, CONCLUIDA, ATRASADA
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.agendaestudantil.excecao;
|
||||
|
||||
public class ExcecaoNegocio extends RuntimeException {
|
||||
public ExcecaoNegocio(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.agendaestudantil.excecao;
|
||||
|
||||
public class ExcecaoRecursoNaoEncontrado extends RuntimeException {
|
||||
public ExcecaoRecursoNaoEncontrado(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.agendaestudantil.excecao;
|
||||
|
||||
import com.agendaestudantil.dto.RespostaApi;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ManipuladorExcecaoGlobal {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ManipuladorExcecaoGlobal.class);
|
||||
|
||||
@ExceptionHandler(ExcecaoRecursoNaoEncontrado.class)
|
||||
public ResponseEntity<RespostaApi<Void>> handleResourceNotFound(ExcecaoRecursoNaoEncontrado ex) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(ExcecaoNegocio.class)
|
||||
public ResponseEntity<RespostaApi<Void>> handleExcecaoNegocio(ExcecaoNegocio ex) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(new RespostaApi<>(null, ex.getMessage(), LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<RespostaApi<Map<String, String>>> handleValidationException(
|
||||
MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
for (FieldError error : ex.getBindingResult().getFieldErrors()) {
|
||||
errors.put(error.getField(), error.getDefaultMessage());
|
||||
}
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(new RespostaApi<>(errors, "Falha na validação", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(ResponseStatusException.class)
|
||||
public ResponseEntity<RespostaApi<Void>> handleResponseStatusException(ResponseStatusException ex) {
|
||||
String reason = ex.getReason();
|
||||
if (reason == null || reason.isBlank()) {
|
||||
reason = "Acesso não autorizado";
|
||||
}
|
||||
return ResponseEntity.status(ex.getStatusCode())
|
||||
.body(new RespostaApi<>(null, reason, LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<RespostaApi<Void>> handleGenericException(Exception ex) {
|
||||
log.error("Erro interno no servidor", ex);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(new RespostaApi<>(null, "Erro interno no servidor", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.agendaestudantil.filtro;
|
||||
|
||||
import com.agendaestudantil.utilitario.UtilJwt;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class FiltroJwt extends OncePerRequestFilter {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FiltroJwt.class);
|
||||
|
||||
private final UtilJwt utilJwt;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
public FiltroJwt(UtilJwt utilJwt, UserDetailsService userDetailsService) {
|
||||
this.utilJwt = utilJwt;
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
String path = request.getRequestURI();
|
||||
return path.equals("/") || path.equals("/index.html") || path.equals("/favicon.ico")
|
||||
|| path.startsWith("/static/") || path.startsWith("/css/") || path.startsWith("/js/")
|
||||
|| path.startsWith("/img/") || path.startsWith("/imagens/")
|
||||
|| path.startsWith("/api/estudantes/login") || path.startsWith("/api/estudantes/cadastro")
|
||||
|| path.endsWith(".css") || path.endsWith(".js")
|
||||
|| path.endsWith(".ico") || path.endsWith(".html") || path.endsWith(".png")
|
||||
|| path.endsWith(".svg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
String header = request.getHeader("Authorization");
|
||||
String token = null;
|
||||
String estudanteId = null;
|
||||
|
||||
if (header != null && header.startsWith("Bearer ")) {
|
||||
token = header.substring(7);
|
||||
}
|
||||
|
||||
if (token != null && utilJwt.validateToken(token)) {
|
||||
estudanteId = utilJwt.getEstudanteIdFromToken(token);
|
||||
|
||||
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
try {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(estudanteId);
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, userDetails.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
} catch (Exception e) {
|
||||
log.warn("Falha ao carregar usuario do token (id={}): {}", estudanteId, e.getMessage());
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.agendaestudantil.repositorio;
|
||||
|
||||
import com.agendaestudantil.entidade.Disciplina;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface DisciplinaRepositorio extends MongoRepository<Disciplina, String> {
|
||||
List<Disciplina> findByEstudanteId(String estudanteId);
|
||||
|
||||
List<Disciplina> findByIdIn(Collection<String> ids);
|
||||
|
||||
void deleteByEstudanteId(String estudanteId);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.agendaestudantil.repositorio;
|
||||
|
||||
import com.agendaestudantil.entidade.Estudante;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface EstudanteRepositorio extends MongoRepository<Estudante, String> {
|
||||
Optional<Estudante> findByEmail(String email);
|
||||
boolean existsByEmail(String email);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.agendaestudantil.repositorio;
|
||||
|
||||
import com.agendaestudantil.entidade.Evento;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface EventoRepositorio extends MongoRepository<Evento, String> {
|
||||
List<Evento> findByEstudanteId(String estudanteId);
|
||||
|
||||
List<Evento> findByDisciplinaId(String disciplinaId);
|
||||
|
||||
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1, $lte: ?2}}")
|
||||
List<Evento> findByEstudanteIdAndDataHoraBetween(String estudanteId, LocalDateTime inicio, LocalDateTime fim);
|
||||
|
||||
@Query("{'estudanteId': ?0, 'dataHora': {$gte: ?1}}")
|
||||
List<Evento> findProximosEventosByEstudanteId(String estudanteId, LocalDateTime data);
|
||||
|
||||
@Query("{'dataHora': {$gte: ?0, $lte: ?1}}")
|
||||
List<Evento> findEventosNasProximas24h(LocalDateTime inicio, LocalDateTime fim);
|
||||
|
||||
void deleteByEstudanteId(String estudanteId);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.agendaestudantil.repositorio;
|
||||
|
||||
import com.agendaestudantil.entidade.Notificacao;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface NotificacaoRepositorio extends MongoRepository<Notificacao, String> {
|
||||
|
||||
List<Notificacao> findByEstudanteIdAndLidaFalse(String estudanteId);
|
||||
|
||||
List<Notificacao> findByEstudanteId(String estudanteId);
|
||||
|
||||
long countByEstudanteIdAndLidaFalse(String estudanteId);
|
||||
|
||||
void deleteByEstudanteId(String estudanteId);
|
||||
|
||||
boolean existsByEstudanteIdAndReferenciaId(String estudanteId, String referenciaId);
|
||||
|
||||
boolean existsByEstudanteIdAndReferenciaIdAndTipo(String estudanteId, String referenciaId, Notificacao.TipoNotificacao tipo);
|
||||
|
||||
List<Notificacao> findByEstudanteIdAndReferenciaIdAndTipo(String estudanteId, String referenciaId, Notificacao.TipoNotificacao tipo);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.agendaestudantil.repositorio;
|
||||
|
||||
import com.agendaestudantil.entidade.Tarefa;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface TarefaRepositorio extends MongoRepository<Tarefa, String> {
|
||||
List<Tarefa> findByEstudanteId(String estudanteId);
|
||||
|
||||
List<Tarefa> findByEstudanteIdAndStatus(String estudanteId, Tarefa.StatusTarefa status);
|
||||
|
||||
List<Tarefa> findByDisciplinaId(String disciplinaId);
|
||||
|
||||
@Query("{'estudanteId': ?0, 'dataEntrega': ?1}")
|
||||
List<Tarefa> findByEstudanteIdAndDataEntrega(String estudanteId, LocalDate data);
|
||||
|
||||
@Query("{'estudanteId': ?0, 'dataEntrega': {$gte: ?1, $lte: ?2}}")
|
||||
List<Tarefa> findByEstudanteIdAndDataEntregaBetween(String estudanteId, LocalDate inicio, LocalDate fim);
|
||||
|
||||
@Query("{'estudanteId': ?0, 'status': {$ne: ?1}}")
|
||||
List<Tarefa> findTarefasPendentesByEstudanteId(String estudanteId, Tarefa.StatusTarefa status);
|
||||
|
||||
@Query("{'status': {$ne: 'CONCLUIDA'}, 'dataEntrega': {$gte: ?0, $lte: ?1}}")
|
||||
List<Tarefa> findTarefasNaoConcluidasComDataEntre(LocalDate inicio, LocalDate fim);
|
||||
|
||||
void deleteByEstudanteId(String estudanteId);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.agendaestudantil.seguranca;
|
||||
|
||||
import com.agendaestudantil.entidade.Estudante;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class DetalhesUsuarioPersonalizado implements UserDetails {
|
||||
|
||||
private final Estudante estudante;
|
||||
|
||||
public DetalhesUsuarioPersonalizado(Estudante estudante) {
|
||||
this.estudante = estudante;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return estudante.getSenha();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return estudante.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.agendaestudantil.seguranca;
|
||||
|
||||
import com.agendaestudantil.entidade.Estudante;
|
||||
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ServicoAutenticacaoUsuario implements UserDetailsService {
|
||||
|
||||
private final EstudanteRepositorio estudanteRepositorio;
|
||||
|
||||
public ServicoAutenticacaoUsuario(EstudanteRepositorio estudanteRepositorio) {
|
||||
this.estudanteRepositorio = estudanteRepositorio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String estudanteId) throws UsernameNotFoundException {
|
||||
Estudante estudante = estudanteRepositorio.findById(estudanteId)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("Estudante não encontrado"));
|
||||
|
||||
return new DetalhesUsuarioPersonalizado(estudante);
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
|
||||
import com.agendaestudantil.entidade.Disciplina;
|
||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class DisciplinaServico {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DisciplinaServico.class);
|
||||
|
||||
private final DisciplinaRepositorio disciplinaRepositorio;
|
||||
private final TarefaRepositorio tarefaRepositorio;
|
||||
private final EventoRepositorio eventoRepositorio;
|
||||
|
||||
public DisciplinaServico(DisciplinaRepositorio disciplinaRepositorio, TarefaRepositorio tarefaRepositorio,
|
||||
EventoRepositorio eventoRepositorio) {
|
||||
this.disciplinaRepositorio = disciplinaRepositorio;
|
||||
this.tarefaRepositorio = tarefaRepositorio;
|
||||
this.eventoRepositorio = eventoRepositorio;
|
||||
}
|
||||
|
||||
private void validarAcesso(String estudanteId) {
|
||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
if (!authUser.equals(estudanteId)) {
|
||||
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||
}
|
||||
}
|
||||
|
||||
public RespostaDisciplinaDTO criarDisciplina(Disciplina disciplina, String estudanteId) {
|
||||
log.debug("Criando disciplina para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
|
||||
disciplina.setEstudanteId(estudanteId);
|
||||
Disciplina salva = disciplinaRepositorio.save(disciplina);
|
||||
return toDTO(salva);
|
||||
}
|
||||
|
||||
public List<RespostaDisciplinaDTO> listarPorEstudante(String estudanteId) {
|
||||
log.debug("Listando disciplinas para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
|
||||
return disciplinaRepositorio.findByEstudanteId(estudanteId).stream()
|
||||
.map(this::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public RespostaDisciplinaDTO buscarPorId(String id, String estudanteId) {
|
||||
Disciplina disciplina = getDisciplinaEntity(id);
|
||||
validarAcesso(disciplina.getEstudanteId());
|
||||
return toDTO(disciplina);
|
||||
}
|
||||
|
||||
public RespostaDisciplinaDTO atualizarDisciplina(String id, Disciplina disciplinaAtualizada, String estudanteId) {
|
||||
Disciplina disciplina = getDisciplinaEntity(id);
|
||||
validarAcesso(disciplina.getEstudanteId());
|
||||
|
||||
disciplina.setNome(disciplinaAtualizada.getNome());
|
||||
disciplina.setProfessor(disciplinaAtualizada.getProfessor());
|
||||
disciplina.setSala(disciplinaAtualizada.getSala());
|
||||
disciplina.setCor(disciplinaAtualizada.getCor());
|
||||
|
||||
return toDTO(disciplinaRepositorio.save(disciplina));
|
||||
}
|
||||
|
||||
public void excluirDisciplina(String id, String estudanteId) {
|
||||
Disciplina disciplina = getDisciplinaEntity(id);
|
||||
validarAcesso(disciplina.getEstudanteId());
|
||||
tarefaRepositorio.findByDisciplinaId(id).forEach(tarefa -> {
|
||||
tarefa.setDisciplinaId(null);
|
||||
tarefaRepositorio.save(tarefa);
|
||||
});
|
||||
eventoRepositorio.findByDisciplinaId(id).forEach(evento -> {
|
||||
evento.setDisciplinaId(null);
|
||||
eventoRepositorio.save(evento);
|
||||
});
|
||||
disciplinaRepositorio.delete(disciplina);
|
||||
}
|
||||
|
||||
private Disciplina getDisciplinaEntity(String id) {
|
||||
return disciplinaRepositorio.findById(id)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Disciplina não encontrada"));
|
||||
}
|
||||
|
||||
private RespostaDisciplinaDTO toDTO(Disciplina disciplina) {
|
||||
return new RespostaDisciplinaDTO(
|
||||
disciplina.getId(),
|
||||
disciplina.getEstudanteId(),
|
||||
disciplina.getNome(),
|
||||
disciplina.getProfessor(),
|
||||
disciplina.getSala(),
|
||||
disciplina.getCor()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.dto.RequisicaoAtualizacaoEstudanteDTO;
|
||||
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||
import com.agendaestudantil.dto.RespostaDadosCompletoDTO;
|
||||
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||
import com.agendaestudantil.dto.RequisicaoTrocaSenhaDTO;
|
||||
import com.agendaestudantil.dto.RespostaDisciplinaDTO;
|
||||
import com.agendaestudantil.dto.RespostaEventoDTO;
|
||||
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||
import com.agendaestudantil.entidade.Estudante;
|
||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
||||
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
||||
import com.agendaestudantil.repositorio.NotificacaoRepositorio;
|
||||
import com.agendaestudantil.utilitario.UtilJwt;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class EstudanteServico {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EstudanteServico.class);
|
||||
|
||||
private final EstudanteRepositorio estudanteRepositorio;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final UtilJwt utilJwt;
|
||||
private final TarefaRepositorio tarefaRepositorio;
|
||||
private final EventoRepositorio eventoRepositorio;
|
||||
private final DisciplinaRepositorio disciplinaRepositorio;
|
||||
private final NotificacaoRepositorio notificacaoRepositorio;
|
||||
|
||||
public EstudanteServico(EstudanteRepositorio estudanteRepositorio, PasswordEncoder passwordEncoder,
|
||||
UtilJwt utilJwt, TarefaRepositorio tarefaRepositorio, EventoRepositorio eventoRepositorio,
|
||||
DisciplinaRepositorio disciplinaRepositorio, NotificacaoRepositorio notificacaoRepositorio) {
|
||||
this.estudanteRepositorio = estudanteRepositorio;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.utilJwt = utilJwt;
|
||||
this.tarefaRepositorio = tarefaRepositorio;
|
||||
this.eventoRepositorio = eventoRepositorio;
|
||||
this.disciplinaRepositorio = disciplinaRepositorio;
|
||||
this.notificacaoRepositorio = notificacaoRepositorio;
|
||||
}
|
||||
|
||||
public RespostaEstudanteDTO cadastrar(RequisicaoCadastroDTO dto) {
|
||||
log.debug("Acessando metodo cadastrar para email: {}", dto.email());
|
||||
if (!Boolean.TRUE.equals(dto.consentimentoLgpd())) {
|
||||
throw new ExcecaoNegocio("E necessario aceitar os termos para se cadastrar");
|
||||
}
|
||||
Optional<Estudante> existente = estudanteRepositorio.findByEmail(dto.email());
|
||||
if (existente.isPresent()) {
|
||||
log.error("Email ja cadastrado: {}", dto.email());
|
||||
throw new ExcecaoNegocio("Email ja cadastrado");
|
||||
}
|
||||
|
||||
Estudante estudante = new Estudante();
|
||||
estudante.setNome(dto.nome());
|
||||
estudante.setEmail(dto.email());
|
||||
estudante.setSenha(passwordEncoder.encode(dto.senha()));
|
||||
estudante.setCurso(dto.curso());
|
||||
estudante.setPeriodo(dto.periodo());
|
||||
estudante.setConsentimentoLgpd(dto.consentimentoLgpd());
|
||||
estudante.setDataConsentimento(LocalDateTime.now());
|
||||
estudante.setVersaoTermos("1.0");
|
||||
|
||||
Estudante salvo = estudanteRepositorio.save(estudante);
|
||||
return toResponse(salvo);
|
||||
}
|
||||
|
||||
public RespostaLoginDTO login(RequisicaoLoginDTO dto) {
|
||||
log.debug("Acessando metodo login para email: {}", dto.email());
|
||||
Optional<Estudante> estudanteParam = estudanteRepositorio.findByEmail(dto.email());
|
||||
|
||||
if (estudanteParam.isEmpty()) {
|
||||
log.error("Email ou senha incorretos para email: {}", dto.email());
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos");
|
||||
}
|
||||
|
||||
Estudante estudante = estudanteParam.get();
|
||||
|
||||
if (!passwordEncoder.matches(dto.senha(), estudante.getSenha())) {
|
||||
log.error("Falha ao validar senha para email: {}", dto.email());
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Email ou senha incorretos");
|
||||
}
|
||||
|
||||
String token = utilJwt.generateToken(estudante.getId());
|
||||
return new RespostaLoginDTO(token, toResponse(estudante));
|
||||
}
|
||||
|
||||
public RespostaEstudanteDTO buscarPorId(String id) {
|
||||
Estudante estudante = estudanteRepositorio.findById(id)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante nao encontrado"));
|
||||
return toResponse(estudante);
|
||||
}
|
||||
|
||||
public RespostaEstudanteDTO atualizar(String id, RequisicaoAtualizacaoEstudanteDTO dto) {
|
||||
Estudante estudante = estudanteRepositorio.findById(id)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante nao encontrado"));
|
||||
|
||||
estudante.setNome(dto.nome());
|
||||
estudante.setCurso(dto.curso());
|
||||
estudante.setPeriodo(dto.periodo());
|
||||
|
||||
Estudante atualizado = estudanteRepositorio.save(estudante);
|
||||
return toResponse(atualizado);
|
||||
}
|
||||
|
||||
public void trocarSenha(String id, RequisicaoTrocaSenhaDTO dto) {
|
||||
Estudante estudante = estudanteRepositorio.findById(id)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Estudante nao encontrado"));
|
||||
|
||||
if (!passwordEncoder.matches(dto.senhaAtual(), estudante.getSenha())) {
|
||||
throw new ExcecaoNegocio("Senha atual incorreta");
|
||||
}
|
||||
|
||||
estudante.setSenha(passwordEncoder.encode(dto.novaSenha()));
|
||||
estudanteRepositorio.save(estudante);
|
||||
}
|
||||
|
||||
public void excluirConta(String id) {
|
||||
if (!estudanteRepositorio.existsById(id)) {
|
||||
throw new ExcecaoRecursoNaoEncontrado("Estudante nao encontrado");
|
||||
}
|
||||
tarefaRepositorio.deleteByEstudanteId(id);
|
||||
eventoRepositorio.deleteByEstudanteId(id);
|
||||
disciplinaRepositorio.deleteByEstudanteId(id);
|
||||
notificacaoRepositorio.deleteByEstudanteId(id);
|
||||
estudanteRepositorio.deleteById(id);
|
||||
log.info("Conta e todos os dados do estudante {} removidos (LGPD Art. 18-VI)", id);
|
||||
}
|
||||
|
||||
public RespostaDadosCompletoDTO exportarDados(String estudanteId) {
|
||||
RespostaEstudanteDTO estudante = buscarPorId(estudanteId);
|
||||
|
||||
List<RespostaTarefaDTO> tarefas = tarefaRepositorio
|
||||
.findByEstudanteId(estudanteId).stream()
|
||||
.map(t -> new RespostaTarefaDTO(
|
||||
t.getId(), t.getTitulo(), t.getDescricao(),
|
||||
t.getPrioridade() != null ? t.getPrioridade().name() : null,
|
||||
t.getStatus() != null ? t.getStatus().name() : null,
|
||||
t.getDataEntrega(), t.getDisciplinaId(), t.getEstudanteId()))
|
||||
.toList();
|
||||
|
||||
List<RespostaEventoDTO> eventos = eventoRepositorio
|
||||
.findByEstudanteId(estudanteId).stream()
|
||||
.map(e -> new RespostaEventoDTO(
|
||||
e.getId(), e.getEstudanteId(), e.getTitulo(), e.getDescricao(),
|
||||
e.getTipo() != null ? e.getTipo().name() : null,
|
||||
e.getLocal(), e.getDisciplinaId(), e.getDataHora(),
|
||||
e.getStatus() != null ? e.getStatus().name() : null,
|
||||
null))
|
||||
.toList();
|
||||
|
||||
List<RespostaDisciplinaDTO> disciplinas = disciplinaRepositorio
|
||||
.findByEstudanteId(estudanteId).stream()
|
||||
.map(d -> new RespostaDisciplinaDTO(
|
||||
d.getId(), d.getEstudanteId(), d.getNome(), d.getProfessor(), d.getSala(), d.getCor()))
|
||||
.toList();
|
||||
|
||||
return new RespostaDadosCompletoDTO(estudante, tarefas, eventos, disciplinas, LocalDateTime.now());
|
||||
}
|
||||
|
||||
private RespostaEstudanteDTO toResponse(Estudante estudante) {
|
||||
return new RespostaEstudanteDTO(
|
||||
estudante.getId(),
|
||||
estudante.getNome(),
|
||||
estudante.getEmail(),
|
||||
estudante.getCurso(),
|
||||
estudante.getPeriodo());
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.dto.RequisicaoEventoDTO;
|
||||
import com.agendaestudantil.dto.RespostaEventoDTO;
|
||||
import com.agendaestudantil.entidade.Disciplina;
|
||||
import com.agendaestudantil.entidade.Evento;
|
||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||
import com.agendaestudantil.repositorio.DisciplinaRepositorio;
|
||||
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class EventoServico {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EventoServico.class);
|
||||
|
||||
private final EventoRepositorio eventoRepositorio;
|
||||
private final DisciplinaRepositorio disciplinaRepositorio;
|
||||
|
||||
public EventoServico(EventoRepositorio eventoRepositorio, DisciplinaRepositorio disciplinaRepositorio) {
|
||||
this.eventoRepositorio = eventoRepositorio;
|
||||
this.disciplinaRepositorio = disciplinaRepositorio;
|
||||
}
|
||||
|
||||
private void validarAcesso(String estudanteId) {
|
||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
if (!authUser.equals(estudanteId)) {
|
||||
log.error("Acesso negado. Usuario {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||
throw new ExcecaoNegocio("Acesso negado");
|
||||
}
|
||||
}
|
||||
|
||||
private void validarDisciplina(String disciplinaId, String estudanteId) {
|
||||
if (disciplinaId == null || disciplinaId.isBlank()) {
|
||||
return;
|
||||
}
|
||||
Disciplina disciplina = disciplinaRepositorio.findById(disciplinaId)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Disciplina não encontrada"));
|
||||
if (!disciplina.getEstudanteId().equals(estudanteId)) {
|
||||
throw new ExcecaoNegocio("Disciplina não pertence ao estudante");
|
||||
}
|
||||
}
|
||||
|
||||
public RespostaEventoDTO criarEvento(Evento evento, String estudanteId) {
|
||||
log.debug("Criando evento para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
|
||||
if (evento.getDisciplinaId() != null) {
|
||||
validarDisciplina(evento.getDisciplinaId(), estudanteId);
|
||||
}
|
||||
|
||||
evento.setEstudanteId(estudanteId);
|
||||
Evento salvo = eventoRepositorio.save(evento);
|
||||
return toDTO(salvo, Map.of());
|
||||
}
|
||||
|
||||
public List<RespostaEventoDTO> listarPorEstudante(String estudanteId) {
|
||||
log.debug("Listando eventos para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
|
||||
List<Evento> eventos = eventoRepositorio.findByEstudanteId(estudanteId);
|
||||
Map<String, String> disciplinasMap = resolverDisciplinas(eventos);
|
||||
return eventos.stream()
|
||||
.map(e -> toDTO(e, disciplinasMap))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<RespostaEventoDTO> listarPorPeriodo(String estudanteId, java.time.LocalDateTime inicio, java.time.LocalDateTime fim) {
|
||||
validarAcesso(estudanteId);
|
||||
List<Evento> eventos = eventoRepositorio.findByEstudanteIdAndDataHoraBetween(estudanteId, inicio, fim);
|
||||
Map<String, String> disciplinasMap = resolverDisciplinas(eventos);
|
||||
return eventos.stream()
|
||||
.map(e -> toDTO(e, disciplinasMap))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<RespostaEventoDTO> listarProximosEventos(String estudanteId) {
|
||||
validarAcesso(estudanteId);
|
||||
List<Evento> eventos = eventoRepositorio.findProximosEventosByEstudanteId(estudanteId, java.time.LocalDateTime.now());
|
||||
Map<String, String> disciplinasMap = resolverDisciplinas(eventos);
|
||||
return eventos.stream()
|
||||
.map(e -> toDTO(e, disciplinasMap))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public RespostaEventoDTO buscarPorId(String id) {
|
||||
Evento evento = getEventoEntity(id);
|
||||
validarAcesso(evento.getEstudanteId());
|
||||
return toDTO(evento, Map.of());
|
||||
}
|
||||
|
||||
public RespostaEventoDTO atualizarEvento(String id, RequisicaoEventoDTO dto) {
|
||||
Evento evento = getEventoEntity(id);
|
||||
validarAcesso(evento.getEstudanteId());
|
||||
|
||||
evento.setTitulo(dto.titulo());
|
||||
evento.setDescricao(dto.descricao());
|
||||
evento.setTipo(dto.tipo());
|
||||
evento.setLocal(dto.local());
|
||||
evento.setDataHora(dto.dataHora());
|
||||
|
||||
if (dto.disciplinaId() != null) {
|
||||
validarDisciplina(dto.disciplinaId(), evento.getEstudanteId());
|
||||
evento.setDisciplinaId(dto.disciplinaId());
|
||||
}
|
||||
|
||||
return toDTO(eventoRepositorio.save(evento), Map.of());
|
||||
}
|
||||
|
||||
public void excluirEvento(String id) {
|
||||
Evento evento = getEventoEntity(id);
|
||||
validarAcesso(evento.getEstudanteId());
|
||||
eventoRepositorio.delete(evento);
|
||||
}
|
||||
|
||||
public RespostaEventoDTO cancelarEvento(String id) {
|
||||
Evento evento = getEventoEntity(id);
|
||||
validarAcesso(evento.getEstudanteId());
|
||||
evento.setStatus(Evento.StatusEvento.CANCELADO);
|
||||
return toDTO(eventoRepositorio.save(evento), Map.of());
|
||||
}
|
||||
|
||||
private Evento getEventoEntity(String id) {
|
||||
return eventoRepositorio.findById(id)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Evento não encontrado"));
|
||||
}
|
||||
|
||||
private Map<String, String> resolverDisciplinas(List<Evento> eventos) {
|
||||
Set<String> disciplinaIds = eventos.stream()
|
||||
.map(Evento::getDisciplinaId)
|
||||
.filter(id -> id != null && !id.isBlank())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (disciplinaIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
return disciplinaRepositorio.findByIdIn(disciplinaIds).stream()
|
||||
.collect(Collectors.toMap(Disciplina::getId, Disciplina::getNome));
|
||||
}
|
||||
|
||||
private RespostaEventoDTO toDTO(Evento evento, Map<String, String> disciplinasMap) {
|
||||
String nomeDisciplina = null;
|
||||
final String disciplinaId = evento.getDisciplinaId();
|
||||
if (disciplinaId != null) {
|
||||
nomeDisciplina = disciplinasMap.get(disciplinaId);
|
||||
}
|
||||
return new RespostaEventoDTO(
|
||||
evento.getId(),
|
||||
evento.getEstudanteId(),
|
||||
evento.getTitulo(),
|
||||
evento.getDescricao(),
|
||||
evento.getTipo() != null ? evento.getTipo().name() : null,
|
||||
evento.getLocal(),
|
||||
evento.getDisciplinaId(),
|
||||
evento.getDataHora(),
|
||||
evento.getStatus() != null ? evento.getStatus().name() : null,
|
||||
nomeDisciplina
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.entidade.Evento;
|
||||
import com.agendaestudantil.entidade.Notificacao;
|
||||
import com.agendaestudantil.entidade.Tarefa;
|
||||
import com.agendaestudantil.repositorio.EventoRepositorio;
|
||||
import com.agendaestudantil.repositorio.NotificacaoRepositorio;
|
||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class NotificacaoAgendadorServico {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(NotificacaoAgendadorServico.class);
|
||||
|
||||
private final TarefaRepositorio tarefaRepositorio;
|
||||
private final EventoRepositorio eventoRepositorio;
|
||||
private final NotificacaoRepositorio notificacaoRepositorio;
|
||||
|
||||
public NotificacaoAgendadorServico(TarefaRepositorio tarefaRepositorio, EventoRepositorio eventoRepositorio,
|
||||
NotificacaoRepositorio notificacaoRepositorio) {
|
||||
this.tarefaRepositorio = tarefaRepositorio;
|
||||
this.eventoRepositorio = eventoRepositorio;
|
||||
this.notificacaoRepositorio = notificacaoRepositorio;
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 3600000)
|
||||
public void gerarNotificacoes() {
|
||||
log.debug("Iniciando geracao de notificacoes");
|
||||
LocalDate hoje = LocalDate.now();
|
||||
LocalDate ontem = hoje.minusDays(1);
|
||||
LocalDate tresDiasDepois = hoje.plusDays(3);
|
||||
|
||||
List<Tarefa> tarefas = tarefaRepositorio.findTarefasNaoConcluidasComDataEntre(ontem, tresDiasDepois);
|
||||
for (Tarefa tarefa : tarefas) {
|
||||
if (tarefa.getStatus() == Tarefa.StatusTarefa.CONCLUIDA) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalDate dataEntrega = tarefa.getDataEntrega();
|
||||
if (dataEntrega == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long diasAteEntrega = ChronoUnit.DAYS.between(hoje, dataEntrega);
|
||||
|
||||
if (dataEntrega.isBefore(hoje)) {
|
||||
if (!tarefa.getStatus().equals(Tarefa.StatusTarefa.ATRASADA)) {
|
||||
tarefa.setStatus(Tarefa.StatusTarefa.ATRASADA);
|
||||
tarefaRepositorio.save(tarefa);
|
||||
}
|
||||
|
||||
if (!notificacaoRepositorio.existsByEstudanteIdAndReferenciaIdAndTipo(
|
||||
tarefa.getEstudanteId(), tarefa.getId(), Notificacao.TipoNotificacao.TAREFA_ATRASADA)) {
|
||||
criarNotificacao(tarefa.getEstudanteId(), tarefa.getId(),
|
||||
"Tarefa atrasada: " + tarefa.getTitulo(),
|
||||
"A tarefa '" + tarefa.getTitulo() + "' esta atrasada desde " + dataEntrega,
|
||||
Notificacao.TipoNotificacao.TAREFA_ATRASADA,
|
||||
Notificacao.TipoReferencia.TAREFA);
|
||||
}
|
||||
} else if (diasAteEntrega == 0 || diasAteEntrega == 1 || diasAteEntrega == 2 || diasAteEntrega == 3) {
|
||||
boolean enviarNotificacao = false;
|
||||
if (diasAteEntrega == 0 || diasAteEntrega == 1) {
|
||||
enviarNotificacao = true;
|
||||
} else if (diasAteEntrega == 2 || diasAteEntrega == 3) {
|
||||
if (tarefa.getPrioridade() == Tarefa.Prioridade.ALTA || tarefa.getPrioridade() == Tarefa.Prioridade.URGENTE) {
|
||||
enviarNotificacao = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (enviarNotificacao) {
|
||||
boolean jaNotificouHoje = notificacaoRepositorio.findByEstudanteIdAndReferenciaIdAndTipo(
|
||||
tarefa.getEstudanteId(), tarefa.getId(), Notificacao.TipoNotificacao.PRAZO_PROXIMO)
|
||||
.stream()
|
||||
.anyMatch(n -> n.getDataGeracao().toLocalDate().equals(hoje));
|
||||
|
||||
if (!jaNotificouHoje) {
|
||||
String prazo = diasAteEntrega == 0 ? "hoje" : (diasAteEntrega == 1 ? "amanha" : "em " + diasAteEntrega + " dias");
|
||||
criarNotificacao(tarefa.getEstudanteId(), tarefa.getId(),
|
||||
"Tarefa com prazo proximo: " + tarefa.getTitulo(),
|
||||
"A tarefa '" + tarefa.getTitulo() + "' vence " + prazo,
|
||||
Notificacao.TipoNotificacao.PRAZO_PROXIMO,
|
||||
Notificacao.TipoReferencia.TAREFA);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocalDateTime agora = LocalDateTime.now();
|
||||
LocalDateTime em24Horas = agora.plusHours(24);
|
||||
List<Evento> eventos = eventoRepositorio.findEventosNasProximas24h(agora, em24Horas);
|
||||
for (Evento evento : eventos) {
|
||||
if (evento.getDataHora() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!notificacaoRepositorio.existsByEstudanteIdAndReferenciaIdAndTipo(
|
||||
evento.getEstudanteId(), evento.getId(), Notificacao.TipoNotificacao.EVENTO_PROXIMO)) {
|
||||
criarNotificacao(evento.getEstudanteId(), evento.getId(),
|
||||
"Evento nas proximas 24h: " + evento.getTitulo(),
|
||||
"O evento '" + evento.getTitulo() + "' sera em " + evento.getDataHora(),
|
||||
Notificacao.TipoNotificacao.EVENTO_PROXIMO,
|
||||
Notificacao.TipoReferencia.EVENTO);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Geracao de notificacoes concluida");
|
||||
}
|
||||
|
||||
private void criarNotificacao(String estudanteId, String referenciaId, String titulo, String mensagem,
|
||||
Notificacao.TipoNotificacao tipo, Notificacao.TipoReferencia tipoReferencia) {
|
||||
Notificacao notif = new Notificacao();
|
||||
notif.setEstudanteId(estudanteId);
|
||||
notif.setReferenciaId(referenciaId);
|
||||
notif.setTitulo(titulo);
|
||||
notif.setMensagem(mensagem);
|
||||
notif.setTipo(tipo);
|
||||
notif.setTipoReferencia(tipoReferencia);
|
||||
notif.setLida(false);
|
||||
notif.setDataGeracao(LocalDateTime.now());
|
||||
notificacaoRepositorio.save(notif);
|
||||
log.debug("Notificacao criada para estudante {}", estudanteId);
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.dto.RespostaNotificacaoDTO;
|
||||
import com.agendaestudantil.entidade.Notificacao;
|
||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||
import com.agendaestudantil.repositorio.NotificacaoRepositorio;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class NotificacaoServico {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(NotificacaoServico.class);
|
||||
|
||||
private final NotificacaoRepositorio notificacaoRepositorio;
|
||||
|
||||
public NotificacaoServico(NotificacaoRepositorio notificacaoRepositorio) {
|
||||
this.notificacaoRepositorio = notificacaoRepositorio;
|
||||
}
|
||||
|
||||
private void validarAcesso(String estudanteId) {
|
||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
if (!authUser.equals(estudanteId)) {
|
||||
log.error("Acesso negado. Usuário {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Acesso negado");
|
||||
}
|
||||
}
|
||||
|
||||
public List<RespostaNotificacaoDTO> listarNaoLidas(String estudanteId) {
|
||||
log.debug("Listando notificações não lidas para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
return notificacaoRepositorio.findByEstudanteIdAndLidaFalse(estudanteId).stream()
|
||||
.map(this::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<RespostaNotificacaoDTO> listarTodas(String estudanteId) {
|
||||
log.debug("Listando todas as notificações para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
return notificacaoRepositorio.findByEstudanteId(estudanteId).stream()
|
||||
.map(this::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public long contarNaoLidas(String estudanteId) {
|
||||
log.debug("Contando notificações não lidas para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
return notificacaoRepositorio.countByEstudanteIdAndLidaFalse(estudanteId);
|
||||
}
|
||||
|
||||
public RespostaNotificacaoDTO marcarComoLida(String id) {
|
||||
Notificacao notificacao = getNotificacaoEntity(id);
|
||||
validarAcesso(notificacao.getEstudanteId());
|
||||
notificacao.setLida(true);
|
||||
return toDTO(notificacaoRepositorio.save(notificacao));
|
||||
}
|
||||
|
||||
public void marcarTodasComoLidas(String estudanteId) {
|
||||
log.debug("Marcando todas as notificações como lidas para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
List<Notificacao> notificacoes = notificacaoRepositorio.findByEstudanteIdAndLidaFalse(estudanteId);
|
||||
for (Notificacao n : notificacoes) {
|
||||
n.setLida(true);
|
||||
notificacaoRepositorio.save(n);
|
||||
}
|
||||
}
|
||||
|
||||
public void excluirNotificacao(String id) {
|
||||
Notificacao notificacao = getNotificacaoEntity(id);
|
||||
validarAcesso(notificacao.getEstudanteId());
|
||||
notificacaoRepositorio.delete(notificacao);
|
||||
}
|
||||
|
||||
private Notificacao getNotificacaoEntity(String id) {
|
||||
return notificacaoRepositorio.findById(id)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Notificação não encontrada"));
|
||||
}
|
||||
|
||||
private RespostaNotificacaoDTO toDTO(Notificacao notificacao) {
|
||||
return new RespostaNotificacaoDTO(
|
||||
notificacao.getId(),
|
||||
notificacao.getTitulo(),
|
||||
notificacao.getMensagem(),
|
||||
notificacao.getTipo() != null ? notificacao.getTipo().name() : null,
|
||||
notificacao.getReferenciaId(),
|
||||
notificacao.getTipoReferencia() != null ? notificacao.getTipoReferencia().name() : null,
|
||||
notificacao.isLida(),
|
||||
notificacao.getDataGeracao()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||
import com.agendaestudantil.entidade.Tarefa;
|
||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class TarefaServico {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TarefaServico.class);
|
||||
|
||||
private final TarefaRepositorio tarefaRepositorio;
|
||||
|
||||
public TarefaServico(TarefaRepositorio tarefaRepositorio) {
|
||||
this.tarefaRepositorio = tarefaRepositorio;
|
||||
}
|
||||
|
||||
private void validarAcesso(String estudanteId) {
|
||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
if (!authUser.equals(estudanteId)) {
|
||||
log.error("Acesso negado. Usuario {} tentou acessar recurso do estudante {}", authUser, estudanteId);
|
||||
throw new ExcecaoNegocio("Acesso negado");
|
||||
}
|
||||
}
|
||||
|
||||
private void validarAcessoTarefa(Tarefa tarefa) {
|
||||
validarAcesso(tarefa.getEstudanteId());
|
||||
}
|
||||
|
||||
public RespostaTarefaDTO criarTarefa(RequisicaoTarefaDTO dto) {
|
||||
String estudanteId = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
log.debug("Criando tarefa para estudante: {}", estudanteId);
|
||||
|
||||
Tarefa tarefa = new Tarefa();
|
||||
tarefa.setTitulo(dto.titulo());
|
||||
tarefa.setDescricao(dto.descricao());
|
||||
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : Tarefa.Prioridade.MEDIA);
|
||||
tarefa.setStatus(dto.status() != null ? dto.status() : Tarefa.StatusTarefa.PENDENTE);
|
||||
tarefa.setDataEntrega(dto.dataEntrega());
|
||||
tarefa.setEstudanteId(estudanteId);
|
||||
|
||||
if (dto.disciplinaId() != null) {
|
||||
tarefa.setDisciplinaId(dto.disciplinaId());
|
||||
}
|
||||
|
||||
return toDTO(tarefaRepositorio.save(tarefa));
|
||||
}
|
||||
|
||||
public List<RespostaTarefaDTO> listarTarefasPorEstudante(String estudanteId) {
|
||||
log.debug("Listando tarefas para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
return tarefaRepositorio.findByEstudanteId(estudanteId).stream()
|
||||
.map(this::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<RespostaTarefaDTO> listarTarefasPendentes(String estudanteId) {
|
||||
log.debug("Listando tarefas pendentes para estudante: {}", estudanteId);
|
||||
validarAcesso(estudanteId);
|
||||
return tarefaRepositorio.findByEstudanteIdAndStatus(estudanteId, Tarefa.StatusTarefa.PENDENTE).stream()
|
||||
.map(this::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<RespostaTarefaDTO> listarTarefasPorData(String estudanteId, LocalDate data) {
|
||||
validarAcesso(estudanteId);
|
||||
return tarefaRepositorio.findByEstudanteIdAndDataEntrega(estudanteId, data).stream()
|
||||
.map(this::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public List<RespostaTarefaDTO> listarTarefasPorPeriodo(String estudanteId, LocalDate inicio, LocalDate fim) {
|
||||
validarAcesso(estudanteId);
|
||||
return tarefaRepositorio.findByEstudanteIdAndDataEntregaBetween(estudanteId, inicio, fim).stream()
|
||||
.map(this::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public RespostaTarefaDTO buscarTarefaPorId(String id) {
|
||||
return toDTO(getTarefaEntity(id));
|
||||
}
|
||||
|
||||
public RespostaTarefaDTO atualizarTarefa(String id, RequisicaoTarefaDTO dto) {
|
||||
Tarefa tarefa = getTarefaEntity(id);
|
||||
String authUser = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
if (!authUser.equals(tarefa.getEstudanteId())) {
|
||||
throw new ExcecaoNegocio("Acesso negado");
|
||||
}
|
||||
|
||||
tarefa.setTitulo(dto.titulo());
|
||||
tarefa.setDescricao(dto.descricao());
|
||||
tarefa.setPrioridade(dto.prioridade() != null ? dto.prioridade() : tarefa.getPrioridade());
|
||||
tarefa.setStatus(dto.status() != null ? dto.status() : tarefa.getStatus());
|
||||
if (dto.dataEntrega() != null) {
|
||||
tarefa.setDataEntrega(dto.dataEntrega());
|
||||
}
|
||||
|
||||
if (dto.disciplinaId() != null) {
|
||||
tarefa.setDisciplinaId(dto.disciplinaId());
|
||||
}
|
||||
|
||||
return toDTO(tarefaRepositorio.save(tarefa));
|
||||
}
|
||||
|
||||
public void excluirTarefa(String id) {
|
||||
tarefaRepositorio.delete(getTarefaEntity(id));
|
||||
}
|
||||
|
||||
public RespostaTarefaDTO marcarConcluida(String id) {
|
||||
Tarefa tarefa = getTarefaEntity(id);
|
||||
tarefa.setStatus(Tarefa.StatusTarefa.CONCLUIDA);
|
||||
return toDTO(tarefaRepositorio.save(tarefa));
|
||||
}
|
||||
|
||||
private Tarefa getTarefaEntity(String id) {
|
||||
Tarefa tarefa = tarefaRepositorio.findById(id)
|
||||
.orElseThrow(() -> new ExcecaoRecursoNaoEncontrado("Tarefa não encontrada"));
|
||||
validarAcessoTarefa(tarefa);
|
||||
return tarefa;
|
||||
}
|
||||
|
||||
private RespostaTarefaDTO toDTO(Tarefa tarefa) {
|
||||
return new RespostaTarefaDTO(
|
||||
tarefa.getId(),
|
||||
tarefa.getTitulo(),
|
||||
tarefa.getDescricao(),
|
||||
tarefa.getPrioridade() != null ? tarefa.getPrioridade().name() : null,
|
||||
tarefa.getStatus() != null ? tarefa.getStatus().name() : null,
|
||||
tarefa.getDataEntrega(),
|
||||
tarefa.getDisciplinaId(),
|
||||
tarefa.getEstudanteId());
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.agendaestudantil.utilitario;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
public class UtilJwt {
|
||||
|
||||
private final String secret;
|
||||
private final long jwtExpiration;
|
||||
|
||||
public UtilJwt(@Value("${jwt.secret}") String secret, @Value("${jwt.expiration}") long jwtExpiration) {
|
||||
this.secret = secret;
|
||||
this.jwtExpiration = jwtExpiration;
|
||||
}
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public String generateToken(String estudanteId) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + jwtExpiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.subject(estudanteId)
|
||||
.issuedAt(now)
|
||||
.expiration(expiryDate)
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String getEstudanteIdFromToken(String token) {
|
||||
Claims claims = Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
spring.data.mongodb.uri=${MONGO_URI:mongodb://localhost:27017/agenda_estudantil}
|
||||
cors.allowed.origins=${CORS_ORIGINS:http://localhost:8080,http://localhost:3000}
|
||||
jwt.secret=${JWT_SECRET:8a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7A8B9C0D1E2F3}
|
||||
jwt.expiration=${JWT_EXPIRATION:86400000}
|
||||
logging.level.org.springframework.data.mongodb=DEBUG
|
||||
springdoc.api-docs.enabled=true
|
||||
springdoc.swagger-ui.enabled=true
|
||||
@@ -1,8 +0,0 @@
|
||||
spring.data.mongodb.uri=${MONGO_URI}
|
||||
cors.allowed.origins=${CORS_ORIGINS}
|
||||
jwt.secret=${JWT_SECRET}
|
||||
springdoc.api-docs.enabled=false
|
||||
springdoc.swagger-ui.enabled=false
|
||||
logging.level.root=WARN
|
||||
logging.level.root=WARN
|
||||
logging.level.com.agendaestudantil=INFO
|
||||
@@ -1,13 +0,0 @@
|
||||
spring.application.name=${APP_NAME:Focus Agenda}
|
||||
server.port=${SERVER_PORT:8080}
|
||||
server.servlet.context-path=/
|
||||
spring.web.resources.static-locations=classpath:/static/
|
||||
spring.mvc.static-path-pattern=/**
|
||||
spring.mvc.contentnegotiation.favor-parameter=true
|
||||
spring.web.resources.add-mappings=true
|
||||
spring.servlet.multipart.enabled=false
|
||||
|
||||
spring.web.resources.cache.cachecontrol.max-age=3600
|
||||
spring.web.resources.cache.cachecontrol.cache-public=true
|
||||
|
||||
spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev}
|
||||
@@ -1,314 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: 'Poppins', 'Trebuchet MS', Arial, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px 20px;
|
||||
color: #1f2937;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#textotop {
|
||||
padding-left: 20px;
|
||||
font-size: clamp(22px, 5vw, 30px);
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
transition: all 0.4s ease;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
background: #114455;
|
||||
transform: translateY(-50%) rotate(180deg) scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: brightness(0) invert(1);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#log {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
background: #ffffff;
|
||||
padding: 40px 32px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.mens {
|
||||
text-align: center;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.campo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.linha-dupla {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
input, select {
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
font-size: 15px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-family: inherit;
|
||||
background-color: #f9fafb;
|
||||
outline: none;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
input:hover, select:hover {
|
||||
border-color: #c0392b;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
border-color: #c0392b;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.1);
|
||||
}
|
||||
|
||||
#logbtn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
background-color: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin-top: 15px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#logbtn:hover {
|
||||
background-color: #114455;
|
||||
box-shadow: 0 5px 15px rgba(17, 68, 85, 0.4);
|
||||
}
|
||||
|
||||
#logbtn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #c0392b;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #114455;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.campo-consentimento {
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.label-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: #4b5563;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.label-checkbox input[type="checkbox"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
display: inline-grid;
|
||||
place-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
outline: none !important;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.label-checkbox input[type="checkbox"]::before {
|
||||
content: "";
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
transform: scale(0);
|
||||
transition: 120ms transform ease-in-out;
|
||||
background-color: #fff;
|
||||
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
||||
}
|
||||
|
||||
.label-checkbox input[type="checkbox"]:checked {
|
||||
background-color: #c0392b;
|
||||
border-color: #c0392b;
|
||||
}
|
||||
|
||||
.label-checkbox input[type="checkbox"]:checked::before {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.label-checkbox input[type="checkbox"]:hover {
|
||||
border-color: #c0392b;
|
||||
}
|
||||
|
||||
.label-checkbox input[type="checkbox"]:focus,
|
||||
.label-checkbox input[type="checkbox"]:active {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#mensagem-erro, #mensagem-sucesso {
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mensagem-erro { background: #fee2e2; border: 1px solid #fca5a5; color: #b91c1c; }
|
||||
#mensagem-sucesso { background: #d1fae5; border: 1px solid #6ee7b7; color: #065f46; }
|
||||
|
||||
|
||||
[data-theme="dark"] body {
|
||||
background: #121212;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #log {
|
||||
background: #1e1e1e;
|
||||
border-color: #333;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mens,
|
||||
[data-theme="dark"] label,
|
||||
[data-theme="dark"] .label-checkbox {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] input,
|
||||
[data-theme="dark"] select {
|
||||
background: #1a1a1a;
|
||||
border-color: #333;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .campo-consentimento {
|
||||
background: #1a1a1a;
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .label-checkbox input[type="checkbox"] {
|
||||
background-color: #121212;
|
||||
border-color: #444;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .label-checkbox input[type="checkbox"]:checked {
|
||||
background-color: #c0392b;
|
||||
border-color: #c0392b;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #mensagem-erro {
|
||||
background: rgba(254, 226, 226, 0.1);
|
||||
border-color: rgba(252, 165, 165, 0.2);
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #mensagem-sucesso {
|
||||
background: rgba(209, 250, 229, 0.1);
|
||||
border-color: rgba(110, 231, 183, 0.2);
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
[data-theme="dark"] a {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] a:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-toggle-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-toggle-btn:hover {
|
||||
background: #3498db;
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="imagens/icone.png">
|
||||
<link rel="stylesheet" href="cadastro.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<title>Cadastro - Focus Agenda</title>
|
||||
<script src="theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="topo">
|
||||
<h1 id="textotop">Focus Agenda</h1>
|
||||
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro"><img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema"></button>
|
||||
</div>
|
||||
|
||||
<div id="log">
|
||||
<h1 class="mens">Crie Sua Conta</h1>
|
||||
|
||||
<div id="mensagem-erro" role="alert"></div>
|
||||
<div id="mensagem-sucesso" role="status"></div>
|
||||
|
||||
<form id="cadastroForm" novalidate>
|
||||
<div class="campo">
|
||||
<label for="emailid">Email</label>
|
||||
<input type="email" placeholder="Digite seu email" id="emailid" autocomplete="email" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="nomeid">Nome</label>
|
||||
<input type="text" placeholder="Seu nome completo" id="nomeid" autocomplete="name" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="cursoid">Curso</label>
|
||||
<input type="text" placeholder="Ex: Tecnico em Informatica" id="cursoid" required>
|
||||
</div>
|
||||
|
||||
<div class="linha-dupla">
|
||||
<div class="campo">
|
||||
<label for="periodoid">Periodo</label>
|
||||
<select id="periodoid" required>
|
||||
<option value="">Selecione</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="senhaid">Senha</label>
|
||||
<input type="password" placeholder="Minimo 6 caracteres" id="senhaid" autocomplete="new-password" required minlength="6">
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="csenhaid">Confirmar Senha</label>
|
||||
<input type="password" placeholder="Confirme sua senha" id="csenhaid" autocomplete="new-password" required>
|
||||
</div>
|
||||
|
||||
<div class="campo campo-consentimento">
|
||||
<label class="label-checkbox">
|
||||
<input type="checkbox" id="consentimentoId" required>
|
||||
<span>
|
||||
Li e concordo com a
|
||||
<a href="politica-privacidade.html" target="_blank">Politica de Privacidade</a>
|
||||
e autorizo o tratamento dos meus dados pessoais para uso do FocusAgenda,
|
||||
conforme a LGPD (Lei n 13.709/2018).
|
||||
</span>
|
||||
</label>
|
||||
<span class="erro-inline" id="erroConsentimento" style="display:none; color:#c0392b; font-size:12px;">
|
||||
Voce precisa aceitar os termos para continuar.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="logbtn">Cadastrar</button>
|
||||
</form>
|
||||
|
||||
<p class="mens"><a href="login.html">Ja tem uma conta?</a></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const form = document.getElementById('cadastroForm');
|
||||
const erroEl = document.getElementById('mensagem-erro');
|
||||
const sucessoEl = document.getElementById('mensagem-sucesso');
|
||||
const btn = document.getElementById('logbtn');
|
||||
|
||||
function mostrarErro(msg) {
|
||||
erroEl.textContent = msg;
|
||||
erroEl.style.display = 'block';
|
||||
sucessoEl.style.display = 'none';
|
||||
}
|
||||
|
||||
function mostrarSucesso(msg) {
|
||||
sucessoEl.textContent = msg;
|
||||
sucessoEl.style.display = 'block';
|
||||
erroEl.style.display = 'none';
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
erroEl.style.display = 'none';
|
||||
sucessoEl.style.display = 'none';
|
||||
|
||||
const nome = document.getElementById('nomeid').value.trim();
|
||||
const email = document.getElementById('emailid').value.trim();
|
||||
const curso = document.getElementById('cursoid').value.trim();
|
||||
const periodo = parseInt(document.getElementById('periodoid').value, 10);
|
||||
const senha = document.getElementById('senhaid').value;
|
||||
const csenha = document.getElementById('csenhaid').value;
|
||||
const consentimento = document.getElementById('consentimentoId').checked;
|
||||
|
||||
if (!nome || !email || !curso || !periodo || !senha) {
|
||||
mostrarErro('Preencha todos os campos.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (senha.length < 6) {
|
||||
mostrarErro('A senha deve ter pelo menos 6 caracteres.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (senha !== csenha) {
|
||||
mostrarErro('As senhas nao conferem.');
|
||||
document.getElementById('csenhaid').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!consentimento) {
|
||||
document.getElementById('erroConsentimento').style.display = 'block';
|
||||
mostrarErro('Voce precisa aceitar os termos para continuar.');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Cadastrando...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/estudantes/cadastro', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nome, email, senha, curso, periodo, consentimentoLgpd: true })
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
if (typeof json.data === 'object' && json.data !== null) {
|
||||
const msgs = Object.values(json.data).join('; ');
|
||||
mostrarErro(msgs);
|
||||
} else {
|
||||
mostrarErro(json.message || 'Erro ao cadastrar.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mostrarSucesso('Conta criada! Redirecionando para o login...');
|
||||
setTimeout(() => window.location.href = 'login.html', 2000);
|
||||
} catch (err) {
|
||||
mostrarErro('Erro de conexao. Verifique se o servidor esta rodando.');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Cadastrar';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,519 +0,0 @@
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
background: linear-gradient(to right, #c0392b, #114455);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#title { color: #fff; padding-left: 20px; font-size: 28px; }
|
||||
|
||||
#barraesquerda {
|
||||
position: fixed;
|
||||
top: 50px; left: 0;
|
||||
width: 280px;
|
||||
height: calc(100vh - 50px);
|
||||
background: #c0392b;
|
||||
padding: 15px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: 'Inter', sans-serif;
|
||||
overflow-y: auto;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#calendario { margin-top: 10px; }
|
||||
.calendariotop { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||
#mes { font-size: 16px; font-weight: 600; }
|
||||
#calendarseta { display: flex; gap: 10px; }
|
||||
#calendarseta button { background: transparent; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 2px 8px; transition: transform 0.2s; }
|
||||
#calendarseta button:hover { transform: scale(1.2); }
|
||||
|
||||
.calendariodia { width: 100%; border-collapse: collapse; }
|
||||
.calendariodia th { font-size: 10px; opacity: 0.8; padding: 3px; text-align: center; }
|
||||
.calendariodia td { text-align: center; padding: 4px; font-size: 11px; border-radius: 50%; cursor: pointer; transition: background 0.15s; }
|
||||
.calendariodia td:hover { background: rgba(255,255,255,0.2); }
|
||||
.calendariodia td.outromes { opacity: 0.4; }
|
||||
.calendariodia td.today { background: #fff; color: #c0392b; font-weight: 700; border-radius: 50%; }
|
||||
.calendariodia td.selecionado { background: rgba(255,255,255,0.35); border-radius: 50%; }
|
||||
|
||||
#agenda { margin-top: 18px; }
|
||||
.agenda-header { font-size: 12px; font-weight: 700; opacity: 0.8; margin-bottom: 8px; letter-spacing: 0.5px; }
|
||||
.agenda-empty { font-size: 12px; opacity: 0.7; font-style: italic; }
|
||||
.evento { background: rgba(255,255,255,0.15); border-radius: 8px; padding: 8px 10px; margin-bottom: 8px; cursor: pointer; transition: background 0.15s; }
|
||||
.evento:hover { background: rgba(255,255,255,0.25); }
|
||||
.evento .hora { font-size: 11px; opacity: 0.8; }
|
||||
.evento .titulo { font-size: 13px; font-weight: 600; }
|
||||
|
||||
#feriados { margin-top: 16px; }
|
||||
.feriados-header { font-size: 12px; font-weight: 700; opacity: 0.8; margin-bottom: 8px; letter-spacing: 0.5px; }
|
||||
.feriado { display: flex; align-items: center; gap: 8px; font-size: 12px; margin-bottom: 4px; }
|
||||
.dot { width: 8px; height: 8px; background: #fff; border-radius: 50%; flex-shrink: 0; }
|
||||
|
||||
.main {
|
||||
margin-left: 280px;
|
||||
margin-top: 50px;
|
||||
padding: 24px;
|
||||
min-height: calc(100vh - 50px);
|
||||
}
|
||||
|
||||
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.topbar h1 { font-size: 24px; color: #1f2937; }
|
||||
|
||||
.user-area { display: flex; align-items: center; gap: 14px; }
|
||||
.icone-img { width: 28px; height: 28px; cursor: pointer; opacity: 0.7; transition: opacity 0.2s; }
|
||||
.icone-img:hover { opacity: 1; }
|
||||
|
||||
.perfil { display: flex; align-items: center; gap: 10px; }
|
||||
.avatar { width: 38px; height: 38px; border-radius: 50%; background: linear-gradient(135deg, #c0392b, #114455); }
|
||||
.info { display: flex; flex-direction: column; }
|
||||
.nome { font-size: 14px; font-weight: 600; color: #1f2937; }
|
||||
.cargo { font-size: 12px; color: #6b7280; }
|
||||
|
||||
#btnLogout {
|
||||
background: transparent;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
color: #6b7280;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
#btnLogout:hover { background: #fee2e2; border-color: #c0392b; color: #c0392b; }
|
||||
|
||||
#btnConfig {
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
#btnConfig:hover { opacity: 1; transform: scale(1.15); }
|
||||
.config-icon { width: 22px; height: 22px; object-fit: contain; }
|
||||
|
||||
.theme-toggle-btn {
|
||||
background: transparent;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
padding: 4px;
|
||||
}
|
||||
.theme-toggle-btn:hover { background: #f3f4f6; transform: scale(1.1); }
|
||||
.theme-icon { width: 100%; height: 100%; object-fit: contain; }
|
||||
|
||||
.calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 10px; }
|
||||
|
||||
.mes-nav { display: flex; align-items: center; gap: 12px; }
|
||||
.seta { background: transparent; border: 1px solid #d1d5db; border-radius: 6px; width: 32px; height: 32px; font-size: 20px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.15s; }
|
||||
.seta:hover { background: #f3f4f6; }
|
||||
.titulo-mes { font-size: 18px; font-weight: 600; color: #1f2937; min-width: 200px; text-align: center; }
|
||||
|
||||
.view-switch { display: flex; gap: 4px; background: #f3f4f6; padding: 4px; border-radius: 8px; }
|
||||
.view-switch button { border: none; background: transparent; padding: 6px 14px; border-radius: 6px; font-size: 14px; cursor: pointer; color: #6b7280; transition: all 0.2s; font-family: inherit; }
|
||||
.view-switch button.active { background: #fff; color: #1f2937; font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }
|
||||
|
||||
#btnNovoEvento {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
#btnNovoEvento:hover { background: #a03224; }
|
||||
#btnGerenciarDisciplinas {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
font-family: inherit;
|
||||
}
|
||||
#btnGerenciarDisciplinas:hover { background: #a03224; }
|
||||
|
||||
.calendar-area { background: #fff; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.08); overflow: hidden; }
|
||||
|
||||
.month-view { display: grid; grid-template-columns: repeat(7, 1fr); }
|
||||
.dia-semana { text-align: center; padding: 10px; font-size: 12px; font-weight: 600; color: #6b7280; background: #f9fafb; border-bottom: 1px solid #e5e7eb; }
|
||||
.dia-box { min-height: 100px; padding: 8px; border-right: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background 0.1s; }
|
||||
.dia-box:hover { background: #fef2f2; }
|
||||
.dia-box:nth-child(7n) { border-right: none; }
|
||||
.dia-box.outro-mes { background: #fafafa; color: #d1d5db; }
|
||||
.dia-box.outro-mes .num-dia { color: #d1d5db; }
|
||||
.num-dia { font-size: 13px; font-weight: 500; color: #374151; margin-bottom: 4px; }
|
||||
.dia-box.today .num-dia { background: #c0392b; color: #fff; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; }
|
||||
.dia-box.selecionado { background: #fef2f2; }
|
||||
.evento-mini { font-size: 10px; background: #c0392b; color: #fff; border-radius: 3px; padding: 1px 4px; margin-top: 2px; truncate: clip; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.evento-mini.verde { background: #16a34a; }
|
||||
.evento-mini.azul { background: #1d4ed8; }
|
||||
.evento-mini.amarelo { background: #d97706; }
|
||||
.mais-eventos { font-size: 10px; color: #6b7280; margin-top: 2px; cursor: pointer; }
|
||||
|
||||
.week-view { display: grid; grid-template-columns: repeat(7, 1fr); }
|
||||
.week-col { border-right: 1px solid #e5e7eb; padding: 10px 8px; min-height: 300px; }
|
||||
.week-col:last-child { border-right: none; }
|
||||
.week-col.today { background: #fef9f9; }
|
||||
.week-col-head { font-size: 13px; font-weight: 600; color: #6b7280; margin-bottom: 6px; }
|
||||
.week-col-date { display: block; font-size: 11px; font-weight: 400; color: #9ca3af; }
|
||||
.week-events { display: flex; flex-direction: column; gap: 6px; }
|
||||
.week-empty { font-size: 11px; color: #d1d5db; font-style: italic; }
|
||||
|
||||
.day-panel { padding: 20px; }
|
||||
.day-panel-header { font-size: 16px; font-weight: 600; color: #374151; margin-bottom: 16px; }
|
||||
.day-events { display: flex; flex-direction: column; gap: 10px; }
|
||||
.day-empty { color: #9ca3af; font-style: italic; font-size: 14px; }
|
||||
|
||||
.calendar-event { border-radius: 6px; padding: 8px 10px; background: #fee2e2; border-left: 4px solid #c0392b; }
|
||||
.calendar-event.verde { background: #d1fae5; border-color: #16a34a; }
|
||||
.calendar-event.azul { background: #dbeafe; border-color: #1d4ed8; }
|
||||
.calendar-event.amarelo { background: #fef3c7; border-color: #d97706; }
|
||||
.calendar-event.rosa { background: #fce7f3; border-color: #db2777; }
|
||||
.calendar-event-hora { font-size: 11px; color: #6b7280; }
|
||||
.calendar-event-titulo { font-size: 13px; font-weight: 600; color: #1f2937; }
|
||||
|
||||
.loading { display: flex; align-items: center; justify-content: center; padding: 40px; color: #9ca3af; gap: 10px; font-size: 14px; }
|
||||
.spinner { width: 20px; height: 20px; border: 2px solid #e5e7eb; border-top-color: #c0392b; border-radius: 50%; animation: spin 0.7s linear infinite; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.45);
|
||||
z-index: 100;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.modal-overlay.aberto { display: flex; }
|
||||
|
||||
.modal {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
margin: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-titulo { font-size: 18px; font-weight: 700; color: #1f2937; margin-bottom: 20px; }
|
||||
|
||||
.modal-campo { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
|
||||
.modal-campo label { font-size: 13px; font-weight: 600; color: #374151; }
|
||||
.modal-campo input,
|
||||
.modal-campo select,
|
||||
.modal-campo textarea {
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s;
|
||||
background: #fff;
|
||||
}
|
||||
.modal-campo input:focus,
|
||||
.modal-campo select:focus,
|
||||
.modal-campo textarea:focus {
|
||||
outline: none;
|
||||
border-color: #c0392b;
|
||||
}
|
||||
.modal-campo textarea { resize: vertical; min-height: 70px; }
|
||||
|
||||
.modal-linha { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
|
||||
.modal-acoes { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
||||
|
||||
.btn-secundario {
|
||||
background: transparent;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 9px 18px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: #6b7280;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-secundario:hover { background: #f3f4f6; }
|
||||
|
||||
.btn-primario {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 9px 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.btn-primario:hover { background: #a03224; }
|
||||
.btn-primario:disabled { background: #ccc; cursor: not-allowed; }
|
||||
|
||||
.btn-perigo {
|
||||
background: transparent;
|
||||
border: 1px solid #fca5a5;
|
||||
color: #c0392b;
|
||||
border-radius: 8px;
|
||||
padding: 9px 18px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s;
|
||||
margin-right: auto;
|
||||
}
|
||||
.btn-perigo:hover { background: #fee2e2; }
|
||||
|
||||
#toast {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
background: #1f2937;
|
||||
color: #fff;
|
||||
padding: 12px 18px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
z-index: 200;
|
||||
transform: translateY(80px);
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
max-width: 320px;
|
||||
}
|
||||
#toast.visivel { transform: translateY(0); opacity: 1; }
|
||||
#toast.sucesso { background: #065f46; }
|
||||
#toast.erro { background: #b91c1c; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#barraesquerda { display: none; }
|
||||
.main { margin-left: 0; }
|
||||
.month-view { font-size: 12px; }
|
||||
.dia-box { min-height: 60px; padding: 4px; }
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--bg-primary: #121212;
|
||||
--bg-secondary: #1e1e1e;
|
||||
--bg-card: #2a2a2a;
|
||||
--bg-input: #1a1a1a;
|
||||
--text-primary: #e8e8e8;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-muted: #666;
|
||||
--border-color: #333;
|
||||
--border-light: #282828;
|
||||
--shadow: 0 1px 4px rgba(0,0,0,0.5);
|
||||
--hover-bg: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
[data-theme="dark"] body {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .topbar h1 { color: var(--text-primary); }
|
||||
[data-theme="dark"] .nome { color: var(--text-primary); }
|
||||
[data-theme="dark"] .cargo { color: var(--text-secondary); }
|
||||
|
||||
[data-theme="dark"] #btnLogout {
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
[data-theme="dark"] #btnLogout:hover { background: rgba(192,57,43,0.2); border-color: #c0392b; color: #e74c3c; }
|
||||
|
||||
[data-theme="dark"] .calendar-header .seta {
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
[data-theme="dark"] .calendar-header .seta:hover { background: var(--hover-bg); }
|
||||
[data-theme="dark"] .titulo-mes { color: var(--text-primary); }
|
||||
|
||||
[data-theme="dark"] .view-switch { background: var(--bg-secondary); }
|
||||
[data-theme="dark"] .view-switch button { color: var(--text-secondary); }
|
||||
[data-theme="dark"] .view-switch button.active {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .calendar-area {
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .dia-semana {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-secondary);
|
||||
border-bottom-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .dia-box {
|
||||
border-right-color: var(--border-light);
|
||||
border-bottom-color: var(--border-light);
|
||||
}
|
||||
[data-theme="dark"] .dia-box:hover { background: rgba(192,57,43,0.1); }
|
||||
[data-theme="dark"] .dia-box.outro-mes { background: var(--bg-primary); color: #444; }
|
||||
[data-theme="dark"] .dia-box.outro-mes .num-dia { color: #444; }
|
||||
[data-theme="dark"] .num-dia { color: var(--text-primary); }
|
||||
[data-theme="dark"] .dia-box.selecionado { background: rgba(192,57,43,0.15); }
|
||||
|
||||
[data-theme="dark"] .week-col { border-right-color: var(--border-color); }
|
||||
[data-theme="dark"] .week-col.today { background: rgba(192,57,43,0.08); }
|
||||
[data-theme="dark"] .week-col-head { color: var(--text-secondary); }
|
||||
[data-theme="dark"] .week-col-date { color: var(--text-muted); }
|
||||
[data-theme="dark"] .week-empty { color: #444; }
|
||||
|
||||
[data-theme="dark"] .day-panel-header { color: var(--text-primary); }
|
||||
[data-theme="dark"] .day-empty { color: var(--text-muted); }
|
||||
|
||||
[data-theme="dark"] .calendar-event { background: rgba(192,57,43,0.2); }
|
||||
[data-theme="dark"] .calendar-event.verde { background: rgba(22,163,74,0.2); }
|
||||
[data-theme="dark"] .calendar-event.azul { background: rgba(29,78,216,0.2); }
|
||||
[data-theme="dark"] .calendar-event.amarelo { background: rgba(217,119,6,0.2); }
|
||||
[data-theme="dark"] .calendar-event.rosa { background: rgba(219,39,119,0.2); }
|
||||
[data-theme="dark"] .calendar-event-hora { color: var(--text-secondary); }
|
||||
[data-theme="dark"] .calendar-event-titulo { color: var(--text-primary); }
|
||||
|
||||
[data-theme="dark"] .loading { color: var(--text-muted); }
|
||||
[data-theme="dark"] .spinner { border-color: var(--border-color); border-top-color: #c0392b; }
|
||||
|
||||
[data-theme="dark"] .modal {
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
||||
}
|
||||
[data-theme="dark"] .modal-titulo { color: var(--text-primary); }
|
||||
[data-theme="dark"] .modal-campo label { color: var(--text-primary); }
|
||||
[data-theme="dark"] .modal-campo input,
|
||||
[data-theme="dark"] .modal-campo select,
|
||||
[data-theme="dark"] .modal-campo textarea {
|
||||
background: var(--bg-input);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .btn-secundario {
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
[data-theme="dark"] .btn-secundario:hover { background: var(--hover-bg); }
|
||||
|
||||
[data-theme="dark"] .btn-perigo {
|
||||
border-color: rgba(252,165,165,0.3);
|
||||
color: #e74c3c;
|
||||
}
|
||||
[data-theme="dark"] .btn-perigo:hover { background: rgba(192,57,43,0.2); }
|
||||
|
||||
[data-theme="dark"] #toast { background: #2a2a2a; }
|
||||
|
||||
[data-theme="dark"] .mais-eventos { color: var(--text-secondary); }
|
||||
|
||||
[data-theme="dark"] #btnGerenciarDisciplinas {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
}
|
||||
[data-theme="dark"] #btnGerenciarDisciplinas:hover {
|
||||
background: #a03224;
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -1,333 +0,0 @@
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: 'Poppins', 'Trebuchet MS', Arial, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#textotop {
|
||||
padding-left: 20px;
|
||||
font-size: clamp(22px, 5vw, 38px);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#voltar {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(255,255,255,0.15);
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
color: #fff;
|
||||
padding: 6px 16px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
#voltar:hover { background: rgba(255,255,255,0.3); }
|
||||
|
||||
.theme-toggle-btn {
|
||||
position: absolute;
|
||||
right: 140px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(255,255,255,0.15);
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover { background: rgba(255,255,255,0.3); transform: translateY(-50%) scale(1.1); }
|
||||
.theme-icon { width: 100%; height: 100%; object-fit: contain; }
|
||||
|
||||
#log {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.titulo-pagina { text-align: center; color: #1f2937; }
|
||||
.subtitulo { text-align: center; color: #6b7280; font-weight: 400; font-size: 15px; }
|
||||
|
||||
.campo { display: flex; flex-direction: column; gap: 8px; }
|
||||
label { font-weight: 700; color: #1f2937; }
|
||||
|
||||
input, select {
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #c7c7c7;
|
||||
border-radius: 6px;
|
||||
font-family: inherit;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
background: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
form { display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
.linha-dupla { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
|
||||
#logbtn {
|
||||
align-self: center;
|
||||
width: 50%;
|
||||
padding: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background-color: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#logbtn:hover { background-color: #a03224; }
|
||||
#logbtn:disabled { background-color: #ccc; cursor: not-allowed; }
|
||||
|
||||
.secao {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.secao-titulo {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.secao-descricao {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.acoes-lgpd {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.acao-lgpd {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 16px;
|
||||
background: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.acao-lgpd strong {
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.acao-lgpd p {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.acao-lgpd button,
|
||||
.btn-link-politica {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #c0392b;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: #c0392b;
|
||||
font-size: 13px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.acao-lgpd button:hover,
|
||||
.btn-link-politica:hover {
|
||||
background: #c0392b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#btnSenha {
|
||||
align-self: center;
|
||||
width: 60%;
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
background-color: #114455;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#btnSenha:hover { background-color: #0d3644; }
|
||||
#btnSenha:disabled { background-color: #ccc; cursor: not-allowed; }
|
||||
|
||||
.zona-perigo {
|
||||
background: #fff;
|
||||
border: 1px solid #fca5a5;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.zona-perigo .secao-titulo {
|
||||
color: #b91c1c;
|
||||
border-bottom-color: #fee2e2;
|
||||
}
|
||||
|
||||
.zona-perigo p {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#btnExcluirConta {
|
||||
align-self: center;
|
||||
width: 60%;
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
background-color: transparent;
|
||||
color: #b91c1c;
|
||||
border: 2px solid #b91c1c;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#btnExcluirConta:hover { background-color: #fee2e2; }
|
||||
#btnExcluirConta:disabled { background-color: #ccc; border-color: #ccc; color: #fff; cursor: not-allowed; }
|
||||
|
||||
#mensagem-erro {
|
||||
background: #fee2e2;
|
||||
border: 1px solid #fca5a5;
|
||||
color: #b91c1c;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mensagem-sucesso {
|
||||
background: #d1fae5;
|
||||
border: 1px solid #6ee7b7;
|
||||
color: #065f46;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] body {
|
||||
background: #121212;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .titulo-pagina { color: #e8e8e8; }
|
||||
[data-theme="dark"] .subtitulo { color: #a0a0a0; }
|
||||
|
||||
[data-theme="dark"] .secao {
|
||||
background: #1e1e1e;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .secao-titulo {
|
||||
color: #e8e8e8;
|
||||
border-bottom-color: #333;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .secao-descricao { color: #a0a0a0; }
|
||||
|
||||
[data-theme="dark"] label { color: #e8e8e8; }
|
||||
|
||||
[data-theme="dark"] input,
|
||||
[data-theme="dark"] select {
|
||||
background: #1a1a1a;
|
||||
border-color: #333;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] input:disabled {
|
||||
background: #161616;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .zona-perigo {
|
||||
background: #1e1e1e;
|
||||
border-color: rgba(252,165,165,0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .zona-perigo p { color: #a0a0a0; }
|
||||
|
||||
[data-theme="dark"] #mensagem-erro {
|
||||
background: rgba(254,226,226,0.1);
|
||||
border-color: rgba(252,165,165,0.2);
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #mensagem-sucesso {
|
||||
background: rgba(209,250,229,0.1);
|
||||
border-color: rgba(110,231,183,0.2);
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
[data-theme="dark"] a { color: #e8e8e8; }
|
||||
|
||||
[data-theme="dark"] .acao-lgpd {
|
||||
background: #1a1a1a;
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .acao-lgpd strong { color: #e8e8e8; }
|
||||
[data-theme="dark"] .acao-lgpd p { color: #a0a0a0; }
|
||||
@@ -1,323 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="imagens/icone.png">
|
||||
<link rel="stylesheet" href="configuracoes.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<title>Configuracoes - Focus Agenda</title>
|
||||
<script src="theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="topo">
|
||||
<h1 id="textotop">Focus Agenda</h1>
|
||||
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro"><img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema"></button>
|
||||
<button id="voltar" onclick="window.location.href='calendario.html'">Voltar</button>
|
||||
</div>
|
||||
|
||||
<div id="log">
|
||||
<h1 class="titulo-pagina">Configuracoes</h1>
|
||||
<p class="subtitulo">Gerencie seus dados e preferencias</p>
|
||||
|
||||
<div id="mensagem-erro" role="alert"></div>
|
||||
<div id="mensagem-sucesso" role="status"></div>
|
||||
|
||||
<div class="secao">
|
||||
<div class="secao-titulo">Dados Pessoais</div>
|
||||
<form id="dadosForm" novalidate>
|
||||
<div class="campo">
|
||||
<label for="nomeid">Nome</label>
|
||||
<input type="text" placeholder="Seu nome completo" id="nomeid" autocomplete="name" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="emailid">Email</label>
|
||||
<input type="email" placeholder="Seu email" id="emailid" autocomplete="email" required disabled>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="cursoid">Curso</label>
|
||||
<input type="text" placeholder="Ex: Tecnico em Informatica" id="cursoid" required>
|
||||
</div>
|
||||
|
||||
<div class="linha-dupla">
|
||||
<div class="campo">
|
||||
<label for="periodoid">Periodo</label>
|
||||
<select id="periodoid" required>
|
||||
<option value="">Selecione</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="logbtn">Salvar Alteracoes</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="secao">
|
||||
<div class="secao-titulo">Alterar Senha</div>
|
||||
<form id="senhaForm" novalidate>
|
||||
<div class="campo">
|
||||
<label for="senhaAtualid">Senha Atual</label>
|
||||
<input type="password" placeholder="Digite sua senha atual" id="senhaAtualid" autocomplete="current-password" required>
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="novaSenhaid">Nova Senha</label>
|
||||
<input type="password" placeholder="Minimo 6 caracteres" id="novaSenhaid" autocomplete="new-password" required minlength="6">
|
||||
</div>
|
||||
|
||||
<div class="campo">
|
||||
<label for="confirmaNovaid">Confirmar Nova Senha</label>
|
||||
<input type="password" placeholder="Confirme a nova senha" id="confirmaNovaid" autocomplete="new-password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="btnSenha">Alterar Senha</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="secao">
|
||||
<div class="secao-titulo">Seus Dados (LGPD)</div>
|
||||
<p class="secao-descricao">
|
||||
Conforme a Lei Geral de Protecao de Dados (Lei n 13.709/2018),
|
||||
voce tem direito de acessar, exportar e excluir seus dados pessoais.
|
||||
</p>
|
||||
<div class="acoes-lgpd">
|
||||
<div class="acao-lgpd">
|
||||
<div>
|
||||
<strong>Exportar meus dados</strong>
|
||||
<p>Baixe um arquivo JSON com todas as suas tarefas, eventos e disciplinas.</p>
|
||||
</div>
|
||||
<button type="button" id="btnExportarDados">Exportar JSON</button>
|
||||
</div>
|
||||
<div class="acao-lgpd">
|
||||
<div>
|
||||
<strong>Politica de Privacidade</strong>
|
||||
<p>Veja como seus dados sao tratados e quais sao seus direitos.</p>
|
||||
</div>
|
||||
<a href="politica-privacidade.html" class="btn-link-politica">Ver politica</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zona-perigo">
|
||||
<div class="secao-titulo">Zona de Perigo</div>
|
||||
<p>
|
||||
Ao excluir sua conta, exercendo o direito previsto no Art. 18, VI da LGPD,
|
||||
todos os seus dados pessoais (tarefas, eventos, disciplinas e notificacoes)
|
||||
serao removidos <strong>permanentemente e imediatamente</strong> dos nossos servidores.
|
||||
Esta acao e irreversivel.
|
||||
</p>
|
||||
<button type="button" id="btnExcluirConta">Excluir Minha Conta</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
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) {}
|
||||
|
||||
const erroEl = document.getElementById('mensagem-erro');
|
||||
const sucessoEl = document.getElementById('mensagem-sucesso');
|
||||
|
||||
function mostrarErro(msg) {
|
||||
erroEl.textContent = msg;
|
||||
erroEl.style.display = 'block';
|
||||
sucessoEl.style.display = 'none';
|
||||
erroEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
function mostrarSucesso(msg) {
|
||||
sucessoEl.textContent = msg;
|
||||
sucessoEl.style.display = 'block';
|
||||
erroEl.style.display = 'none';
|
||||
sucessoEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
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);
|
||||
const json = await res.json().catch(() => ({}));
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(json.message || 'Erro na requisicao');
|
||||
}
|
||||
return json.data;
|
||||
}
|
||||
|
||||
async function carregarUsuario() {
|
||||
try {
|
||||
const u = await api('GET', '/api/estudantes/me');
|
||||
usuario = u;
|
||||
localStorage.setItem('fa_user', JSON.stringify(u));
|
||||
|
||||
document.getElementById('nomeid').value = u.nome || '';
|
||||
document.getElementById('emailid').value = u.email || '';
|
||||
document.getElementById('cursoid').value = u.curso || '';
|
||||
document.getElementById('periodoid').value = u.periodo || '';
|
||||
} catch(e) {
|
||||
mostrarErro('Erro ao carregar dados: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('dadosForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
erroEl.style.display = 'none';
|
||||
sucessoEl.style.display = 'none';
|
||||
|
||||
const nome = document.getElementById('nomeid').value.trim();
|
||||
const curso = document.getElementById('cursoid').value.trim();
|
||||
const periodo = parseInt(document.getElementById('periodoid').value, 10);
|
||||
|
||||
if (!nome || !curso || !periodo) {
|
||||
mostrarErro('Preencha todos os campos.');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('logbtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Salvando...';
|
||||
|
||||
try {
|
||||
const atualizado = await api('PUT', '/api/estudantes/me', { nome, curso, periodo });
|
||||
localStorage.setItem('fa_user', JSON.stringify(atualizado));
|
||||
mostrarSucesso('Dados atualizados com sucesso!');
|
||||
} catch(err) {
|
||||
mostrarErro(err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Salvar Alteracoes';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('senhaForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
erroEl.style.display = 'none';
|
||||
sucessoEl.style.display = 'none';
|
||||
|
||||
const senhaAtual = document.getElementById('senhaAtualid').value;
|
||||
const novaSenha = document.getElementById('novaSenhaid').value;
|
||||
const confirmaNova = document.getElementById('confirmaNovaid').value;
|
||||
|
||||
if (!senhaAtual || !novaSenha || !confirmaNova) {
|
||||
mostrarErro('Preencha todos os campos.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (novaSenha.length < 6) {
|
||||
mostrarErro('A nova senha deve ter pelo menos 6 caracteres.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (novaSenha !== confirmaNova) {
|
||||
mostrarErro('As senhas nao conferem.');
|
||||
document.getElementById('confirmaNovaid').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (senhaAtual === novaSenha) {
|
||||
mostrarErro('A nova senha deve ser diferente da atual.');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnSenha');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Alterando...';
|
||||
|
||||
try {
|
||||
await api('PUT', '/api/estudantes/senha', { senhaAtual, novaSenha });
|
||||
mostrarSucesso('Senha alterada com sucesso!');
|
||||
document.getElementById('senhaAtualid').value = '';
|
||||
document.getElementById('novaSenhaid').value = '';
|
||||
document.getElementById('confirmaNovaid').value = '';
|
||||
} catch(err) {
|
||||
mostrarErro(err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Alterar Senha';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btnExportarDados').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('btnExportarDados');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Gerando...';
|
||||
try {
|
||||
const res = await fetch('/api/estudantes/me/dados', {
|
||||
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('fa_token') }
|
||||
});
|
||||
if (res.status === 401) {
|
||||
localStorage.clear();
|
||||
window.location.href = '/login.html?sessao=expirada';
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
const blob = new Blob([JSON.stringify(data.data, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'focusagenda-meus-dados.json';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (err) {
|
||||
alert('Erro ao exportar dados: ' + err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Exportar JSON';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btnExcluirConta').addEventListener('click', async () => {
|
||||
if (!confirm('Tem certeza que deseja excluir sua conta? Esta acao e irreversivel!')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmacao = prompt('Digite "EXCLUIR" para confirmar a exclusao da sua conta:');
|
||||
if (confirmacao !== 'EXCLUIR') {
|
||||
mostrarErro('Exclusao cancelada.');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btnExcluirConta');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Excluindo...';
|
||||
|
||||
try {
|
||||
await api('DELETE', '/api/estudantes/me');
|
||||
localStorage.removeItem('fa_token');
|
||||
localStorage.removeItem('fa_user');
|
||||
mostrarSucesso('Conta excluida. Redirecionando...');
|
||||
setTimeout(() => window.location.href = 'login.html', 2000);
|
||||
} catch(err) {
|
||||
mostrarErro(err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Excluir Minha Conta';
|
||||
}
|
||||
});
|
||||
|
||||
carregarUsuario();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.32031 11.6835C3.32031 16.6541 7.34975 20.6835 12.3203 20.6835C16.1075 20.6835 19.3483 18.3443 20.6768 15.032C19.6402 15.4486 18.5059 15.6834 17.3203 15.6834C12.3497 15.6834 8.32031 11.654 8.32031 6.68342C8.32031 5.50338 8.55165 4.36259 8.96453 3.32996C5.65605 4.66028 3.32031 7.89912 3.32031 11.6835Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 624 B |
|
Before Width: | Height: | Size: 8.6 KiB |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 3V4M12 20V21M4 12H3M6.31412 6.31412L5.5 5.5M17.6859 6.31412L18.5 5.5M6.31412 17.69L5.5 18.5001M17.6859 17.69L18.5 18.5001M21 12H20M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 568 B |
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0;url=login.html">
|
||||
<title>Focus Agenda</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>window.location.href='login.html';</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,244 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: 'Poppins', 'Trebuchet MS', Arial, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px 20px;
|
||||
color: #1f2937;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#textotop {
|
||||
padding-left: 20px;
|
||||
font-size: clamp(22px, 5vw, 30px);
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
transition: all 0.4s ease;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
background: #114455;
|
||||
transform: translateY(-50%) rotate(180deg) scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: brightness(0) invert(1);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#log {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
background: #ffffff;
|
||||
padding: 40px 32px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.mens {
|
||||
text-align: center;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.campo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
font-size: 15px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-family: inherit;
|
||||
background-color: #f9fafb;
|
||||
outline: none;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
input[type="email"]:hover,
|
||||
input[type="password"]:hover {
|
||||
border-color: #c0392b;
|
||||
}
|
||||
|
||||
input[type="email"]:focus,
|
||||
input[type="password"]:focus {
|
||||
border-color: #c0392b;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.1);
|
||||
}
|
||||
|
||||
#logbtn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
background-color: #c0392b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#logbtn:hover {
|
||||
background-color: #114455;
|
||||
box-shadow: 0 5px 15px rgba(17, 68, 85, 0.4);
|
||||
}
|
||||
|
||||
#logbtn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #c0392b;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #114455;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#mensagem-erro {
|
||||
background: #fee2e2;
|
||||
border: 1px solid #fca5a5;
|
||||
color: #b91c1c;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mensagem-sucesso {
|
||||
background: #d1fae5;
|
||||
border: 1px solid #6ee7b7;
|
||||
color: #065f46;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] body {
|
||||
background: #121212;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #log {
|
||||
background: #1e1e1e;
|
||||
border-color: #333;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mens {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] label {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] input,
|
||||
[data-theme="dark"] select {
|
||||
background: #1a1a1a;
|
||||
border-color: #333;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #mensagem-erro {
|
||||
background: rgba(254, 226, 226, 0.1);
|
||||
border-color: rgba(252, 165, 165, 0.2);
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #mensagem-sucesso {
|
||||
background: rgba(209, 250, 229, 0.1);
|
||||
border-color: rgba(110, 231, 183, 0.2);
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
[data-theme="dark"] a {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] a:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-toggle-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-toggle-btn:hover {
|
||||
background: #3498db;
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="imagens/icone.png">
|
||||
<link rel="stylesheet" href="login.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<title>Login – Focus Agenda</title>
|
||||
<script src="theme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="topo">
|
||||
<h1 id="textotop">Focus Agenda</h1>
|
||||
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro"><img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema"></button>
|
||||
</div>
|
||||
|
||||
<div id="log">
|
||||
<h1 class="mens">Bem-vindo!</h1>
|
||||
<h3 class="mens">Faca seu login</h3>
|
||||
|
||||
<div id="mensagem-erro" role="alert"></div>
|
||||
|
||||
<form id="loginForm" novalidate>
|
||||
<div class="campo">
|
||||
<label for="emailid">Email</label>
|
||||
<input type="email" placeholder="Digite seu email" id="emailid" autocomplete="email" required>
|
||||
</div>
|
||||
<div class="campo">
|
||||
<label for="senhaid">Senha</label>
|
||||
<input type="password" placeholder="Digite sua senha" id="senhaid" autocomplete="current-password" required>
|
||||
</div>
|
||||
<button type="submit" id="logbtn">Entrar</button>
|
||||
</form>
|
||||
|
||||
<p class="mens"><a href="cadastro.html">Cadastrar-se</a></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('sessao') === 'expirada') {
|
||||
localStorage.removeItem('fa_token');
|
||||
localStorage.removeItem('fa_user');
|
||||
const erroEl = document.getElementById('mensagem-erro');
|
||||
erroEl.textContent = 'Sua sessao expirou. Faca login novamente.';
|
||||
erroEl.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('fa_token');
|
||||
if (token && token.split('.').length === 3) {
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
if (payload.exp && Date.now() >= payload.exp * 1000) {
|
||||
localStorage.removeItem('fa_token');
|
||||
localStorage.removeItem('fa_user');
|
||||
} else {
|
||||
window.location.href = 'calendario.html';
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem('fa_token');
|
||||
localStorage.removeItem('fa_user');
|
||||
}
|
||||
} else if (token) {
|
||||
localStorage.removeItem('fa_token');
|
||||
localStorage.removeItem('fa_user');
|
||||
}
|
||||
})();
|
||||
|
||||
const form = document.getElementById('loginForm');
|
||||
const erroEl = document.getElementById('mensagem-erro');
|
||||
const btn = document.getElementById('logbtn');
|
||||
|
||||
function mostrarErro(msg) {
|
||||
erroEl.textContent = msg;
|
||||
erroEl.style.display = 'block';
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
erroEl.style.display = 'none';
|
||||
|
||||
const email = document.getElementById('emailid').value.trim();
|
||||
const senha = document.getElementById('senhaid').value;
|
||||
|
||||
if (!email || !senha) {
|
||||
mostrarErro('Preencha todos os campos.');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Entrando...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/estudantes/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, senha })
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
mostrarErro(json.message || 'Email ou senha incorretos.');
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem('fa_token', json.data.token);
|
||||
localStorage.setItem('fa_user', JSON.stringify(json.data.estudante));
|
||||
|
||||
window.location.href = 'calendario.html';
|
||||
} catch (err) {
|
||||
mostrarErro('Erro de conexao. Verifique se o servidor esta rodando.');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Entrar';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,160 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
font-family: 'Poppins', 'Trebuchet MS', Arial, sans-serif;
|
||||
background: #f5f5f5;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #c0392b 47%, #7a4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#textotop {
|
||||
padding-left: 20px;
|
||||
font-size: clamp(22px, 5vw, 30px);
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
transition: all 0.4s ease;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
background: #114455;
|
||||
transform: translateY(-50%) rotate(180deg) scale(1.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: brightness(0) invert(1);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.politica-container {
|
||||
max-width: 720px;
|
||||
margin: 100px auto 40px;
|
||||
padding: 40px 32px;
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid #e5e7eb;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.politica-container h1 {
|
||||
font-size: 28px;
|
||||
color: #c0392b;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.politica-container h2 {
|
||||
font-size: 18px;
|
||||
color: #114455;
|
||||
margin-top: 28px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #f3f4f6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.politica-container p,
|
||||
.politica-container li {
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.politica-container ul {
|
||||
padding-left: 20px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.politica-container li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.voltar-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 24px;
|
||||
color: #c0392b;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.voltar-link:hover {
|
||||
color: #114455;
|
||||
text-decoration: underline;
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
|
||||
[data-theme="dark"] body {
|
||||
background: #121212;
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .politica-container {
|
||||
background: #1e1e1e;
|
||||
border-color: #333;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .politica-container h1 {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .politica-container h2 {
|
||||
color: #3498db;
|
||||
border-bottom-color: #333;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .politica-container p,
|
||||
[data-theme="dark"] .politica-container li {
|
||||
color: #d0d0d0;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .voltar-link {
|
||||
color: #e8e8e8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .voltar-link:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-toggle-btn:hover {
|
||||
background: #3498db;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="imagens/icone.png">
|
||||
<link rel="stylesheet" href="politica-privacidade.css">
|
||||
<title>Politica de Privacidade - Focus Agenda</title>
|
||||
<script src="theme.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="topo">
|
||||
<h1 id="textotop">Focus Agenda</h1>
|
||||
<button class="theme-toggle-btn" onclick="toggleTheme()" title="Tema Escuro">
|
||||
<img src="imagens/moon-svgrepo-com.svg" class="theme-icon" alt="Tema">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="politica-container">
|
||||
<a href="cadastro.html" class="voltar-link">← Voltar</a>
|
||||
<h1>Politica de Privacidade</h1>
|
||||
<p>Versao 1.0 - Vigente a partir de maio de 2026</p>
|
||||
|
||||
<h2>1. Quem somos</h2>
|
||||
<p>FocusAgenda e um sistema de agenda estudantil desenvolvido como Trabalho de Conclusao de Curso (TCC) no curso Tecnico em Desenvolvimento de Sistemas - ETEC Pedro D'Arcadia Neto.</p>
|
||||
|
||||
<h2>2. Quais dados coletamos e por que (Art. 9, LGPD)</h2>
|
||||
<ul>
|
||||
<li><strong>Nome completo:</strong> identificacao na plataforma</li>
|
||||
<li><strong>Endereco de email:</strong> autenticacao e comunicacao</li>
|
||||
<li><strong>Senha:</strong> armazenada com criptografia BCrypt (nunca em texto simples)</li>
|
||||
<li><strong>Curso e periodo:</strong> personalizao da experiencia</li>
|
||||
<li><strong>Tarefas, eventos e disciplinas:</strong> funcionalidade principal do servico</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Base legal para o tratamento (Art. 7, I, LGPD)</h2>
|
||||
<p>Consentimento livre, informado e inequivoco do titular. Ao criar uma conta no FocusAgenda, voce aceita explicitamente o tratamento dos seus dados pessoais conforme descrito nesta politica.</p>
|
||||
|
||||
<h2>4. Como protegemos seus dados (Art. 46, LGPD)</h2>
|
||||
<ul>
|
||||
<li>Senhas criptografadas com BCrypt</li>
|
||||
<li>Comunicacao protegida por HTTPS</li>
|
||||
<li>Autenticacao via JWT com expiracao de 24 horas</li>
|
||||
<li>Acesso restrito aos dados do proprio usuario autenticado</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Seus direitos como titular (Art. 18, LGPD)</h2>
|
||||
<ul>
|
||||
<li><strong>Acesso:</strong> visualize seus dados em Configuracoes</li>
|
||||
<li><strong>Portabilidade:</strong> exporte todos os seus dados em JSON via Configuracoes</li>
|
||||
<li><strong>Correcao:</strong> edite seus dados em Configuracoes > Dados Pessoais</li>
|
||||
<li><strong>Eliminacao:</strong> exclua sua conta em Configuracoes > Zona de Perigo (todos os dados sao removidos permanentemente e imediatamente)</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Retencao de dados</h2>
|
||||
<p>Os dados sao mantidos enquanto a conta estiver ativa. Apos a exclusao da conta, todos os dados sao removidos imediatamente e de forma irreversivel.</p>
|
||||
|
||||
<h2>7. Contato</h2>
|
||||
<p>Para duvidas sobre privacidade, entre em contato com a equipe do FocusAgenda.</p>
|
||||
|
||||
<h2>8. Versao e vigencia</h2>
|
||||
<p>Versao 1.0 - vigente a partir de maio de 2026.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,124 +0,0 @@
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: black;
|
||||
margin: 0;
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif
|
||||
}
|
||||
|
||||
label{
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
#log {
|
||||
width: 100% !important;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: #111;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #C0392B 47%, #7A4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
#textotop {
|
||||
padding-left: 20px;
|
||||
font-size: 38px;
|
||||
margin: 0;
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
}
|
||||
|
||||
#log {
|
||||
width: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
color: white;
|
||||
margin-top: 70px;
|
||||
}
|
||||
#campo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-self: center;
|
||||
}
|
||||
#menslog{
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#emailid, #senhaid {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#logbtn {
|
||||
align-self: center;
|
||||
width: 50%;
|
||||
padding: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background-color: #C0392B;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
#logbtn:hover {
|
||||
background-color: #A03224;
|
||||
}
|
||||
.mens {
|
||||
align-self: center;
|
||||
}
|
||||
#linkcada{
|
||||
color: #111;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
(function() {
|
||||
var saved = localStorage.getItem('fa_theme');
|
||||
var theme = saved || 'light';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
})();
|
||||
|
||||
function toggleTheme() {
|
||||
var current = document.documentElement.getAttribute('data-theme');
|
||||
var next = current === 'dark' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-theme', next);
|
||||
localStorage.setItem('fa_theme', next);
|
||||
updateThemeButtons();
|
||||
}
|
||||
|
||||
function updateThemeButtons() {
|
||||
var theme = document.documentElement.getAttribute('data-theme');
|
||||
var moonSrc = 'imagens/moon-svgrepo-com.svg';
|
||||
var sunSrc = 'imagens/sun-svgrepo-com.svg';
|
||||
|
||||
var icons = document.querySelectorAll('.theme-icon');
|
||||
for (var i = 0; i < icons.length; i++) {
|
||||
icons[i].src = theme === 'dark' ? sunSrc : moonSrc;
|
||||
icons[i].alt = theme === 'dark' ? 'Tema Claro' : 'Tema Escuro';
|
||||
}
|
||||
|
||||
var btns = document.querySelectorAll('.theme-toggle-btn');
|
||||
for (var j = 0; j < btns.length; j++) {
|
||||
btns[j].title = theme === 'dark' ? 'Tema Claro' : 'Tema Escuro';
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', updateThemeButtons);
|
||||
} else {
|
||||
updateThemeButtons();
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
async function apiFetch(url, options = {}) {
|
||||
const token = localStorage.getItem('fa_token');
|
||||
const headers = { 'Content-Type': 'application/json', ...(options.headers || {}) };
|
||||
if (token) headers['Authorization'] = 'Bearer ' + token;
|
||||
|
||||
const res = await fetch(url, { ...options, headers });
|
||||
|
||||
if (res.status === 401) {
|
||||
localStorage.clear();
|
||||
window.location.href = '/login.html?sessao=expirada';
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.message || 'Erro desconhecido');
|
||||
return data;
|
||||
}
|
||||
|
||||
function showToast(mensagem, tipo = 'info') {
|
||||
const existing = document.querySelector('.fa-toast');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const styleId = 'fa-toast-style';
|
||||
if (!document.getElementById(styleId)) {
|
||||
const style = document.createElement('style');
|
||||
style.id = styleId;
|
||||
style.textContent = `
|
||||
.fa-toast {
|
||||
position: fixed; bottom: 24px; right: 24px; z-index: 99999;
|
||||
padding: 12px 18px; border-radius: 8px; font-size: 14px;
|
||||
font-family: 'Poppins', sans-serif; max-width: 340px;
|
||||
animation: faSlideIn 0.3s ease forwards;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
.fa-toast.fa-toast-sai { animation: faSlideOut 0.3s ease forwards; }
|
||||
.fa-toast-sucesso { background: #114455; color: #fff; }
|
||||
.fa-toast-erro { background: #c0392b; color: #fff; }
|
||||
.fa-toast-info { background: #f5f5f5; color: #1f2937; border: 1px solid #c0392b; }
|
||||
@keyframes faSlideIn { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
||||
@keyframes faSlideOut { from { transform: translateY(0); opacity: 1; } to { transform: translateY(30px); opacity: 0; } }
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'fa-toast fa-toast-' + tipo;
|
||||
toast.textContent = mensagem;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.add('fa-toast-sai');
|
||||
toast.addEventListener('animationend', () => toast.remove());
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function setButtonLoading(btn, loading, textoOriginal) {
|
||||
if (loading) {
|
||||
btn.disabled = true;
|
||||
btn.dataset.textoOriginal = textoOriginal || btn.textContent;
|
||||
btn.textContent = 'Salvando...';
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
if (btn.dataset.textoOriginal) {
|
||||
btn.textContent = btn.dataset.textoOriginal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(function() {
|
||||
window.addEventListener('offline', () => {
|
||||
let banner = document.getElementById('fa-offline-banner');
|
||||
if (!banner) {
|
||||
banner = document.createElement('div');
|
||||
banner.id = 'fa-offline-banner';
|
||||
banner.textContent = 'Sem conexao — algumas funcoes podem nao funcionar';
|
||||
banner.style.cssText = 'position:fixed;top:0;left:0;width:100%;background:#c0392b;color:#fff;text-align:center;padding:10px;font-family:Poppins,sans-serif;font-size:14px;z-index:9999;';
|
||||
document.body.prepend(banner);
|
||||
}
|
||||
});
|
||||
window.addEventListener('online', () => {
|
||||
document.getElementById('fa-offline-banner')?.remove();
|
||||
});
|
||||
})();
|
||||
@@ -1,107 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.dto.RequisicaoCadastroDTO;
|
||||
import com.agendaestudantil.dto.RespostaEstudanteDTO;
|
||||
import com.agendaestudantil.dto.RequisicaoLoginDTO;
|
||||
import com.agendaestudantil.dto.RespostaLoginDTO;
|
||||
import com.agendaestudantil.entidade.Estudante;
|
||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||
import com.agendaestudantil.repositorio.EstudanteRepositorio;
|
||||
import com.agendaestudantil.utilitario.UtilJwt;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class EstudanteServicoTest {
|
||||
|
||||
@Mock
|
||||
private EstudanteRepositorio estudanteRepositorio;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Mock
|
||||
private UtilJwt utilJwt;
|
||||
|
||||
@InjectMocks
|
||||
private EstudanteServico estudanteServico;
|
||||
|
||||
private RequisicaoCadastroDTO requisicaoCadastro;
|
||||
private RequisicaoLoginDTO requisicaoLogin;
|
||||
private Estudante estudante;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
requisicaoCadastro = new RequisicaoCadastroDTO("Teste", "teste@teste.com", "123456", "Curso", 1, true);
|
||||
requisicaoLogin = new RequisicaoLoginDTO("teste@teste.com", "123456");
|
||||
|
||||
estudante = new Estudante();
|
||||
estudante.setId("1");
|
||||
estudante.setNome("Teste");
|
||||
estudante.setEmail("teste@teste.com");
|
||||
estudante.setSenha("encodedPassword");
|
||||
estudante.setCurso("Curso");
|
||||
estudante.setPeriodo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveCadastrarEstudanteComSucesso() {
|
||||
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||
when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");
|
||||
when(estudanteRepositorio.save(any(Estudante.class))).thenReturn(estudante);
|
||||
|
||||
RespostaEstudanteDTO resposta = estudanteServico.cadastrar(requisicaoCadastro);
|
||||
|
||||
assertNotNull(resposta);
|
||||
assertEquals("Teste", resposta.nome());
|
||||
verify(estudanteRepositorio, times(1)).save(any(Estudante.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveLancarExcecaoAoCadastrarEmailExistente() {
|
||||
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
|
||||
|
||||
assertThrows(ExcecaoNegocio.class, () -> estudanteServico.cadastrar(requisicaoCadastro));
|
||||
verify(estudanteRepositorio, never()).save(any(Estudante.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveRealizarLoginComSucesso() {
|
||||
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
|
||||
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true);
|
||||
when(utilJwt.generateToken(anyString())).thenReturn("token123");
|
||||
|
||||
RespostaLoginDTO resposta = estudanteServico.login(requisicaoLogin);
|
||||
|
||||
assertNotNull(resposta);
|
||||
assertEquals("token123", resposta.token());
|
||||
assertEquals("Teste", resposta.estudante().nome());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveRejeitarSenhaInvalida() {
|
||||
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
|
||||
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false);
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveRejeitarEmailNaoEncontrado() {
|
||||
when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
|
||||
|
||||
assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package com.agendaestudantil.servico;
|
||||
|
||||
import com.agendaestudantil.dto.RequisicaoTarefaDTO;
|
||||
import com.agendaestudantil.dto.RespostaTarefaDTO;
|
||||
import com.agendaestudantil.entidade.Tarefa;
|
||||
import com.agendaestudantil.excecao.ExcecaoNegocio;
|
||||
import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
|
||||
import com.agendaestudantil.repositorio.TarefaRepositorio;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class TarefaServicoTest {
|
||||
|
||||
@Mock
|
||||
private TarefaRepositorio tarefaRepositorio;
|
||||
|
||||
@Mock
|
||||
private SecurityContext securityContext;
|
||||
|
||||
@Mock
|
||||
private Authentication authentication;
|
||||
|
||||
@InjectMocks
|
||||
private TarefaServico tarefaServico;
|
||||
|
||||
private RequisicaoTarefaDTO requisicaoTarefa;
|
||||
private Tarefa tarefa;
|
||||
private final String estudanteId = "estudante123";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
requisicaoTarefa = new RequisicaoTarefaDTO(
|
||||
"Estudar Spring",
|
||||
"Aprender Spring Boot 3",
|
||||
Tarefa.Prioridade.ALTA,
|
||||
Tarefa.StatusTarefa.PENDENTE,
|
||||
LocalDate.now(),
|
||||
null);
|
||||
|
||||
tarefa = new Tarefa();
|
||||
tarefa.setId("tarefa1");
|
||||
tarefa.setTitulo("Estudar Spring");
|
||||
tarefa.setDescricao("Aprender Spring Boot 3");
|
||||
tarefa.setPrioridade(Tarefa.Prioridade.ALTA);
|
||||
tarefa.setStatus(Tarefa.StatusTarefa.PENDENTE);
|
||||
tarefa.setDataEntrega(LocalDate.now());
|
||||
tarefa.setEstudanteId(estudanteId);
|
||||
}
|
||||
|
||||
private void mockAuthentication(String user) {
|
||||
when(securityContext.getAuthentication()).thenReturn(authentication);
|
||||
when(authentication.getName()).thenReturn(user);
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveCriarTarefaComSucesso() {
|
||||
mockAuthentication(estudanteId);
|
||||
when(tarefaRepositorio.save(any(Tarefa.class))).thenReturn(tarefa);
|
||||
|
||||
RespostaTarefaDTO resposta = tarefaServico.criarTarefa(requisicaoTarefa);
|
||||
|
||||
assertNotNull(resposta);
|
||||
assertEquals("Estudar Spring", resposta.titulo());
|
||||
verify(tarefaRepositorio, times(1)).save(any(Tarefa.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveLancarExcecaoAoCriarTarefaParaOutroUsuario() {
|
||||
String outroId = "outroEstudante";
|
||||
mockAuthentication(outroId);
|
||||
when(tarefaRepositorio.save(any(Tarefa.class))).thenAnswer(inv -> {
|
||||
Tarefa t = inv.getArgument(0);
|
||||
t.setId("tarefa2");
|
||||
return t;
|
||||
});
|
||||
|
||||
RespostaTarefaDTO resposta = tarefaServico.criarTarefa(requisicaoTarefa);
|
||||
|
||||
assertNotNull(resposta);
|
||||
assertEquals(outroId, resposta.estudanteId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveListarTarefasPorEstudanteComSucesso() {
|
||||
mockAuthentication(estudanteId);
|
||||
when(tarefaRepositorio.findByEstudanteId(estudanteId)).thenReturn(List.of(tarefa));
|
||||
|
||||
List<RespostaTarefaDTO> tarefas = tarefaServico.listarTarefasPorEstudante(estudanteId);
|
||||
|
||||
assertFalse(tarefas.isEmpty());
|
||||
assertEquals(1, tarefas.size());
|
||||
assertEquals("Estudar Spring", tarefas.get(0).titulo());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveMudarStatusTarefaParaConcluidoComSucesso() {
|
||||
mockAuthentication(estudanteId);
|
||||
when(tarefaRepositorio.findById(anyString())).thenReturn(Optional.of(tarefa));
|
||||
when(tarefaRepositorio.save(any(Tarefa.class))).thenReturn(tarefa);
|
||||
|
||||
RespostaTarefaDTO resposta = tarefaServico.marcarConcluida("tarefa1");
|
||||
|
||||
assertNotNull(resposta);
|
||||
verify(tarefaRepositorio, times(1)).save(any(Tarefa.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deveLancarExcecaoQuandoTarefaNaoEncontrada() {
|
||||
when(tarefaRepositorio.findById(anyString())).thenReturn(Optional.empty());
|
||||
|
||||
assertThrows(ExcecaoRecursoNaoEncontrado.class, () -> tarefaServico.buscarTarefaPorId("tarefaNaoExistente"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: black;
|
||||
margin: 0;
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(139, 141, 147, 0.911);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
#log {
|
||||
width: 100% !important;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
#topo {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: #111;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #C0392B 47%, #7A4951 73%, #114455 87%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding-left: 20px;
|
||||
|
||||
}
|
||||
|
||||
#textotop {
|
||||
font-size: 38px;
|
||||
margin: 0;
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
}
|
||||
|
||||
#log {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#campo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#menslog {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#emailid,
|
||||
#senhaid,
|
||||
#nomeid,
|
||||
#csenhaid {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#logbtn {
|
||||
align-self: center;
|
||||
width: 50%;
|
||||
padding: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
background-color: #C0392B;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
#logbtn:hover {
|
||||
background-color: #A03224;
|
||||
}
|
||||
|
||||
.mens {
|
||||
align-self: center;
|
||||
}
|
||||
.card {
|
||||
background-color: rgb(216, 214, 197);
|
||||
border-radius: 16px;
|
||||
padding: 50px 35px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||
width: 400px;
|
||||
max-width: 90%;
|
||||
margin-top: 70px; /* Compensa a barra fixa do topo */
|
||||
}
|
||||