├── 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 | 4 | 5 | Simulador de Grade 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |

Simulador de Grade

26 | 27 | 28 |
29 | 30 |
31 |

Adicionar Disciplina

32 | 36 | 40 | 44 |

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 | 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 | --------------------------------------------------------------------------------