├── lawd.png
├── lawd-icon.jpg
├── OfertaDCOMP-2025.pdf
├── LICENSE
├── index.html
├── README.md
├── styles.css
└── script.js
/lawd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lawd-UFS/SIMGrade/HEAD/lawd.png
--------------------------------------------------------------------------------
/lawd-icon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lawd-UFS/SIMGrade/HEAD/lawd-icon.jpg
--------------------------------------------------------------------------------
/OfertaDCOMP-2025.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lawd-UFS/SIMGrade/HEAD/OfertaDCOMP-2025.pdf
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 LAWD
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Caso sua disciplina tenha horário alternados, por exemplo: "2N34 4N12" adicione a disciplina primeiro com 2N34 depois repita o processo novamente, preservando os campos de Disciplina e Professor e alterando apenas horário para 4N12
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
Editar Disciplina
67 |
71 |
75 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SIMGrade
2 |
3 | Bem-vindo ao Simulador de Grade! Este é um webapp desenvolvido pela LAWD, ele permite visualizar e gerenciar suas grades horárias de forma interativa e intuitiva.
4 |
5 |
6 | Descrição
7 | Funcionalidades
8 | Tecnologias Utilizadas
9 | Como Executar o Projeto
10 | Instruções de Uso
11 | Organização dos Arquivos
12 | Contribuição
13 | Licença
14 | Agradecimentos
15 |
16 | Descrição
17 |
18 | O Simulador de Grade é uma ferramenta desenvolvida para auxiliar estudantes a planejar suas disciplinas de acordo com os horários disponíveis. Com uma interface moderna e amigável, o webapp permite adicionar disciplinas, verificar conflitos de horários, editar ou excluir disciplinas e salvar a grade como imagem.
19 | Funcionalidades
20 |
21 | Adicionar Disciplinas: Insira o nome da disciplina, nome do professor e código de horário para adicionar à grade.
22 | Verificação de Conflitos: O sistema alerta se houver conflitos de horário com disciplinas já adicionadas.
23 | Editar/Excluir Disciplinas: Clique em uma disciplina na grade para abrir um modal que permite editar ou excluir.
24 | Salvar Grade: Salve a grade como imagem (PNG) para compartilhar ou imprimir.
25 | Modo Claro/Escuro: Alterne entre modo claro e escuro conforme sua preferência.
26 | Persistência de Dados: As disciplinas adicionadas são salvas no navegador, permitindo continuar de onde parou.
27 |
28 |
29 | Tecnologias Utilizadas
30 |
31 | HTML5: Estrutura do webapp.
32 | CSS3: Estilização e design responsivo.
33 | JavaScript (ES6+): Funcionalidades interativas e manipulação do DOM.
34 | html2canvas: Biblioteca para salvar a grade como imagem.
35 | Google Fonts: Fonte "Roboto" para uma melhor tipografia.
36 |
37 | Como Executar o Projeto
38 |
39 | Clone o repositório ou faça o download dos arquivos:
40 |
41 | bash
42 |
43 | git clone https://github.com/seu-usuario/simulador-de-grade.git
44 |
45 | Navegue até o diretório do projeto:
46 |
47 | bash
48 |
49 | cd simulador-de-grade
50 |
51 | Abra o arquivo index.html em um navegador web:
52 | Você pode utilizar a extensão Live Server do VSCode para subir a aplicação ou
53 | Clicar com o botão direito no arquivo index.html e selecionar "Abrir com" e escolher seu navegador preferido.
54 |
55 | Instruções de Uso
56 |
57 | Adicionando Disciplinas:
58 | Preencha os campos:
59 | Nome da Disciplina: Nome da matéria que deseja adicionar.
60 | Nome do Professor: Nome do professor responsável pela disciplina.
61 | Código de Horário: Código no formato DMTN (ex: 24T12).
62 | Dias: Números de 2 a 7 representando os dias da semana (Segunda a Sábado).
63 | Período:
64 | M - Manhã
65 | T - Tarde
66 | N - Noite
67 | Horários: Números de 1 a 6 representando os horários no período.
68 | Clique em "Adicionar à Grade".
69 |
70 | Verificação de Conflitos:
71 | O sistema avisará se o horário desejado já está ocupado por outra disciplina.
72 |
73 | Editar ou Excluir Disciplinas:
74 | Clique em uma disciplina na grade para abrir o modal de edição.
75 | Faça as alterações necessárias ou clique em "Excluir Disciplina" para removê-la.
76 |
77 | Salvar Grade como Imagem:
78 | Clique em "Salvar Grade como Imagem" para baixar a grade atual em formato PNG.
79 |
80 | Alternar Modo Claro/Escuro:
81 | Clique em "Ativar Modo Claro" ou "Ativar Modo Escuro" para alternar entre os modos.
82 |
83 | Visualização das Disciplinas Adicionadas:
84 | As disciplinas adicionadas são listadas acima da grade para fácil referência.
85 |
86 | Organização dos Arquivos
87 |
88 | index.html: Estrutura HTML do webapp.
89 | styles.css: Estilos e design do webapp.
90 | script.js: Lógica e funcionalidades interativas.
91 | README.md: Este documento.
92 |
93 |
94 | Licença
95 |
96 | Este projeto está licenciado sob a licença MIT - veja o arquivo LICENSE para detalhes.
97 | Agradecimentos
98 |
99 | Equipe Desenvolvedora: Agradecemos a todos os colaboradores que tornaram este projeto possível.
100 | Usuários: Obrigado por usar o Simulador de Grade Horária! Seu feedback é importante para nós.
101 | Comunidade: Agradecemos à comunidade de desenvolvedores pelas ferramentas e bibliotecas utilizadas.
102 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | /* Estilos CSS */
2 | body {
3 | font-family: 'Roboto', sans-serif;
4 | margin: 20px;
5 | background-color: var(--bg-color);
6 | color: var(--text-color);
7 | }
8 | .container {
9 | text-align: center;
10 | margin-bottom: 20px;
11 | }
12 | .logo {
13 | width: 150px;
14 | height: 150px;
15 | border-radius: 50%;
16 | object-fit: cover;
17 | margin-bottom: 20px;
18 | }
19 | .container h1 {
20 | font-size: 36px;
21 | margin-bottom: 10px;
22 | }
23 | .container h2 {
24 | font-size: 24px;
25 | margin-bottom: 20px;
26 | }
27 | .input-form {
28 | display: inline-block;
29 | text-align: center;
30 | margin-bottom: 20px;
31 | }
32 | .input-form label {
33 | display: block;
34 | margin-bottom: 10px;
35 | font-size: 16px;
36 | font-weight: 500;
37 | }
38 | .input-form input {
39 | width: 100%;
40 | max-width: 300px;
41 | padding: 10px;
42 | background-color: var(--input-bg-color);
43 | color: var(--text-color);
44 | border: 1px solid var(--border-color);
45 | border-radius: 4px;
46 | margin-bottom: 10px;
47 | font-size: 16px;
48 | }
49 | .input-form button {
50 | margin-top: 10px;
51 | padding: 10px 20px;
52 | background-color: var(--button-bg-color);
53 | color: var(--button-text-color);
54 | border: none;
55 | cursor: pointer;
56 | border-radius: 4px;
57 | font-size: 16px;
58 | transition: background-color 0.3s;
59 | margin: 5px;
60 | }
61 | .input-form button:hover {
62 | background-color: var(--button-hover-bg-color);
63 | }
64 | .timetable {
65 | display: grid;
66 | grid-template-columns: 120px repeat(6, 1fr);
67 | grid-auto-rows: 60px;
68 | border: 1px solid var(--border-color);
69 | margin-top: 20px;
70 | overflow-x: auto;
71 | }
72 | .timetable .cell {
73 | border: 1px solid var(--border-color);
74 | position: relative;
75 | background-color: var(--cell-bg-color);
76 | color: var(--text-color);
77 | }
78 | .timetable .header {
79 | background-color: var(--header-bg-color);
80 | font-weight: 500;
81 | text-align: center;
82 | display: flex;
83 | align-items: center;
84 | justify-content: center;
85 | color: var(--text-color);
86 | font-size: 16px;
87 | }
88 | .timetable .time-cell {
89 | background-color: var(--time-cell-bg-color);
90 | text-align: right;
91 | padding-right: 10px;
92 | font-weight: bold;
93 | color: var(--text-color);
94 | font-size: 14px;
95 | display: flex;
96 | align-items: center;
97 | }
98 | .card {
99 | background-color: var(--card-bg-color);
100 | color: var(--card-text-color);
101 | padding: 10px;
102 | position: absolute;
103 | top: 5px;
104 | bottom: 5px;
105 | left: 5px;
106 | right: 5px;
107 | overflow: hidden;
108 | text-align: center;
109 | border-radius: 4px;
110 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
111 | cursor: pointer;
112 | transition: transform 0.2s;
113 | }
114 | .card:hover {
115 | transform: scale(1.02);
116 | }
117 | /* Estilos para o modal */
118 | .modal {
119 | display: none;
120 | position: fixed;
121 | z-index: 1000;
122 | left: 0;
123 | top: 0;
124 | width: 100%;
125 | height: 100%;
126 | overflow: auto;
127 | background-color: rgba(0,0,0,0.5);
128 | }
129 | .modal-content {
130 | background-color: var(--bg-color);
131 | margin: 10% auto;
132 | padding: 20px;
133 | border: 1px solid var(--border-color);
134 | width: 90%;
135 | max-width: 400px;
136 | border-radius: 8px;
137 | }
138 | .modal-content h3 {
139 | margin-top: 0;
140 | }
141 | .modal-content label {
142 | display: block;
143 | margin-bottom: 10px;
144 | font-size: 16px;
145 | font-weight: 500;
146 | }
147 | .modal-content input {
148 | width: 100%;
149 | padding: 10px;
150 | background-color: var(--input-bg-color);
151 | color: var(--text-color);
152 | border: 1px solid var(--border-color);
153 | border-radius: 4px;
154 | margin-bottom: 10px;
155 | font-size: 16px;
156 | }
157 | .modal-content button {
158 | margin-top: 10px;
159 | padding: 10px 20px;
160 | background-color: var(--button-bg-color);
161 | color: var(--button-text-color);
162 | border: none;
163 | cursor: pointer;
164 | border-radius: 4px;
165 | font-size: 16px;
166 | transition: background-color 0.3s;
167 | margin: 5px;
168 | }
169 | .modal-content button:hover {
170 | background-color: var(--button-hover-bg-color);
171 | }
172 | /* Estilos para a lista de disciplinas */
173 | .disciplinas-adicionadas {
174 | margin-top: 20px;
175 | text-align: center;
176 | font-size: 18px;
177 | font-weight: 500;
178 | }
179 | .disciplinas-adicionadas div {
180 | margin-bottom: 5px;
181 | }
182 |
183 | /* Variáveis de Cor para Modo Escuro */
184 | :root {
185 | --bg-color: #080808;
186 | --text-color: #ffffff;
187 | --input-bg-color: #1e1e1e;
188 | --border-color: #444;
189 | --button-bg-color: #7b00ff;
190 | --button-text-color: #ffffff;
191 | --button-hover-bg-color: #a34eff;
192 | --cell-bg-color: #1e1e1e;
193 | --header-bg-color: #2c2c2c;
194 | --time-cell-bg-color: #1e1e1e;
195 | --card-bg-color: #7b00ff;
196 | --card-text-color: #ffffff;
197 | }
198 | /* Variáveis de Cor para Modo Claro */
199 | .light-mode {
200 | --bg-color: #ffffff;
201 | --text-color: #000000;
202 | --input-bg-color: #ffffff;
203 | --border-color: #ccc;
204 | --button-bg-color: #7b00ff;
205 | --button-text-color: #ffffff;
206 | --button-hover-bg-color: #a34eff;
207 | --cell-bg-color: #ffffff;
208 | --header-bg-color: #f0f0f0;
209 | --time-cell-bg-color: #fafafa;
210 | --card-bg-color: #a34eff;
211 | --card-text-color: #000000;
212 | }
213 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | // Definição dos dias e horários
2 | const dias = [null, 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'];
3 | const horarios = [
4 | '07:30 - 08:15',
5 | '08:15 - 09:00',
6 | '09:15 - 10:00',
7 | '10:00 - 10:45',
8 | '11:00 - 11:45',
9 | '11:45 - 12:30',
10 | '13:30 - 14:15',
11 | '14:15 - 15:00',
12 | '15:15 - 16:00',
13 | '16:00 - 16:45',
14 | '17:00 - 17:45',
15 | '17:45 - 18:30',
16 | '19:00 - 19:45',
17 | '19:45 - 20:30',
18 | '20:45 - 21:30',
19 | '21:30 - 22:15'
20 | ];
21 |
22 | let darkMode = true;
23 |
24 | // Estrutura para armazenar os horários ocupados
25 | let occupiedSlots = {};
26 |
27 | // Variável para armazenar o ID da disciplina em edição
28 | let disciplinaEmEdicaoId = null;
29 |
30 | // Gera a grade de horários vazia
31 | function gerarGrade() {
32 | const timetable = document.getElementById('timetable');
33 | timetable.innerHTML = '';
34 |
35 | // Cabeçalho vazio para a coluna de horários
36 | const emptyHeaderCell = document.createElement('div');
37 | emptyHeaderCell.classList.add('cell', 'header');
38 | timetable.appendChild(emptyHeaderCell);
39 |
40 | // Cabeçalhos dos dias
41 | for (let i = 1; i < dias.length; i++) {
42 | const headerCell = document.createElement('div');
43 | headerCell.classList.add('cell', 'header');
44 | headerCell.innerText = dias[i];
45 | timetable.appendChild(headerCell);
46 | }
47 |
48 | // Células de horário
49 | for (let i = 0; i < horarios.length; i++) {
50 | // Célula de horário na primeira coluna
51 | const timeCell = document.createElement('div');
52 | timeCell.classList.add('cell', 'time-cell');
53 | timeCell.innerText = horarios[i];
54 | timetable.appendChild(timeCell);
55 |
56 | // Células dos dias
57 | for (let j = 1; j < dias.length; j++) {
58 | const cell = document.createElement('div');
59 | cell.classList.add('cell');
60 | timetable.appendChild(cell);
61 | }
62 | }
63 | }
64 |
65 | // Salva a grade no localStorage
66 | function saveSchedule() {
67 | localStorage.setItem('occupiedSlots', JSON.stringify(occupiedSlots));
68 | }
69 |
70 | // Carrega a grade do localStorage
71 | function loadSchedule() {
72 | const storedSlots = localStorage.getItem('occupiedSlots');
73 | if (storedSlots) {
74 | occupiedSlots = JSON.parse(storedSlots);
75 | for (let slotKey in occupiedSlots) {
76 | const slot = occupiedSlots[slotKey];
77 | const [indiceDia, indiceHorario] = slotKey.split('-').map(Number);
78 | const cellIndex = (indiceHorario * (dias.length)) + indiceDia;
79 | const cell = document.getElementsByClassName('cell')[dias.length + cellIndex];
80 |
81 | // Criação do card
82 | const card = document.createElement('div');
83 | card.classList.add('card');
84 | card.innerHTML = `${slot.disciplina} ${slot.professor}`;
85 |
86 | // Adicionar event listener para abrir o modal
87 | card.addEventListener('click', function() {
88 | abrirModal(slot.id);
89 | });
90 |
91 | cell.appendChild(card);
92 | }
93 | }
94 | }
95 |
96 | // Adiciona a disciplina na grade com base no código
97 | function adicionarDisciplina() {
98 | const nomeDisciplina = document.getElementById('nomeDisciplina').value.trim();
99 | const nomeProfessor = document.getElementById('nomeProfessor').value.trim();
100 | const codigoHorario = document.getElementById('codigoHorario').value.toUpperCase().trim();
101 |
102 | if (!nomeDisciplina || !nomeProfessor || !codigoHorario) {
103 | alert('Por favor, preencha todos os campos.');
104 | return;
105 | }
106 |
107 | // Análise do código de horário
108 | const diasCodigoMatch = codigoHorario.match(/^[2-7]+/g);
109 | const periodoMatch = codigoHorario.match(/[MTN]/g);
110 | const horariosCodigoMatch = codigoHorario.match(/\d+$/g);
111 |
112 | if (!diasCodigoMatch || !periodoMatch || !horariosCodigoMatch) {
113 | alert('Código de horário inválido. Por favor, siga o formato correto.');
114 | return;
115 | }
116 |
117 | const diasCodigo = diasCodigoMatch[0].split('');
118 | const periodo = periodoMatch[0];
119 | const horariosCodigo = horariosCodigoMatch[0].split('');
120 |
121 | // Mapeamento de período para índices de horários
122 | let offset;
123 | if (periodo === 'M') {
124 | offset = 0;
125 | } else if (periodo === 'T') {
126 | offset = 6;
127 | } else if (periodo === 'N') {
128 | offset = 12;
129 | } else {
130 | alert('Período inválido. Use M, T ou N.');
131 | return;
132 | }
133 |
134 | const conflictingSlots = [];
135 | const slotsToOccupy = [];
136 |
137 | const disciplinaId = Date.now(); // ID único para a disciplina
138 |
139 | // Primeiro, verificar conflitos
140 | diasCodigo.forEach(dia => {
141 | horariosCodigo.forEach(horario => {
142 | const indiceDia = parseInt(dia) - 1; // Ajuste no índice do dia
143 | const indiceHorario = offset + parseInt(horario) - 1;
144 |
145 | // Cálculo do índice da célula na grade
146 | const cellIndex = (indiceHorario * (dias.length)) + indiceDia;
147 |
148 | const slotKey = `${indiceDia}-${indiceHorario}`;
149 | if (occupiedSlots[slotKey]) {
150 | conflictingSlots.push({
151 | dia: dias[parseInt(dia)],
152 | horario: horarios[indiceHorario],
153 | disciplina: occupiedSlots[slotKey].disciplina,
154 | professor: occupiedSlots[slotKey].professor
155 | });
156 | } else {
157 | slotsToOccupy.push({
158 | indiceDia,
159 | indiceHorario,
160 | cellIndex,
161 | slotKey
162 | });
163 | }
164 | });
165 | });
166 |
167 | if (conflictingSlots.length > 0) {
168 | let mensagem = 'Conflito de horário detectado com as seguintes disciplinas:\n';
169 | conflictingSlots.forEach(conflict => {
170 | mensagem += `- ${conflict.disciplina} (${conflict.professor}) em ${conflict.dia}, ${conflict.horario}\n`;
171 | });
172 | alert(mensagem);
173 | return;
174 | }
175 |
176 | // Se não houver conflitos, adicionar a disciplina
177 | slotsToOccupy.forEach(slot => {
178 | const { indiceDia, indiceHorario, cellIndex, slotKey } = slot;
179 |
180 | // Como adicionamos uma coluna a mais para os horários, precisamos somar mais uma posição
181 | const cell = document.getElementsByClassName('cell')[dias.length + cellIndex];
182 |
183 | // Criação do card
184 | const card = document.createElement('div');
185 | card.classList.add('card');
186 | card.innerHTML = `${nomeDisciplina} ${nomeProfessor}`;
187 |
188 | // Adicionar event listener para abrir o modal
189 | card.addEventListener('click', function() {
190 | abrirModal(disciplinaId);
191 | });
192 |
193 | cell.appendChild(card);
194 |
195 | // Marcar o slot como ocupado
196 | occupiedSlots[slotKey] = {
197 | id: disciplinaId,
198 | disciplina: nomeDisciplina,
199 | professor: nomeProfessor,
200 | codigoHorario: codigoHorario
201 | };
202 | });
203 |
204 | // Salvar a grade atualizada
205 | saveSchedule();
206 |
207 | // Limpar os campos de entrada
208 | document.getElementById('nomeDisciplina').value = '';
209 | document.getElementById('nomeProfessor').value = '';
210 | document.getElementById('codigoHorario').value = '';
211 | }
212 |
213 | // Função para abrir o modal
214 | function abrirModal(disciplinaId) {
215 | disciplinaEmEdicaoId = disciplinaId;
216 | const slots = Object.entries(occupiedSlots).filter(([key, value]) => value.id === disciplinaId);
217 | if (slots.length === 0) {
218 | return; // Nenhum slot encontrado
219 | }
220 |
221 | const disciplina = slots[0][1];
222 |
223 | // Preencher os campos do modal com os dados atuais
224 | document.getElementById('modalNomeDisciplina').value = disciplina.disciplina;
225 | document.getElementById('modalNomeProfessor').value = disciplina.professor;
226 | document.getElementById('modalCodigoHorario').value = disciplina.codigoHorario;
227 |
228 | // Exibir o modal
229 | document.getElementById('modal').style.display = 'block';
230 | }
231 |
232 | // Função para fechar o modal
233 | function fecharModal() {
234 | document.getElementById('modal').style.display = 'none';
235 | disciplinaEmEdicaoId = null;
236 | }
237 |
238 | // Função para salvar as alterações no modal
239 | function salvarEdicao() {
240 | const novoNomeDisciplina = document.getElementById('modalNomeDisciplina').value.trim();
241 | const novoNomeProfessor = document.getElementById('modalNomeProfessor').value.trim();
242 | const novoCodigoHorario = document.getElementById('modalCodigoHorario').value.toUpperCase().trim();
243 |
244 | if (!novoNomeDisciplina || !novoNomeProfessor || !novoCodigoHorario) {
245 | alert('Por favor, preencha todos os campos.');
246 | return;
247 | }
248 |
249 | // Remover a disciplina atual
250 | removerDisciplina(disciplinaEmEdicaoId, false);
251 |
252 | // Temporariamente armazenar o occupiedSlots atual
253 | const occupiedSlotsTemp = { ...occupiedSlots };
254 |
255 | // Atualizar os dados da disciplina
256 | const nomeDisciplina = novoNomeDisciplina;
257 | const nomeProfessor = novoNomeProfessor;
258 | const codigoHorario = novoCodigoHorario;
259 | const disciplinaId = disciplinaEmEdicaoId;
260 |
261 | // Reutilizar a função adicionarDisciplina com os novos dados
262 | // Porém, precisamos adaptar a função para aceitar parâmetros
263 |
264 | const resultado = adicionarDisciplinaEditada(nomeDisciplina, nomeProfessor, codigoHorario, disciplinaId);
265 |
266 | if (resultado) {
267 | // Fechar o modal
268 | fecharModal();
269 | } else {
270 | // Se houve conflito, restaurar o occupiedSlots original
271 | occupiedSlots = { ...occupiedSlotsTemp };
272 | saveSchedule();
273 | }
274 | }
275 |
276 | // Função para adicionar disciplina editada
277 | function adicionarDisciplinaEditada(nomeDisciplina, nomeProfessor, codigoHorario, disciplinaId) {
278 | // (O código desta função é semelhante ao adicionarDisciplina, mas adaptado)
279 | // Análise do código de horário
280 | const diasCodigoMatch = codigoHorario.match(/^[2-7]+/g);
281 | const periodoMatch = codigoHorario.match(/[MTN]/g);
282 | const horariosCodigoMatch = codigoHorario.match(/\d+$/g);
283 |
284 | if (!diasCodigoMatch || !periodoMatch || !horariosCodigoMatch) {
285 | alert('Código de horário inválido. Por favor, siga o formato correto.');
286 | return false;
287 | }
288 |
289 | const diasCodigo = diasCodigoMatch[0].split('');
290 | const periodo = periodoMatch[0];
291 | const horariosCodigo = horariosCodigoMatch[0].split('');
292 |
293 | // Mapeamento de período para índices de horários
294 | let offset;
295 | if (periodo === 'M') {
296 | offset = 0;
297 | } else if (periodo === 'T') {
298 | offset = 6;
299 | } else if (periodo === 'N') {
300 | offset = 12;
301 | } else {
302 | alert('Período inválido. Use M, T ou N.');
303 | return false;
304 | }
305 |
306 | const conflictingSlots = [];
307 | const slotsToOccupy = [];
308 |
309 | // Primeiro, verificar conflitos
310 | diasCodigo.forEach(dia => {
311 | horariosCodigo.forEach(horario => {
312 | const indiceDia = parseInt(dia) - 1; // Ajuste no índice do dia
313 | const indiceHorario = offset + parseInt(horario) - 1;
314 |
315 | // Cálculo do índice da célula na grade
316 | const cellIndex = (indiceHorario * (dias.length)) + indiceDia;
317 |
318 | const slotKey = `${indiceDia}-${indiceHorario}`;
319 | if (occupiedSlots[slotKey]) {
320 | conflictingSlots.push({
321 | dia: dias[parseInt(dia)],
322 | horario: horarios[indiceHorario],
323 | disciplina: occupiedSlots[slotKey].disciplina,
324 | professor: occupiedSlots[slotKey].professor
325 | });
326 | } else {
327 | slotsToOccupy.push({
328 | indiceDia,
329 | indiceHorario,
330 | cellIndex,
331 | slotKey
332 | });
333 | }
334 | });
335 | });
336 |
337 | if (conflictingSlots.length > 0) {
338 | let mensagem = 'Conflito de horário detectado com as seguintes disciplinas:\n';
339 | conflictingSlots.forEach(conflict => {
340 | mensagem += `- ${conflict.disciplina} (${conflict.professor}) em ${conflict.dia}, ${conflict.horario}\n`;
341 | });
342 | alert(mensagem);
343 | return false;
344 | }
345 |
346 | // Se não houver conflitos, adicionar a disciplina
347 | slotsToOccupy.forEach(slot => {
348 | const { indiceDia, indiceHorario, cellIndex, slotKey } = slot;
349 |
350 | // Como adicionamos uma coluna a mais para os horários, precisamos somar mais uma posição
351 | const cell = document.getElementsByClassName('cell')[dias.length + cellIndex];
352 |
353 | // Remover qualquer conteúdo anterior
354 | cell.innerHTML = '';
355 |
356 | // Criação do card
357 | const card = document.createElement('div');
358 | card.classList.add('card');
359 | card.innerHTML = `${nomeDisciplina} ${nomeProfessor}`;
360 |
361 | // Adicionar event listener para abrir o modal
362 | card.addEventListener('click', function() {
363 | abrirModal(disciplinaId);
364 | });
365 |
366 | cell.appendChild(card);
367 |
368 | // Marcar o slot como ocupado
369 | occupiedSlots[slotKey] = {
370 | id: disciplinaId,
371 | disciplina: nomeDisciplina,
372 | professor: nomeProfessor,
373 | codigoHorario: codigoHorario
374 | };
375 | });
376 |
377 | // Salvar a grade atualizada
378 | saveSchedule();
379 |
380 | return true;
381 | }
382 |
383 | // Função para remover disciplina
384 | function removerDisciplina(disciplinaId, atualizarTela = true) {
385 | const slotsToRemove = Object.entries(occupiedSlots).filter(([key, value]) => value.id === disciplinaId);
386 |
387 | slotsToRemove.forEach(([slotKey, value]) => {
388 | const [indiceDia, indiceHorario] = slotKey.split('-').map(Number);
389 | const cellIndex = (indiceHorario * (dias.length)) + indiceDia;
390 | const cell = document.getElementsByClassName('cell')[dias.length + cellIndex];
391 | cell.innerHTML = '';
392 |
393 | // Remover o slot do occupiedSlots
394 | delete occupiedSlots[slotKey];
395 | });
396 |
397 | // Salvar a grade atualizada
398 | saveSchedule();
399 |
400 | if (atualizarTela) {
401 | // Fechar o modal
402 | fecharModal();
403 | }
404 | }
405 |
406 | // Função para excluir disciplina
407 | function excluirDisciplina() {
408 | if (confirm('Tem certeza que deseja excluir esta disciplina?')) {
409 | removerDisciplina(disciplinaEmEdicaoId);
410 | fecharModal();
411 | }
412 | }
413 |
414 | // Alterna o modo escuro/claro
415 | function toggleLightMode() {
416 | darkMode = !darkMode;
417 | if (darkMode) {
418 | document.body.classList.remove('light-mode');
419 | document.getElementById('lightModeButton').innerText = 'Ativar Modo Claro';
420 | } else {
421 | document.body.classList.add('light-mode');
422 | document.getElementById('lightModeButton').innerText = 'Ativar Modo Escuro';
423 | }
424 | }
425 |
426 | // Salva a grade como imagem
427 | function salvarComoImagem() {
428 | const tabela = document.getElementById('timetable');
429 | html2canvas(tabela).then(canvas => {
430 | const link = document.createElement('a');
431 | link.download = 'grade-horaria.png';
432 | link.href = canvas.toDataURL();
433 | link.click();
434 | });
435 | }
436 |
437 | // Inicializa a grade ao carregar a página
438 | window.onload = function() {
439 | gerarGrade();
440 | // Iniciar no modo escuro por padrão
441 | darkMode = true;
442 | document.body.classList.remove('light-mode');
443 | document.getElementById('lightModeButton').innerText = 'Ativar Modo Claro';
444 | // Carregar a grade do localStorage
445 | loadSchedule();
446 | };
447 |
448 | // ... (código existente)
449 |
450 | // Função para baixar o PDF
451 | function baixarPDF() {
452 | // Caminho para o arquivo PDF
453 | const pdfUrl = 'OfertaDCOMP-2025.pdf';
454 |
455 | // Cria um link temporário
456 | const link = document.createElement('a');
457 | link.href = pdfUrl;
458 | link.download = 'OfertaDCOMP-2025.pdf';
459 | link.click();
460 | }
461 |
462 | // Função para limpar todas as disciplinas adicionadas
463 | function limparMaterias() {
464 | if (confirm('Deseja realmente limpar todas as disciplinas adicionadas?')) {
465 | // Remove os dados do localStorage
466 | localStorage.removeItem('occupiedSlots');
467 | // Limpa o objeto de horários ocupados
468 | occupiedSlots = {};
469 | // Regenera a grade para limpar todos os cards
470 | gerarGrade();
471 | alert('Todas as disciplinas foram removidas.');
472 | }
473 | }
474 |
--------------------------------------------------------------------------------